diff options
472 files changed, 10055 insertions, 4220 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 36430b6a215d..6d74a840525b 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -16,6 +16,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.content.res.flags-aconfig-java{.generated_srcjars}", ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", ":android.os.flags-aconfig-java{.generated_srcjars}", @@ -47,6 +48,7 @@ aconfig_srcjars = [ ":android.credentials.flags-aconfig-java{.generated_srcjars}", ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", ":android.service.voice.flags-aconfig-java{.generated_srcjars}", + ":aconfig_midi_flags_java_lib{.generated_srcjars}", ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", ":com.android.net.flags-aconfig-java{.generated_srcjars}", ] @@ -305,6 +307,19 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Resources +aconfig_declarations { + name: "android.content.res.flags-aconfig", + package: "android.content.res", + srcs: ["core/java/android/content/res/*.aconfig"], +} + +java_aconfig_library { + name: "android.content.res.flags-aconfig-java", + aconfig_declarations: "android.content.res.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Media BetterTogether aconfig_declarations { name: "com.android.media.flags.bettertogether-aconfig", diff --git a/Android.bp b/Android.bp index a507465aa419..0c199a634725 100644 --- a/Android.bp +++ b/Android.bp @@ -227,7 +227,6 @@ java_library { "android.hardware.radio.messaging-V3-java", "android.hardware.radio.modem-V3-java", "android.hardware.radio.network-V3-java", - "android.hardware.radio.satellite-V1-java", "android.hardware.radio.sim-V3-java", "android.hardware.radio.voice-V3-java", "android.hardware.thermal-V1.0-java-constants", diff --git a/Ravenwood.bp b/Ravenwood.bp index 9218cc9bc3f8..da02298b7415 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -59,6 +59,7 @@ java_genrule_host { // Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules. java_genrule_host { name: "framework-minus-apex.ravenwood", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-minus-apex.ravenwood-base{ravenwood.jar}", @@ -66,5 +67,4 @@ java_genrule_host { out: [ "framework-minus-apex.ravenwood.jar", ], - visibility: ["//visibility:public"], } diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp index 30b442336148..e1621008cc33 100644 --- a/api/ApiDocs.bp +++ b/api/ApiDocs.bp @@ -124,7 +124,6 @@ droidstubs { "packages/modules/Media/apex/aidl/stable", ], }, - extensions_info_file: ":sdk-extensions-info", } droidstubs { @@ -132,13 +131,7 @@ droidstubs { defaults: ["framework-doc-stubs-sources-default"], args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ", - api_levels_annotations_enabled: true, - api_levels_annotations_dirs: [ - "sdk-dir", - "api-versions-jars-dir", - ], - api_levels_sdk_type: "system", - extensions_info_file: ":sdk-extensions-info", + api_levels_module: "api_versions_system", } ///////////////////////////////////////////////////////////////////// diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index fa4bc0f98fa5..7e41660cf1a2 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -695,6 +695,7 @@ java_api_library { "api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -710,6 +711,7 @@ java_api_library { "system-api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -727,6 +729,7 @@ java_api_library { "test-api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -742,6 +745,7 @@ java_api_library { "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", ], + enable_validation: false, } java_api_library { @@ -761,6 +765,7 @@ java_api_library { "module-lib-api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -774,6 +779,7 @@ java_api_library { "stub-annotations", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -798,6 +804,7 @@ java_api_library { visibility: [ "//visibility:private", ], + enable_validation: false, } java_api_library { @@ -814,6 +821,7 @@ java_api_library { "android_module_lib_stubs_current.from-text", ], visibility: ["//visibility:public"], + enable_validation: false, } //////////////////////////////////////////////////////////////////////// diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index d9e72b83e46e..29a8dfa96a57 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -1,58 +1,4 @@ -android/adservices/ondevicepersonalization/DownloadCompletedInput.java:22: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onDownloadCompleted() IsolatedWorker#onDownloadCompleted()" in android.adservices.ondevicepersonalization.DownloadCompletedInput [101] -android/adservices/ondevicepersonalization/DownloadCompletedOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onDownloadCompleted() IsolatedWorker#onDownloadCompleted()" in android.adservices.ondevicepersonalization.DownloadCompletedOutput [101] -android/adservices/ondevicepersonalization/EventLogRecord.java:13: lint: Unresolved link/see tag "RequestRecordRecord" in android.adservices.ondevicepersonalization.EventLogRecord [101] -android/adservices/ondevicepersonalization/EventUrlProvider.java:43: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onEvent IsolatedWorker#onEvent" in android.adservices.ondevicepersonalization.EventUrlProvider [101] -android/adservices/ondevicepersonalization/ExecuteInput.java:22: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteInput [101] -android/adservices/ondevicepersonalization/ExecuteOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101] -android/adservices/ondevicepersonalization/ExecuteOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute() OnDevicePersonalizationManager#execute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101] -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] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:11: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:11: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedService#onExecute() IsolatedService#onExecute()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:19: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "SurfaceView#getHostToken()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "execute" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "#execute()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "View" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:64: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:69: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:70: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101] -android/adservices/ondevicepersonalization/RenderInput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onRender() IsolatedWorker#onRender()" in android.adservices.ondevicepersonalization.RenderInput [101] -android/adservices/ondevicepersonalization/RenderInput.java:53: lint: Unresolved link/see tag "onExecute" in android.adservices.ondevicepersonalization.RenderInput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#requestSurfacePackage() OnDevicePersonalizationManager#requestSurfacePackage()" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:31: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:31: lint: Unresolved link/see tag "getTemplateParams" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:41: lint: Unresolved link/see tag "getContent()" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:52: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput [101] -android/adservices/ondevicepersonalization/RenderOutput.java:102: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101] -android/adservices/ondevicepersonalization/RenderOutput.java:102: lint: Unresolved link/see tag "getTemplateParams" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101] -android/adservices/ondevicepersonalization/RenderOutput.java:114: lint: Unresolved link/see tag "getContent()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101] -android/adservices/ondevicepersonalization/RenderOutput.java:127: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101] -android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "View" in android.adservices.ondevicepersonalization.RenderingConfig [101] -android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RenderingConfig [101] -android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onRender() IsolatedWorker#onRender()" in android.adservices.ondevicepersonalization.RenderingConfig [101] -android/adservices/ondevicepersonalization/RenderingConfig.java:33: lint: Unresolved link/see tag "IsolatedSurface#getRemoteData" in android.adservices.ondevicepersonalization.RenderingConfig [101] -android/adservices/ondevicepersonalization/RenderingConfig.java:85: lint: Unresolved link/see tag "IsolatedSurface#getRemoteData" in android.adservices.ondevicepersonalization.RenderingConfig.Builder [101] -android/adservices/ondevicepersonalization/RequestLogRecord.java:19: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RequestLogRecord [101] -android/adservices/ondevicepersonalization/SurfacePackageToken.java:20: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.SurfacePackageToken [101] -android/adservices/ondevicepersonalization/WebViewEventInput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventInput [101] -android/adservices/ondevicepersonalization/WebViewEventInput.java:30: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.WebViewEventInput [101] -android/adservices/ondevicepersonalization/WebViewEventInput.java:41: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.EventUrlProvider#createEventTrackingUrlWithResponse() EventUrlProvider#createEventTrackingUrlWithResponse()" in android.adservices.ondevicepersonalization.WebViewEventInput [101] -android/adservices/ondevicepersonalization/WebViewEventOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventOutput [101] +// b/305195721 android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101] android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101] android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101] @@ -61,6 +7,8 @@ android/app/admin/DevicePolicyManager.java:7428: lint: Unresolved link/see tag " android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Drawables DevicePolicyResources.Drawables" in android.app.admin.DevicePolicyManager [101] android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyManager [101] android/app/admin/DevicePolicyResourcesManager.java:179: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyResourcesManager [101] + +// b/303477132 android/app/appsearch/AppSearchSchema.java:402: lint: Unresolved link/see tag "#getIndexableNestedProperties()" in android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder [101] android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.AppSearchSession [101] android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.AppSearchSession [101] @@ -73,6 +21,8 @@ android/app/appsearch/SearchSpec.java:244: lint: Unresolved link/see tag "Featur android/app/appsearch/SearchSpec.java:913: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec.Builder [101] android/app/appsearch/SearchSpec.java:925: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec.Builder [101] android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec.Builder [101] + +// b/303582215 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] android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101] android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables" in android.hardware.camera2.CameraCharacteristics [101] @@ -98,77 +48,37 @@ android/hardware/camera2/CaptureRequest.java:704: lint: Unresolved link/see tag android/hardware/camera2/CaptureRequest.java:1501: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureRequest [101] android/hardware/camera2/CaptureResult.java:923: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101] android/hardware/camera2/CaptureResult.java:2337: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ALARM AttributeSdkUsage#USAGE_ALARM" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANT AttributeSdkUsage#USAGE_ASSISTANT" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_GAME AttributeSdkUsage#USAGE_GAME" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_MEDIA AttributeSdkUsage#USAGE_MEDIA" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_NOTIFICATION_EVENT AttributeSdkUsage#USAGE_NOTIFICATION_EVENT" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_UNKNOWN AttributeSdkUsage#USAGE_UNKNOWN" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_VOICE_COMMUNICATION AttributeSdkUsage#USAGE_VOICE_COMMUNICATION" in android.media.AudioAttributes.Builder [101] -android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING" in android.media.AudioAttributes.Builder [101] -android/media/AudioFormat.java:963: lint: Unresolved link/see tag "android.media.AudioSystem#OUT_CHANNEL_COUNT_MAX AudioSystem#OUT_CHANNEL_COUNT_MAX" in android.media.AudioFormat.Builder [101] -android/media/AudioManager.java:275: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101] -android/media/AudioManager.java:287: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101] -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/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] -android/media/tv/TableResponse.java:82: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableResponse [101] -android/net/EthernetNetworkSpecifier.java:21: lint: Unresolved link/see tag "android.net.EthernetManager" in android.net.EthernetNetworkSpecifier [101] -android/net/eap/EapSessionConfig.java:120: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101] -android/net/eap/EapSessionConfig.java:135: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101] -android/net/eap/EapSessionConfig.java:148: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101] -android/net/eap/EapSessionConfig.java:161: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101] -android/net/eap/EapSessionConfig.java:288: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapAkaConfig [101] -android/net/eap/EapSessionConfig.java:390: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapAkaPrimeConfig [101] -android/net/eap/EapSessionConfig.java:587: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapSimConfig [101] -android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.MloLink [101] -android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.MloLink [101] -android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_6_GHZ WifiScanner#WIFI_BAND_6_GHZ" in android.net.wifi.MloLink [101] -android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_UNSPECIFIED WifiScanner#WIFI_BAND_UNSPECIFIED" in android.net.wifi.MloLink [101] -android/net/wifi/SoftApConfiguration.java:9: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder SoftApConfiguration.Builder" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/SoftApConfiguration.java:66: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setSsid(java.lang.String) Builder#setSsid(String)" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/SoftApConfiguration.java:85: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setWifiSsid(android.net.wifi.WifiSsid) Builder#setWifiSsid(WifiSsid)" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/SoftApConfiguration.java:96: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBssid(android.net.MacAddress) Builder#setBssid(MacAddress)" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/SoftApConfiguration.java:107: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setPassphrase(java.lang.String,int) Builder#setPassphrase(String, int)" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/SoftApConfiguration.java:118: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setHiddenSsid(boolean) Builder#setHiddenSsid(boolean)" in android.net.wifi.SoftApConfiguration [101] -android/net/wifi/WifiManager.java:764: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBands(int[]) SoftApConfiguration.Builder#setBands(int[])" in android.net.wifi.WifiManager [101] -android/net/wifi/WifiManager.java:764: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray) SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)" in android.net.wifi.WifiManager [101] -android/net/wifi/WifiManager.java:779: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBands(int[]) SoftApConfiguration.Builder#setBands(int[])" in android.net.wifi.WifiManager [101] -android/net/wifi/WifiManager.java:779: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray) SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)" in android.net.wifi.WifiManager [101] -android/net/wifi/WifiManager.java:2466: lint: Unresolved link/see tag "TelephonyManager#hasCarrierPrivileges()." in android.net.wifi.WifiManager [101] -android/net/wifi/aware/PublishConfig.java:50: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.PublishConfig [101] -android/net/wifi/aware/PublishConfig.java:50: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.PublishConfig [101] -android/net/wifi/aware/PublishConfig.java:249: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.PublishConfig.Builder [101] -android/net/wifi/aware/PublishConfig.java:249: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.PublishConfig.Builder [101] -android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig [101] -android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig [101] -android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101] -android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [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/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101] +// These are javadoc errors for @ChangeId constants, which are problematic to generate documentation +// for. They're not necessarily errors in the docs themselves but are also a limitation in the tool. +// Regardless, the docs currently generated for them is not good, but it is also not used directly +// in production at the moment. +// The main limitation is that all references must be fully qualified in order to resolve properly +// (aside from the normal limitatinos of only being able to @link public APIs). +// See the CompatInfo.java source file in doclava for more information. +android/net/wifi/SoftApConfiguration.java:171: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [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] -android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "STATE_HARDWARE_UNAVAILABLE" in android.service.voice.AlwaysOnHotwordDetector [101] -android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101] +android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101] +android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android [101] +android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "STATE_HARDWARE_UNAVAILABLE" in android [101] +android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector.Callback#onFailure" in android [101] +android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector.Callback#onUnknownFailure" in android [101] +android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector#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] -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] - +android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101] +com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" 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/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.IdentifierType#DAB_SID_EXT" in android [101] +com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101] +com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.RadioTuner" in android [101] +com/android/server/devicepolicy/DevicePolicyManagerService.java:861: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" 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] com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "Build.VERSION_CODES#UPSIDE_DOWN_CAKE API 34" in android [101] 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] +com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101] +com/android/server/pm/PackageInstallerSession.java:330: lint: Unresolved link/see tag "com.android.android.server.pm#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101] +com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]
\ No newline at end of file diff --git a/core/api/current.txt b/core/api/current.txt index dfe023a3c181..66aeb0f7cbaf 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3286,10 +3286,10 @@ package android.accessibilityservice { public abstract class AccessibilityService extends android.app.Service { ctor public AccessibilityService(); - method @Deprecated public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); - method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); - method @Deprecated public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl); - method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); + method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); + method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); + method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl); + method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method public boolean clearCache(); method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo); method public final void disableSelf(); @@ -3401,9 +3401,9 @@ package android.accessibilityservice { field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3 field public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; // 0x9 field public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; // 0x7 - field public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1 - field public static final int OVERLAY_RESULT_INVALID = 2; // 0x2 - field public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0 + field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1 + field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INVALID = 2; // 0x2 + field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0 field public static final String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService"; field public static final String SERVICE_META_DATA = "android.accessibilityservice"; field public static final int SHOW_MODE_AUTO = 0; // 0x0 @@ -9788,7 +9788,7 @@ package android.content { method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String); method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int); method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource); - method @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource); + method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource); method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String); method @NonNull public android.content.AttributionSource.Builder setPid(int); } @@ -10571,7 +10571,7 @@ package android.content { public final class ContextParams { method @Nullable public String getAttributionTag(); method @Nullable public android.content.AttributionSource getNextAttributionSource(); - method @NonNull public boolean shouldRegisterAttributionSource(); + method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public boolean shouldRegisterAttributionSource(); } public static final class ContextParams.Builder { @@ -10580,7 +10580,7 @@ package android.content { method @NonNull public android.content.ContextParams build(); method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String); method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@Nullable android.content.AttributionSource); - method @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean); + method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean); } public class ContextWrapper extends android.content.Context { @@ -25787,15 +25787,15 @@ package android.media.midi { method public abstract void onDisconnect(android.media.midi.MidiReceiver); } - public abstract class MidiUmpDeviceService extends android.app.Service { + @FlaggedApi("com.android.media.midi.flags.virtual_ump") public abstract class MidiUmpDeviceService extends android.app.Service { ctor public MidiUmpDeviceService(); - method @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo(); - method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers(); - method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); - method public void onClose(); - method public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus); - method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers(); - field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService"; + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo(); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers(); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onClose(); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers(); + field @FlaggedApi("com.android.media.midi.flags.virtual_ump") public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService"; } } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 9ecce144f9de..5d1f6dc3e675 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -312,7 +312,7 @@ package android { field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS"; - field @FlaggedApi("backstage_power.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS"; + field @FlaggedApi("android.app.usage.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS"; field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES"; field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"; field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; @@ -3555,7 +3555,7 @@ package android.content { field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED"; field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED"; - field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; + field @FlaggedApi("android.content.pm.archiving") public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP"; field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED"; field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED"; @@ -4028,7 +4028,7 @@ package android.content.pm { field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1 field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff field public static final int MATCH_ANY_USER = 4194304; // 0x400000 - field public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L + field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000 field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000 @@ -16741,6 +16741,23 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @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 NtnSignalStrength implements android.os.Parcelable { + ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public NtnSignalStrength(@Nullable android.telephony.satellite.NtnSignalStrength); + 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 int getLevel(); + 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.NtnSignalStrength> CREATOR; + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GOOD = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GREAT = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_NONE = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_POOR = 1; // 0x1 + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface NtnSignalStrengthCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrength); + } + @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(); @@ -16776,6 +16793,7 @@ package android.telephony.satellite { 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 @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 registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback); 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); @@ -16786,6 +16804,7 @@ package android.telephony.satellite { 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.oem_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,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 @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>); @@ -16794,6 +16813,7 @@ package android.telephony.satellite { 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 unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback); 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); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 0e857a9c5f93..384b9573528e 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3185,7 +3185,6 @@ 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 @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; @@ -3585,8 +3584,8 @@ package android.view { field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000 field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000 field public CharSequence accessibilityTitle; - field public float preferredMaxDisplayRefreshRate; - field public float preferredMinDisplayRefreshRate; + field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMaxDisplayRefreshRate; + field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMinDisplayRefreshRate; field public int privateFlags; } @@ -3616,7 +3615,6 @@ package android.view.accessibility { public final class AccessibilityWindowInfo implements android.os.Parcelable { method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger); - field public static final int UNDEFINED_WINDOW_ID = -1; // 0xffffffff } } @@ -3799,14 +3797,14 @@ package android.view.inputmethod { public final class InputMethodManager { method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession(); method public int getDisplayId(); - method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle); - method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle); + method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle); + method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int); method public boolean hasActiveInputConnection(@Nullable android.view.View); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests(); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown(); - method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle); + method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long); field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 3370c121acfe..1000612ee0e2 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -23,6 +23,7 @@ import android.accessibilityservice.GestureDescription.MotionEventGenerator; import android.annotation.CallbackExecutor; import android.annotation.CheckResult; import android.annotation.ColorInt; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -793,6 +794,7 @@ public abstract class AccessibilityService extends Service { * @hide */ @Retention(RetentionPolicy.SOURCE) + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") @IntDef( prefix = {"OVERLAY_RESULT_"}, value = { @@ -803,6 +805,7 @@ public abstract class AccessibilityService extends Service { public @interface AttachOverlayResult {} /** Result code indicating the overlay was successfully attached. */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_SUCCESS = 0; /** @@ -810,6 +813,7 @@ public abstract class AccessibilityService extends Service { * error and not * because of problems with the input. */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; /** @@ -817,6 +821,7 @@ public abstract class AccessibilityService extends Service { * specified display or * window id was invalid. */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INVALID = 2; private int mConnectionId = AccessibilityInteractionClient.NO_ID; @@ -3506,11 +3511,7 @@ public abstract class AccessibilityService extends Service { * @param displayId the display to which the SurfaceControl should be attached. * @param sc the SurfaceControl containing the overlay content * - * @deprecated Use - * {@link #attachAccessibilityOverlayToDisplay(int, SurfaceControl, Executor, IntConsumer)} - * instead. */ - @Deprecated public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) { Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); AccessibilityInteractionClient.getInstance(this) @@ -3547,6 +3548,7 @@ public abstract class AccessibilityService extends Service { * @see #OVERLAY_RESULT_INVALID * @see #OVERLAY_RESULT_INTERNAL_ERROR */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay( int displayId, @NonNull SurfaceControl sc, @@ -3581,11 +3583,7 @@ public abstract class AccessibilityService extends Service { * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}. * @param sc the SurfaceControl containing the overlay content * - * @deprecated Use - * {@link #attachAccessibilityOverlayToWindow(int, SurfaceControl, Executor,IntConsumer)} - * instead. */ - @Deprecated public void attachAccessibilityOverlayToWindow( int accessibilityWindowId, @NonNull SurfaceControl sc) { Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); @@ -3623,6 +3621,7 @@ public abstract class AccessibilityService extends Service { * @see #OVERLAY_RESULT_INVALID * @see #OVERLAY_RESULT_INTERNAL_ERROR */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow( int accessibilityWindowId, @NonNull SurfaceControl sc, diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java index 0857c9655e8d..1fdc51687433 100644 --- a/core/java/android/app/LocaleConfig.java +++ b/core/java/android/app/LocaleConfig.java @@ -20,7 +20,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -133,15 +132,14 @@ public class LocaleConfig implements Parcelable { return; } } - int resId = 0; Resources res = context.getResources(); + //Get the resource id + int resId = context.getApplicationInfo().getLocaleConfigRes(); + if (resId == 0) { + mStatus = STATUS_NOT_SPECIFIED; + return; + } try { - //Get the resource id - resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes(); - if (resId == 0) { - mStatus = STATUS_NOT_SPECIFIED; - return; - } //Get the parser to read XML data XmlResourceParser parser = res.getXml(resId); parseLocaleConfig(parser, res); diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java index c7602982bee7..113a6dd0ca48 100644 --- a/core/java/android/app/admin/SystemUpdatePolicy.java +++ b/core/java/android/app/admin/SystemUpdatePolicy.java @@ -125,7 +125,7 @@ public final class SystemUpdatePolicy implements Parcelable { * * <p>The system limits each update to one 30-day postponement. The period begins when the * system first postpones the update and setting new {@code TYPE_POSTPONE} policies won’t extend - * the period. If, after 30 days the update isn’t installed (through policy changes), the system + * the period. If, after 30 days the update isn't installed (through policy changes), the system * prompts the user to install the update. * * <p><strong>Note</strong>: Device manufacturers or carriers might choose to exempt important diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig index d1f90676a15f..f401a7607364 100644 --- a/core/java/android/app/usage/flags.aconfig +++ b/core/java/android/app/usage/flags.aconfig @@ -2,7 +2,7 @@ package: "android.app.usage" flag { name: "user_interaction_type_api" - namespace: "power_optimization" + namespace: "backstage_power" description: "Feature flag for user interaction event report/query API" bug: "296061232" } diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 20eae9ade429..62fbcaff79e3 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -744,6 +744,7 @@ public final class AttributionSource implements Parcelable { /** * The next app to receive the permission protected data. */ + @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE) public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) { checkNotUsed(); mBuilderFieldsSet |= 0x20; diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java index 988a9c0ab9b3..b844d358a523 100644 --- a/core/java/android/content/ContextParams.java +++ b/core/java/android/content/ContextParams.java @@ -16,7 +16,10 @@ package android.content; +import static android.permission.flags.Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE; + import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -102,6 +105,7 @@ public final class ContextParams { * registered. */ @NonNull + @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE) public boolean shouldRegisterAttributionSource() { return mShouldRegisterAttributionSource; } @@ -179,6 +183,7 @@ public final class ContextParams { * created should be registered. */ @NonNull + @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE) public Builder setShouldRegisterAttributionSource(boolean shouldRegister) { mShouldRegisterAttributionSource = shouldRegister; return this; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 1eb2cd174a7f..b765562ab587 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3929,6 +3929,8 @@ public class Intent implements Parcelable, Cloneable { * {@link #ACTION_BOOT_COMPLETED} is sent. This is sent as a foreground * broadcast, since it is part of a visible user interaction; be as quick * as possible when handling it. + * + * <p><b>Note:</b> This broadcast is not sent to the system user. */ public static final String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE"; @@ -5323,6 +5325,7 @@ public class Intent implements Parcelable, Cloneable { * @hide */ @SystemApi + @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; // --------------------------------------------------------------------- diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl index 7ab7ed1cc5df..74953fff40d8 100644 --- a/core/java/android/content/pm/ArchivedActivityParcel.aidl +++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl @@ -16,9 +16,12 @@ package android.content.pm; +import android.content.ComponentName; + /** @hide */ parcelable ArchivedActivityParcel { String title; + ComponentName originalComponentName; // PNG compressed bitmaps. byte[] iconBitmap; byte[] monochromeIconBitmap; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index f98ed4b8b37a..1b60f8ed904f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1252,6 +1252,7 @@ public abstract class PackageManager { */ // TODO(b/278553670) Unhide and update @links before launch. @SystemApi + @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32; /** diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index b2cc070228b7..db12728cfb98 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -50,3 +50,11 @@ flag { 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" } + +flag { + name: "sdk_lib_independence" + namespace: "package_manager_service" + description: "Feature flag to keep app working even if its declared sdk-library dependency is unavailable." + bug: "295827951" + is_fixed_read_only: true +} diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig new file mode 100644 index 000000000000..0c2c0f494257 --- /dev/null +++ b/core/java/android/content/res/flags.aconfig @@ -0,0 +1,10 @@ +package: "android.content.res" + +flag { + name: "default_locale" + namespace: "resource_manager" + description: "Feature flag for default locale in LocaleConfig" + bug: "117306409" + # fixed_read_only or device wont boot because of permission issues accessing flags during boot + is_fixed_read_only: true +} diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index f033f9740b3d..bcf447b4267d 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -16,6 +16,7 @@ package android.hardware; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -27,6 +28,8 @@ import android.os.MemoryFile; import android.util.Log; import android.util.SparseArray; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -1809,6 +1812,41 @@ public abstract class SensorManager { protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor, boolean disable); + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({DATA_INJECTION, REPLAY_DATA_INJECTION, HAL_BYPASS_REPLAY_DATA_INJECTION}) + public @interface DataInjectionMode {} + /** + * This mode is only used for testing purposes. Not all HALs support this mode. In this mode, + * the HAL ignores the sensor data provided by physical sensors and accepts the data that is + * injected from the SensorService as if it were the real sensor data. This mode is primarily + * used for testing various algorithms like vendor provided SensorFusion, Step Counter and + * Step Detector etc. Typically, in this mode, there is a client app which injects + * sensor data into the HAL. Normal apps can register and unregister for any sensor + * that supports injection. Registering to sensors that do not support injection will + * give an error. + * This is the default data injection mode. + * @hide + */ + public static final int DATA_INJECTION = 1; + /** + * Mostly equivalent to DATA_INJECTION with the difference being that the injected data is + * delivered to all requesting apps rather than just the package allowed to inject data. + * This mode is only allowed to be used on development builds. + * @hide + */ + public static final int REPLAY_DATA_INJECTION = 3; + /** + * Like REPLAY_DATA_INJECTION but injected data is not sent into the HAL. It is stored in a + * buffer in the platform and played back to all requesting apps. + * This is useful for playing back sensor data to test platform components without + * relying on the HAL to support data injection. + * @hide + */ + public static final int HAL_BYPASS_REPLAY_DATA_INJECTION = 4; + /** * For testing purposes only. Not for third party applications. @@ -1833,13 +1871,47 @@ public abstract class SensorManager { */ @SystemApi public boolean initDataInjection(boolean enable) { - return initDataInjectionImpl(enable); + return initDataInjectionImpl(enable, DATA_INJECTION); + } + + /** + * For testing purposes only. Not for third party applications. + * + * Initialize data injection mode and create a client for data injection. SensorService should + * already be operating in one of DATA_INJECTION, REPLAY_DATA_INJECTION or + * HAL_BYPASS_REPLAY_DATA_INJECTION modes for this call succeed. To set SensorService in + * a Data Injection mode, use one of: + * + * <ul> + * <li>adb shell dumpsys sensorservice data_injection</li> + * <li>adb shell dumpsys sensorservice replay_data_injection package_name</li> + * <li>adb shell dumpsys sensorservice hal_bypass_replay_data_injection package_name</li> + * </ul> + * + * Typically this is done using a host side test. This mode is expected to be used + * only for testing purposes. See {@link DataInjectionMode} for details of each data injection + * mode. Once this method succeeds, the test can call + * {@link #injectSensorData(Sensor, float[], int, long)} to inject sensor data into the HAL. + * To put SensorService back into normal mode, use "adb shell dumpsys sensorservice enable" + * + * @param enable True to initialize a client in a data injection mode. + * False to clean up the native resources. + * + * @param mode One of DATA_INJECTION, REPLAY_DATA_INJECTION or HAL_BYPASS_DATA_INJECTION. + * See {@link DataInjectionMode} for details. + * + * @return true if the HAL supports data injection and false + * otherwise. + * @hide + */ + public boolean initDataInjection(boolean enable, @DataInjectionMode int mode) { + return initDataInjectionImpl(enable, mode); } /** * @hide */ - protected abstract boolean initDataInjectionImpl(boolean enable); + protected abstract boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode); /** * For testing purposes only. Not for third party applications. @@ -1871,9 +1943,6 @@ public abstract class SensorManager { if (sensor == null) { throw new IllegalArgumentException("sensor cannot be null"); } - if (!sensor.isDataInjectionSupported()) { - throw new IllegalArgumentException("sensor does not support data injection"); - } if (values == null) { throw new IllegalArgumentException("sensor data cannot be null"); } diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index dfd380233662..40e03dbb8b34 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -90,6 +90,8 @@ public class SystemSensorManager extends SensorManager { private static native void nativeGetRuntimeSensors( long nativeInstance, int deviceId, List<Sensor> list); private static native boolean nativeIsDataInjectionEnabled(long nativeInstance); + private static native boolean nativeIsReplayDataInjectionEnabled(long nativeInstance); + private static native boolean nativeIsHalBypassReplayDataInjectionEnabled(long nativeInstance); private static native int nativeCreateDirectChannel( long nativeInstance, int deviceId, long size, int channelType, int fd, @@ -384,20 +386,41 @@ public class SystemSensorManager extends SensorManager { } } - protected boolean initDataInjectionImpl(boolean enable) { + protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) { synchronized (sLock) { + boolean isDataInjectionModeEnabled = false; if (enable) { - boolean isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance); + switch (mode) { + case DATA_INJECTION: + isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance); + break; + case REPLAY_DATA_INJECTION: + isDataInjectionModeEnabled = nativeIsReplayDataInjectionEnabled( + mNativeInstance); + break; + case HAL_BYPASS_REPLAY_DATA_INJECTION: + isDataInjectionModeEnabled = nativeIsHalBypassReplayDataInjectionEnabled( + mNativeInstance); + break; + default: + break; + } // The HAL does not support injection OR SensorService hasn't been set in DI mode. if (!isDataInjectionModeEnabled) { - Log.e(TAG, "Data Injection mode not enabled"); + Log.e(TAG, "The correct Data Injection mode has not been enabled"); return false; } + if (sInjectEventQueue != null && sInjectEventQueue.getDataInjectionMode() != mode) { + // The inject event queue has been initialized for a different type of DI + // close it and create a new one + sInjectEventQueue.dispose(); + sInjectEventQueue = null; + } // Initialize a client for data_injection. if (sInjectEventQueue == null) { try { sInjectEventQueue = new InjectEventQueue( - mMainLooper, this, mContext.getPackageName()); + mMainLooper, this, mode, mContext.getPackageName()); } catch (RuntimeException e) { Log.e(TAG, "Cannot create InjectEventQueue: " + e); } @@ -421,6 +444,12 @@ public class SystemSensorManager extends SensorManager { Log.e(TAG, "Data injection mode not activated before calling injectSensorData"); return false; } + if (sInjectEventQueue.getDataInjectionMode() != HAL_BYPASS_REPLAY_DATA_INJECTION + && !sensor.isDataInjectionSupported()) { + // DI mode and Replay DI mode require support from the sensor HAL + // HAL Bypass mode doesn't require this. + throw new IllegalArgumentException("sensor does not support data injection"); + } int ret = sInjectEventQueue.injectSensorData(sensor.getHandle(), values, accuracy, timestamp); // If there are any errors in data injection clean up the native resources. @@ -825,6 +854,8 @@ public class SystemSensorManager extends SensorManager { protected static final int OPERATING_MODE_NORMAL = 0; protected static final int OPERATING_MODE_DATA_INJECTION = 1; + protected static final int OPERATING_MODE_REPLAY_DATA_INJECTION = 3; + protected static final int OPERATING_MODE_HAL_BYPASS_REPLAY_DATA_INJECTION = 4; BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) { if (packageName == null) packageName = ""; @@ -1134,8 +1165,12 @@ public class SystemSensorManager extends SensorManager { } final class InjectEventQueue extends BaseEventQueue { - public InjectEventQueue(Looper looper, SystemSensorManager manager, String packageName) { - super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName); + + private int mMode; + public InjectEventQueue(Looper looper, SystemSensorManager manager, + @DataInjectionMode int mode, String packageName) { + super(looper, manager, mode, packageName); + mMode = mode; } int injectSensorData(int handle, float[] values, int accuracy, long timestamp) { @@ -1161,6 +1196,10 @@ public class SystemSensorManager extends SensorManager { protected void removeSensorEvent(Sensor sensor) { } + + int getDataInjectionMode() { + return mMode; + } } protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) { diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 02212968cdb0..02304b5ba4f3 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -1074,6 +1074,14 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan */ public interface FaceDetectionCallback { void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric); + + /** + * An error has occurred with face detection. + * + * This callback signifies that this operation has been completed, and + * no more callbacks should be expected. + */ + default void onDetectionError(int errorMsgId) {} } /** @@ -1373,6 +1381,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } else if (mRemovalCallback != null) { mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId, getErrorString(mContext, errMsgId, vendorCode)); + } else if (mFaceDetectionCallback != null) { + mFaceDetectionCallback.onDetectionError(errMsgId); + mFaceDetectionCallback = null; } } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 5bfda70f03de..935157a48a45 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -462,6 +462,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * Invoked when a fingerprint has been detected. */ void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric); + + /** + * An error has occurred with fingerprint detection. + * + * This callback signifies that this operation has been completed, and + * no more callbacks should be expected. + */ + default void onDetectionError(int errorMsgId) {} } /** @@ -1484,6 +1492,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing ? mRemoveTracker.mSingleFingerprint : null; mRemovalCallback.onRemovalError(fp, clientErrMsgId, getErrorString(mContext, errMsgId, vendorCode)); + } else if (mFingerprintDetectionCallback != null) { + mFingerprintDetectionCallback.onDetectionError(errMsgId); + mFingerprintDetectionCallback = null; } } diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java index aa55e543dd48..05024ea95eda 100644 --- a/core/java/android/hardware/input/InputDeviceSensorManager.java +++ b/core/java/android/hardware/input/InputDeviceSensorManager.java @@ -644,7 +644,7 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene } @Override - protected boolean initDataInjectionImpl(boolean enable) { + protected boolean initDataInjectionImpl(boolean enable, int mode) { return false; } diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 98f9dffaae13..5078dc351f6f 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -140,6 +140,31 @@ public final class VibrationAttributes implements Parcelable { */ public static final int USAGE_MEDIA = 0x10 | USAGE_CLASS_MEDIA; + /** @hide */ + @IntDef(prefix = { "CATEGORY_" }, value = { + CATEGORY_UNKNOWN, + CATEGORY_KEYBOARD, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Category {} + + /** + * Category value when the vibration category is unknown. + * + * @hide + */ + public static final int CATEGORY_UNKNOWN = 0x0; + + /** + * Category value for keyboard vibrations. + * + * <p>Most typical keyboard vibrations are haptic feedback for virtual keyboard key + * press/release, for example. + * + * @hide + */ + public static final int CATEGORY_KEYBOARD = 1; + /** * @hide */ @@ -147,7 +172,8 @@ public final class VibrationAttributes implements Parcelable { FLAG_BYPASS_INTERRUPTION_POLICY, FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF, FLAG_INVALIDATE_SETTINGS_CACHE, - FLAG_PIPELINED_EFFECT + FLAG_PIPELINED_EFFECT, + FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE }) @Retention(RetentionPolicy.SOURCE) public @interface Flag{} @@ -167,6 +193,8 @@ public final class VibrationAttributes implements Parcelable { * {@link android.view.HapticFeedbackConstants#FLAG_IGNORE_GLOBAL_SETTING} and * {@link AudioAttributes#FLAG_BYPASS_MUTE}. * + * <p>Only privileged apps can ignore user settings, and this flag will be ignored otherwise. + * * @hide */ public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF = 1 << 1; @@ -199,12 +227,31 @@ public final class VibrationAttributes implements Parcelable { public static final int FLAG_PIPELINED_EFFECT = 1 << 3; /** + * Flag requesting that this vibration effect to be played without applying the user + * intensity setting to scale the vibration. + * + * <p>The user setting is still applied to enable/disable the vibration, but the vibration + * effect strength will not be scaled based on the enabled setting value. + * + * <p>This is intended to be used on scenarios where the system needs to enforce a specific + * strength for the vibration effect, regardless of the user preference. Only privileged apps + * can ignore user settings, and this flag will be ignored otherwise. + * + * <p>If you need to bypass the user setting when it's disabling vibrations then this also + * needs the flag {@link #FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF} to be set. + * + * @hide + */ + public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE = 1 << 4; + + /** * All flags supported by vibrator service, update it when adding new flag. * @hide */ public static final int FLAG_ALL_SUPPORTED = FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF - | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT; + | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT + | FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE; /** Creates a new {@link VibrationAttributes} instance with given usage. */ public static @NonNull VibrationAttributes createForUsage(@Usage int usage) { @@ -214,12 +261,14 @@ public final class VibrationAttributes implements Parcelable { private final int mUsage; private final int mFlags; private final int mOriginalAudioUsage; + private final int mCategory; private VibrationAttributes(@Usage int usage, @AudioAttributes.AttributeUsage int audioUsage, - @Flag int flags) { + @Flag int flags, @Category int category) { mUsage = usage; mOriginalAudioUsage = audioUsage; mFlags = flags & FLAG_ALL_SUPPORTED; + mCategory = category; } /** @@ -248,6 +297,20 @@ public final class VibrationAttributes implements Parcelable { } /** + * Return the vibration category. + * + * <p>Vibration categories describe the source of the vibration, and it can be combined with + * the vibration usage to best match to a user setting, e.g. a vibration with usage touch and + * category keyboard can be used to control keyboard haptic feedback independently. + * + * @hide + */ + @Category + public int getCategory() { + return mCategory; + } + + /** * Check whether a flag is set * @return true if a flag is set and false otherwise */ @@ -298,12 +361,14 @@ public final class VibrationAttributes implements Parcelable { dest.writeInt(mUsage); dest.writeInt(mOriginalAudioUsage); dest.writeInt(mFlags); + dest.writeInt(mCategory); } private VibrationAttributes(Parcel src) { mUsage = src.readInt(); mOriginalAudioUsage = src.readInt(); mFlags = src.readInt(); + mCategory = src.readInt(); } public static final @NonNull Parcelable.Creator<VibrationAttributes> @@ -326,12 +391,12 @@ public final class VibrationAttributes implements Parcelable { } VibrationAttributes rhs = (VibrationAttributes) o; return mUsage == rhs.mUsage && mOriginalAudioUsage == rhs.mOriginalAudioUsage - && mFlags == rhs.mFlags; + && mFlags == rhs.mFlags && mCategory == rhs.mCategory; } @Override public int hashCode() { - return Objects.hash(mUsage, mOriginalAudioUsage, mFlags); + return Objects.hash(mUsage, mOriginalAudioUsage, mFlags, mCategory); } @Override @@ -340,6 +405,7 @@ public final class VibrationAttributes implements Parcelable { + "mUsage=" + usageToString() + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage) + ", mFlags=" + mFlags + + ", mCategory=" + categoryToString() + '}'; } @@ -376,6 +442,23 @@ public final class VibrationAttributes implements Parcelable { } } + /** @hide */ + public String categoryToString() { + return categoryToString(mCategory); + } + + /** @hide */ + public static String categoryToString(@Category int category) { + switch (category) { + case CATEGORY_UNKNOWN: + return "UNKNOWN"; + case CATEGORY_KEYBOARD: + return "KEYBOARD"; + default: + return "unknown category " + category; + } + } + /** * Builder class for {@link VibrationAttributes} objects. * By default, all information is set to UNKNOWN. @@ -384,6 +467,7 @@ public final class VibrationAttributes implements Parcelable { private int mUsage = USAGE_UNKNOWN; private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN; private int mFlags = 0x0; + private int mCategory = CATEGORY_UNKNOWN; /** * Constructs a new Builder with the defaults. @@ -399,6 +483,7 @@ public final class VibrationAttributes implements Parcelable { mUsage = vib.mUsage; mOriginalAudioUsage = vib.mOriginalAudioUsage; mFlags = vib.mFlags; + mCategory = vib.mCategory; } } @@ -464,7 +549,8 @@ public final class VibrationAttributes implements Parcelable { * @return a new {@link VibrationAttributes} object */ public @NonNull VibrationAttributes build() { - VibrationAttributes ans = new VibrationAttributes(mUsage, mOriginalAudioUsage, mFlags); + VibrationAttributes ans = new VibrationAttributes( + mUsage, mOriginalAudioUsage, mFlags, mCategory); return ans; } @@ -480,6 +566,19 @@ public final class VibrationAttributes implements Parcelable { } /** + * Sets the attribute describing the category of the corresponding vibration. + * + * @param category The category for the vibration + * @return the same Builder instance. + * + * @hide + */ + public @NonNull Builder setCategory(@Category int category) { + mCategory = category; + return this; + } + + /** * Sets only the flags specified in the bitmask, leaving the other supported flag values * unchanged in the builder. * diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index 99c9925d9cb7..2fc24142acf2 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -184,6 +184,16 @@ public abstract class Vibrator { } /** + * Whether the keyboard vibration is enabled by default. + * + * @return {@code true} if the keyboard vibration is default enabled, {@code false} otherwise. + * @hide + */ + public boolean isDefaultKeyboardVibrationEnabled() { + return getConfig().isDefaultKeyboardVibrationEnabled(); + } + + /** * Return the ID of this vibrator. * * @return A non-negative integer representing the id of the vibrator controlled by this diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 37559b32e841..b7f2e065bac8 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -16,7 +16,7 @@ flag { flag { name: "remove_app_profiler_pss_collection" - namespace: "android_platform_power_optimization" + namespace: "backstage_power" description: "Replaces background PSS collection in AppProfiler with RSS" bug: "297542292" } diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java index bde334a6edc5..92e49676cbae 100644 --- a/core/java/android/os/vibrator/VibrationConfig.java +++ b/core/java/android/os/vibrator/VibrationConfig.java @@ -65,6 +65,8 @@ public class VibrationConfig { @VibrationIntensity private final int mDefaultRingVibrationIntensity; + private final boolean mDefaultKeyboardVibrationEnabled; + /** @hide */ public VibrationConfig(@Nullable Resources resources) { mHapticChannelMaxVibrationAmplitude = loadFloat(resources, @@ -76,6 +78,8 @@ public class VibrationConfig { mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources, com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false); + mDefaultKeyboardVibrationEnabled = loadBoolean(resources, + com.android.internal.R.bool.config_defaultKeyboardVibrationEnabled, true); mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources, com.android.internal.R.integer.config_defaultAlarmVibrationIntensity); @@ -157,6 +161,14 @@ public class VibrationConfig { return mIgnoreVibrationsOnWirelessCharger; } + /** + * Whether keyboard vibration settings is enabled by default. + * @hide + */ + public boolean isDefaultKeyboardVibrationEnabled() { + return mDefaultKeyboardVibrationEnabled; + } + /** Get the default vibration intensity for given usage. */ @VibrationIntensity public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) { diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 66ad12c7559a..69d86a6604ad 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -37,3 +37,10 @@ flag { is_fixed_read_only: true bug: "291128479" } + +flag { + namespace: "haptics" + name: "keyboard_category_enabled" + description: "Enables the independent keyboard vibration settings feature" + bug: "289107579" +} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index d8534dd5fbc1..3f06a91f6e5b 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -21,3 +21,17 @@ flag { description: "enable role controller in system server" bug: "302562590" } + +flag { + name: "set_next_attribution_source" + namespace: "permissions" + description: "enable AttributionSource.setNextAttributionSource" + bug: "304478648" +} + +flag { + name: "should_register_attribution_source" + namespace: "permissions" + description: "enable the shouldRegisterAttributionSource API" + bug: "305057691" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b34e09f2c97a..f0906b1e3c06 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5129,6 +5129,14 @@ public final class Settings { "hardware_haptic_feedback_intensity"; /** + * Whether keyboard vibration feedback is enabled. The value is boolean (1 or 0). + * + * @hide + */ + @Readable + public static final String KEYBOARD_VIBRATION_ENABLED = "keyboard_vibration_enabled"; + + /** * Ringer volume. This is used internally, changing this value will not * change the volume. See AudioManager. * diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java index 7af7fe6108e6..db97d4f52643 100644 --- a/core/java/android/service/voice/AbstractDetector.java +++ b/core/java/android/service/voice/AbstractDetector.java @@ -199,8 +199,12 @@ abstract class AbstractDetector implements HotwordDetector { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + Consumer<AbstractDetector> onDestroyListener; synchronized (mLock) { - mOnDestroyListener.accept(this); + onDestroyListener = mOnDestroyListener; + } + if (onDestroyListener != null) { + onDestroyListener.accept(this); } } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 3f41c56ac7f1..d2806217a276 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -520,7 +520,7 @@ public class VoiceInteractionService extends Service { @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { - // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning + // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -546,6 +546,10 @@ public class VoiceInteractionService extends Service { @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, + // SoundTrigger.ModuleProperties, AlwaysOnHotwordDetector.Callback)} and replace with the + // permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -612,6 +616,11 @@ public class VoiceInteractionService extends Service { @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, + // AlwaysOnHotwordDetector.Callback)} and replace with the permission + // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. + return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ true, options, sharedMemory, /* modulProperties */ null, /* executor= */ null, callback); @@ -663,7 +672,11 @@ public class VoiceInteractionService extends Service { @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { - // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning + // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, + // Executor, AlwaysOnHotwordDetector.Callback)} and replace with the permission + // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -690,6 +703,10 @@ public class VoiceInteractionService extends Service { @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, PersistableBundle, + // SharedMemory, SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} + // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 3ed13bbeeaad..35834fd3886f 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -767,7 +767,7 @@ public class SpeechRecognizer { try { mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource()); if (DBG) Log.d(TAG, "service start listening command succeeded"); - } catch (final RemoteException e) { + } catch (final Exception e) { Log.e(TAG, "startListening() failed", e); mListener.onError(ERROR_CLIENT); } @@ -781,7 +781,7 @@ public class SpeechRecognizer { try { mService.stopListening(mListener); if (DBG) Log.d(TAG, "service stop listening command succeeded"); - } catch (final RemoteException e) { + } catch (final Exception e) { Log.e(TAG, "stopListening() failed", e); mListener.onError(ERROR_CLIENT); } @@ -795,7 +795,7 @@ public class SpeechRecognizer { try { mService.cancel(mListener, /*isShutdown*/ false); if (DBG) Log.d(TAG, "service cancel command succeeded"); - } catch (final RemoteException e) { + } catch (final Exception e) { Log.e(TAG, "cancel() failed", e); mListener.onError(ERROR_CLIENT); } @@ -830,7 +830,7 @@ public class SpeechRecognizer { mContext.getAttributionSource(), new InternalSupportCallback(callbackExecutor, recognitionSupportCallback)); if (DBG) Log.d(TAG, "service support command succeeded"); - } catch (final RemoteException e) { + } catch (final Exception e) { Log.e(TAG, "checkRecognitionSupport() failed", e); callbackExecutor.execute(() -> recognitionSupportCallback.onError(ERROR_CLIENT)); } @@ -850,7 +850,7 @@ public class SpeechRecognizer { mService.triggerModelDownload( recognizerIntent, mContext.getAttributionSource(), null); if (DBG) Log.d(TAG, "triggerModelDownload() without a listener"); - } catch (final RemoteException e) { + } catch (final Exception e) { Log.e(TAG, "triggerModelDownload() without a listener failed", e); mListener.onError(ERROR_CLIENT); } @@ -862,7 +862,7 @@ public class SpeechRecognizer { recognizerIntent, mContext.getAttributionSource(), new InternalModelDownloadListener(callbackExecutor, modelDownloadListener)); if (DBG) Log.d(TAG, "triggerModelDownload() with a listener"); - } catch (final RemoteException e) { + } catch (final Exception e) { Log.e(TAG, "triggerModelDownload() with a listener failed", e); callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT)); } @@ -889,7 +889,7 @@ public class SpeechRecognizer { if (mService != null) { try { mService.cancel(mListener, /*isShutdown*/ true); - } catch (final RemoteException e) { + } catch (final Exception e) { // Not important } } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 2906d86f803d..766e924c994e 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -200,6 +200,12 @@ public class FeatureFlagUtils { public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION = "settings_remote_device_credential_validation"; + /** + * Flag to enable/disable to start treating any calls to "suspend" an app as "quarantine". + * @hide + */ + public static final String SETTINGS_TREAT_PAUSE_AS_QUARANTINE = + "settings_treat_pause_as_quarantine"; private static final Map<String, String> DEFAULT_FLAGS; @@ -247,6 +253,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false"); // TODO: b/298454866 Replace with Trunk Stable Feature Flag DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false"); + DEFAULT_FLAGS.put(SETTINGS_TREAT_PAUSE_AS_QUARANTINE, "false"); } private static final Set<String> PERSISTENT_FLAGS; @@ -264,6 +271,7 @@ public class FeatureFlagUtils { PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA); PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA_PHASE2); PERSISTENT_FLAGS.add(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM); + PERSISTENT_FLAGS.add(SETTINGS_TREAT_PAUSE_AS_QUARANTINE); } /** diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java index a89f79540c65..dc41b70d1683 100644 --- a/core/java/android/view/ContentRecordingSession.java +++ b/core/java/android/view/ContentRecordingSession.java @@ -52,6 +52,12 @@ public final class ContentRecordingSession implements Parcelable { */ public static final int RECORD_CONTENT_TASK = 1; + /** Full screen sharing (app is not selected). */ + public static final int TARGET_UID_FULL_SCREEN = -1; + + /** Can't report (e.g. side loaded app). */ + public static final int TARGET_UID_UNKNOWN = -2; + /** * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has * recorded content rendered to its surface. @@ -89,27 +95,36 @@ public final class ContentRecordingSession implements Parcelable { */ private boolean mWaitingForConsent = false; + /** UID of the package that is captured if selected. */ + private int mTargetUid = TARGET_UID_UNKNOWN; + /** * Default instance, with recording the display. */ private ContentRecordingSession() { } - /** - * Returns an instance initialized for recording the indicated display. - */ + /** Returns an instance initialized for recording the indicated display. */ public static ContentRecordingSession createDisplaySession(int displayToMirror) { - return new ContentRecordingSession().setDisplayToRecord(displayToMirror) - .setContentToRecord(RECORD_CONTENT_DISPLAY); + return new ContentRecordingSession() + .setDisplayToRecord(displayToMirror) + .setContentToRecord(RECORD_CONTENT_DISPLAY) + .setTargetUid(TARGET_UID_FULL_SCREEN); } - /** - * Returns an instance initialized for task recording. - */ + /** Returns an instance initialized for task recording. */ public static ContentRecordingSession createTaskSession( @NonNull IBinder taskWindowContainerToken) { - return new ContentRecordingSession().setContentToRecord(RECORD_CONTENT_TASK) - .setTokenToRecord(taskWindowContainerToken); + return createTaskSession(taskWindowContainerToken, TARGET_UID_UNKNOWN); + } + + /** Returns an instance initialized for task recording. */ + public static ContentRecordingSession createTaskSession( + @NonNull IBinder taskWindowContainerToken, int targetUid) { + return new ContentRecordingSession() + .setContentToRecord(RECORD_CONTENT_TASK) + .setTokenToRecord(taskWindowContainerToken) + .setTargetUid(targetUid); } /** @@ -175,13 +190,33 @@ public final class ContentRecordingSession implements Parcelable { } } + @IntDef(prefix = "TARGET_UID_", value = { + TARGET_UID_FULL_SCREEN, + TARGET_UID_UNKNOWN + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface TargetUid {} + + @DataClass.Generated.Member + public static String targetUidToString(@TargetUid int value) { + switch (value) { + case TARGET_UID_FULL_SCREEN: + return "TARGET_UID_FULL_SCREEN"; + case TARGET_UID_UNKNOWN: + return "TARGET_UID_UNKNOWN"; + default: return Integer.toHexString(value); + } + } + @DataClass.Generated.Member /* package-private */ ContentRecordingSession( int virtualDisplayId, @RecordContent int contentToRecord, int displayToRecord, @Nullable IBinder tokenToRecord, - boolean waitingForConsent) { + boolean waitingForConsent, + int targetUid) { this.mVirtualDisplayId = virtualDisplayId; this.mContentToRecord = contentToRecord; @@ -196,6 +231,7 @@ public final class ContentRecordingSession implements Parcelable { this.mDisplayToRecord = displayToRecord; this.mTokenToRecord = tokenToRecord; this.mWaitingForConsent = waitingForConsent; + this.mTargetUid = targetUid; // onConstructed(); // You can define this method to get a callback } @@ -251,6 +287,14 @@ public final class ContentRecordingSession implements Parcelable { } /** + * UID of the package that is captured if selected. + */ + @DataClass.Generated.Member + public int getTargetUid() { + return mTargetUid; + } + + /** * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has * recorded content rendered to its surface. */ @@ -314,6 +358,15 @@ public final class ContentRecordingSession implements Parcelable { return this; } + /** + * UID of the package that is captured if selected. + */ + @DataClass.Generated.Member + public @NonNull ContentRecordingSession setTargetUid( int value) { + mTargetUid = value; + return this; + } + @Override @DataClass.Generated.Member public String toString() { @@ -325,7 +378,8 @@ public final class ContentRecordingSession implements Parcelable { "contentToRecord = " + recordContentToString(mContentToRecord) + ", " + "displayToRecord = " + mDisplayToRecord + ", " + "tokenToRecord = " + mTokenToRecord + ", " + - "waitingForConsent = " + mWaitingForConsent + + "waitingForConsent = " + mWaitingForConsent + ", " + + "targetUid = " + mTargetUid + " }"; } @@ -346,7 +400,8 @@ public final class ContentRecordingSession implements Parcelable { && mContentToRecord == that.mContentToRecord && mDisplayToRecord == that.mDisplayToRecord && java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord) - && mWaitingForConsent == that.mWaitingForConsent; + && mWaitingForConsent == that.mWaitingForConsent + && mTargetUid == that.mTargetUid; } @Override @@ -361,6 +416,7 @@ public final class ContentRecordingSession implements Parcelable { _hash = 31 * _hash + mDisplayToRecord; _hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord); _hash = 31 * _hash + Boolean.hashCode(mWaitingForConsent); + _hash = 31 * _hash + mTargetUid; return _hash; } @@ -378,6 +434,7 @@ public final class ContentRecordingSession implements Parcelable { dest.writeInt(mContentToRecord); dest.writeInt(mDisplayToRecord); if (mTokenToRecord != null) dest.writeStrongBinder(mTokenToRecord); + dest.writeInt(mTargetUid); } @Override @@ -397,6 +454,7 @@ public final class ContentRecordingSession implements Parcelable { int contentToRecord = in.readInt(); int displayToRecord = in.readInt(); IBinder tokenToRecord = (flg & 0x8) == 0 ? null : (IBinder) in.readStrongBinder(); + int targetUid = in.readInt(); this.mVirtualDisplayId = virtualDisplayId; this.mContentToRecord = contentToRecord; @@ -412,6 +470,7 @@ public final class ContentRecordingSession implements Parcelable { this.mDisplayToRecord = displayToRecord; this.mTokenToRecord = tokenToRecord; this.mWaitingForConsent = waitingForConsent; + this.mTargetUid = targetUid; // onConstructed(); // You can define this method to get a callback } @@ -442,6 +501,7 @@ public final class ContentRecordingSession implements Parcelable { private int mDisplayToRecord; private @Nullable IBinder mTokenToRecord; private boolean mWaitingForConsent; + private int mTargetUid; private long mBuilderFieldsSet = 0L; @@ -513,10 +573,21 @@ public final class ContentRecordingSession implements Parcelable { return this; } + /** + * UID of the package that is captured if selected. + */ + @DataClass.Generated.Member + public @NonNull Builder setTargetUid(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mTargetUid = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull ContentRecordingSession build() { checkNotUsed(); - mBuilderFieldsSet |= 0x20; // Mark builder used + mBuilderFieldsSet |= 0x40; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mVirtualDisplayId = INVALID_DISPLAY; @@ -533,17 +604,21 @@ public final class ContentRecordingSession implements Parcelable { if ((mBuilderFieldsSet & 0x10) == 0) { mWaitingForConsent = false; } + if ((mBuilderFieldsSet & 0x20) == 0) { + mTargetUid = TARGET_UID_UNKNOWN; + } ContentRecordingSession o = new ContentRecordingSession( mVirtualDisplayId, mContentToRecord, mDisplayToRecord, mTokenToRecord, - mWaitingForConsent); + mWaitingForConsent, + mTargetUid); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x20) != 0) { + if ((mBuilderFieldsSet & 0x40) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -551,10 +626,10 @@ public final class ContentRecordingSession implements Parcelable { } @DataClass.Generated( - time = 1683628463074L, + time = 1697456140720L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java", - inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)") + inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\npublic static final int TARGET_UID_FULL_SCREEN\npublic static final int TARGET_UID_UNKNOWN\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\nprivate int mTargetUid\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java index 67ac811287cb..0f35048cac67 100644 --- a/core/java/android/view/SurfaceControlRegistry.java +++ b/core/java/android/view/SurfaceControlRegistry.java @@ -78,6 +78,11 @@ public class SurfaceControlRegistry { for (int i = 0; i < size; i++) { final Map.Entry<SurfaceControl, Long> entry = entries.get(i); final SurfaceControl sc = entry.getKey(); + if (sc == null) { + // Just skip if the key has since been removed from the weak hash map + continue; + } + final long timeRegistered = entry.getValue(); pw.print(" "); pw.print(sc.getName()); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index e9d0e4c557c6..49b16c7697c9 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -19,6 +19,8 @@ package android.view; import static android.content.res.Resources.ID_NULL; import static android.os.Trace.TRACE_TAG_APP; import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP; +import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; +import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; @@ -27,6 +29,7 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH; import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE; import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; +import static android.view.flags.Flags.toolkitSetFrameRate; import static android.view.flags.Flags.viewVelocityApi; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; @@ -114,6 +117,7 @@ import android.sysprop.DisplayProperties; import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.FloatProperty; import android.util.LayoutDirection; import android.util.Log; @@ -5509,6 +5513,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private ViewTranslationResponse mViewTranslationResponse; /** + * A threshold value to determine the frame rate category of the View based on the size. + */ + private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f; + + /** * Simple constructor to use when creating a view from code. * * @param context The Context the view is running in, through which it can @@ -20183,6 +20192,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } + // For VRR to vote the preferred frame rate + votePreferredFrameRate(); + // Reset content capture caches mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK; mContentCaptureSessionCached = false; @@ -20285,6 +20297,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected void damageInParent() { if (mParent != null && mAttachInfo != null) { + // For VRR to vote the preferred frame rate + votePreferredFrameRate(); mParent.onDescendantInvalidated(this, this); } } @@ -32981,6 +32995,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; } + private float getSizePercentage() { + if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) { + return 0; + } + + DisplayMetrics displayMetrics = mResources.getDisplayMetrics(); + int screenSize = displayMetrics.widthPixels + * displayMetrics.heightPixels; + int viewSize = getWidth() * getHeight(); + + if (screenSize == 0 || viewSize == 0) { + return 0f; + } + return (float) viewSize / screenSize; + } + + private int calculateFrameRateCategory() { + float sizePercentage = getSizePercentage(); + + if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) { + return FRAME_RATE_CATEGORY_LOW; + } else { + return FRAME_RATE_CATEGORY_NORMAL; + } + } + + private void votePreferredFrameRate() { + // use toolkitSetFrameRate flag to gate the change + ViewRootImpl viewRootImpl = getViewRootImpl(); + if (toolkitSetFrameRate() && viewRootImpl != null && getSizePercentage() > 0) { + viewRootImpl.votePreferredFrameRateCategory(calculateFrameRateCategory()); + } + } + /** * Set the current velocity of the View, we only track positive value. * We will use the velocity information to adjust the frame rate when applicable. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 939278314df4..9d15c78a6e9e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -24,6 +24,8 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; @@ -74,7 +76,10 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_E import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; @@ -87,6 +92,7 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.accessibility.Flags.forceInvertColor; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; +import static android.view.flags.Flags.toolkitSetFrameRate; import android.Manifest; import android.accessibilityservice.AccessibilityService; @@ -732,6 +738,7 @@ public final class ViewRootImpl implements ViewParent, private SurfaceControl mBoundsLayer; private final SurfaceSession mSurfaceSession = new SurfaceSession(); private final Transaction mTransaction = new Transaction(); + private final Transaction mFrameRateTransaction = new Transaction(); @UnsupportedAppUsage boolean mAdded; @@ -955,6 +962,34 @@ public final class ViewRootImpl implements ViewParent, private AccessibilityWindowAttributes mAccessibilityWindowAttributes; + /* + * for Variable Refresh Rate project + */ + + // The preferred frame rate category of the view that + // could be updated on a frame-by-frame basis. + private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + // The preferred frame rate category of the last frame that + // could be used to lower frame rate after touch boost + private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + // The preferred frame rate of the view that is mainly used for + // touch boosting, view velocity handling, and TextureView. + private float mPreferredFrameRate = 0; + // Used to check if there were any view invalidations in + // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME). + private boolean mHasInvalidation = false; + // Used to check if it is in the touch boosting period. + private boolean mIsFrameRateBoosting = false; + // Used to check if there is a message in the message queue + // for idleness handling. + private boolean mHasIdledMessage = false; + // time for touch boost period. + private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500; + // time for checking idle status periodically. + private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500; + // time for revaluating the idle status before lowering the frame rate. + private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500; + /** * A temporary object used so relayoutWindow can return the latest SyncSeqId * system. The SyncSeqId system was designed to work without synchronous relayout @@ -3918,6 +3953,12 @@ public final class ViewRootImpl implements ViewParent, mWmsRequestSyncGroupState = WMS_SYNC_NONE; } } + + // For the variable refresh rate project. + setPreferredFrameRate(mPreferredFrameRate); + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + mLastPreferredFrameRateCategory = mPreferredFrameRateCategory; + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; } private void createSyncIfNeeded() { @@ -5970,6 +6011,8 @@ public final class ViewRootImpl implements ViewParent, 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; + private static final int MSG_TOUCH_BOOST_TIMEOUT = 39; + private static final int MSG_CHECK_INVALIDATION_IDLE = 40; final class ViewRootHandler extends Handler { @Override @@ -6265,6 +6308,32 @@ public final class ViewRootImpl implements ViewParent, mNumPausedForSync = 0; scheduleTraversals(); break; + case MSG_TOUCH_BOOST_TIMEOUT: + /** + * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). + */ + mIsFrameRateBoosting = false; + setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory, + mLastPreferredFrameRateCategory)); + break; + case MSG_CHECK_INVALIDATION_IDLE: + if (!mHasInvalidation && !mIsFrameRateBoosting) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + mHasIdledMessage = false; + } else { + /** + * If there is no invalidation within a certain period, + * we consider the display is idled. + * We then set the frame rate catetogry to NO_PREFERENCE. + * Note that SurfaceFlinger also has a mechanism to lower the refresh rate + * if there is no updates of the buffer. + */ + mHasInvalidation = false; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, + FRAME_RATE_IDLENESS_REEVALUATE_TIME); + } + break; } } } @@ -7207,6 +7276,7 @@ public final class ViewRootImpl implements ViewParent, private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; + final int action = event.getAction(); boolean handled = mHandwritingInitiator.onTouchEvent(event); if (handled) { // If handwriting is started, toolkit doesn't receive ACTION_UP. @@ -7227,6 +7297,22 @@ public final class ViewRootImpl implements ViewParent, scheduleConsumeBatchedInputImmediately(); } } + + // For the variable refresh rate project + if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { + // set the frame rate to the maximum value. + mIsFrameRateBoosting = true; + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + } + /** + * We want to lower the refresh rate when MotionEvent.ACTION_UP, + * MotionEvent.ACTION_CANCEL is detected. + * Not using ACTION_MOVE to avoid checking and sending messages too frequently. + */ + if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_CANCEL)) { + sendDelayedEmptyMessage(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME); + } return handled ? FINISH_HANDLED : FORWARD; } @@ -11810,4 +11896,94 @@ public final class ViewRootImpl implements ViewParent, @NonNull Consumer<Boolean> listener) { t.clearTrustedPresentationCallback(getSurfaceControl()); } + + private void setPreferredFrameRateCategory(int preferredFrameRateCategory) { + if (!shouldSetFrameRateCategory()) { + return; + } + + int frameRateCategory = mIsFrameRateBoosting + ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; + + try { + mFrameRateTransaction.setFrameRateCategory(mSurfaceControl, + frameRateCategory, false).apply(); + } catch (Exception e) { + Log.e(mTag, "Unable to set frame rate category", e); + } + + if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) { + // Check where the display is idled periodically. + // If so, set the frame rate category to NO_PREFERENCE + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, + FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS); + mHasIdledMessage = true; + } + } + + private void setPreferredFrameRate(float preferredFrameRate) { + if (!shouldSetFrameRate()) { + return; + } + + try { + mFrameRateTransaction.setFrameRate(mSurfaceControl, + preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply(); + } catch (Exception e) { + Log.e(mTag, "Unable to set frame rate", e); + } + } + + private void sendDelayedEmptyMessage(int message, int delayedTime) { + mHandler.removeMessages(message); + + mHandler.sendEmptyMessageDelayed(message, delayedTime); + } + + private boolean shouldSetFrameRateCategory() { + // use toolkitSetFrameRate flag to gate the change + return mSurface.isValid() && toolkitSetFrameRate(); + } + + private boolean shouldSetFrameRate() { + // use toolkitSetFrameRate flag to gate the change + return mPreferredFrameRate > 0 && toolkitSetFrameRate(); + } + + private boolean shouldTouchBoost(int motionEventAction, int windowType) { + boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN + || motionEventAction == MotionEvent.ACTION_MOVE + || motionEventAction == MotionEvent.ACTION_UP; + boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION + || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION; + // use toolkitSetFrameRate flag to gate the change + return desiredAction && desiredType && toolkitSetFrameRate(); + } + + /** + * Allow Views to vote for the preferred frame rate category + * + * @param frameRateCategory the preferred frame rate category of a View + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) + public void votePreferredFrameRateCategory(int frameRateCategory) { + mPreferredFrameRateCategory = Math.max(mPreferredFrameRateCategory, frameRateCategory); + mHasInvalidation = true; + } + + /** + * Get the value of mPreferredFrameRateCategory + */ + @VisibleForTesting + public int getPreferredFrameRateCategory() { + return mPreferredFrameRateCategory; + } + + /** + * Get the value of mPreferredFrameRate + */ + @VisibleForTesting + public float getPreferredFrameRate() { + return mPreferredFrameRate; + } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index a3b93b433dda..4f03ce98ccac 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -17,6 +17,7 @@ package android.view; import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT; +import static android.view.flags.Flags.FLAG_WM_DISPLAY_REFRESH_RATE_TEST; import static android.view.View.STATUS_BAR_DISABLE_BACK; import static android.view.View.STATUS_BAR_DISABLE_CLOCK; import static android.view.View.STATUS_BAR_DISABLE_EXPAND; @@ -3905,6 +3906,7 @@ public interface WindowManager extends ViewManager { * This value is ignored if {@link #preferredDisplayModeId} is set. * @hide */ + @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST) @TestApi public float preferredMinDisplayRefreshRate; @@ -3914,6 +3916,7 @@ public interface WindowManager extends ViewManager { * This value is ignored if {@link #preferredDisplayModeId} is set. * @hide */ + @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST) @TestApi public float preferredMaxDisplayRefreshRate; diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 60f46e60ec9e..12ce0f47c460 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -1731,6 +1731,9 @@ public final class AccessibilityInteractionClient @Override public void sendAttachOverlayResult( @AccessibilityService.AttachOverlayResult int result, int interactionId) { + if (!Flags.a11yOverlayCallbacks()) { + return; + } synchronized (mInstanceLock) { if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) { final Pair<Executor, IntConsumer> pair = diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index 09b2f9c1ee15..fa0052cf664a 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -99,7 +99,6 @@ public final class AccessibilityWindowInfo implements Parcelable { /** @hide */ public static final int UNDEFINED_CONNECTION_ID = -1; /** @hide */ - @TestApi public static final int UNDEFINED_WINDOW_ID = -1; /** @hide */ public static final int ANY_WINDOW_ID = -2; diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 85dadd4a061d..cc612ed93b2f 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -13,3 +13,10 @@ flag { description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen" bug: "303871725" } + +flag { + name: "a11y_overlay_callbacks" + namespace: "accessibility" + description: "Whether to allow the passing of result callbacks when attaching a11y overlays." + bug: "304478691" +} diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 2241fd5dc37a..b44d6a496058 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -317,16 +317,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } - // Should not be possible for mComponentName to be null here but check anyway - if (mManager.mOptions.contentProtectionOptions.enableReceiver - && mManager.getContentProtectionEventBuffer() != null - && mComponentName != null) { + if (isContentProtectionEnabled()) { mContentProtectionEventProcessor = new ContentProtectionEventProcessor( mManager.getContentProtectionEventBuffer(), mHandler, mSystemServerInterface, - mComponentName.getPackageName()); + mComponentName.getPackageName(), + mManager.mOptions.contentProtectionOptions); } else { mContentProtectionEventProcessor = null; } @@ -956,4 +954,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private boolean isContentCaptureReceiverEnabled() { return mManager.mOptions.enableReceiver; } + + @UiThread + private boolean isContentProtectionEnabled() { + // Should not be possible for mComponentName to be null here but check anyway + // Should not be possible for groups to be empty if receiver is enabled but check anyway + return mManager.mOptions.contentProtectionOptions.enableReceiver + && mManager.getContentProtectionEventBuffer() != null + && mComponentName != null + && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty() + || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty()); + } } diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java index b44abf3eb04d..aaf90bd00535 100644 --- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java @@ -19,9 +19,9 @@ package android.view.contentprotection; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiThread; +import android.content.ContentCaptureOptions; import android.content.pm.ParceledListSlice; import android.os.Handler; -import android.text.InputType; import android.util.Log; import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.IContentCaptureManager; @@ -33,10 +33,10 @@ import com.android.internal.util.RingBuffer; import java.time.Duration; import java.time.Instant; import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; +import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.stream.Stream; /** * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow. @@ -47,33 +47,13 @@ public class ContentProtectionEventProcessor { private static final String TAG = "ContentProtectionEventProcessor"; - private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES = - Collections.unmodifiableList( - Arrays.asList( - InputType.TYPE_NUMBER_VARIATION_PASSWORD, - InputType.TYPE_TEXT_VARIATION_PASSWORD, - InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD, - InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)); - - private static final List<String> PASSWORD_TEXTS = - Collections.unmodifiableList( - Arrays.asList("password", "pass word", "code", "pin", "credential")); - - private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS = - Collections.unmodifiableList( - Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in")); - private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3); - private static final String ANDROID_CLASS_NAME_PREFIX = "android."; - private static final Set<Integer> EVENT_TYPES_TO_STORE = - Collections.unmodifiableSet( - new HashSet<>( - Arrays.asList( - ContentCaptureEvent.TYPE_VIEW_APPEARED, - ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, - ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED))); + Set.of( + ContentCaptureEvent.TYPE_VIEW_APPEARED, + ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, + ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED); private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; @@ -85,11 +65,7 @@ public class ContentProtectionEventProcessor { @NonNull private final String mPackageName; - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public boolean mPasswordFieldDetected = false; - - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public boolean mSuspiciousTextDetected = false; + @NonNull private final ContentCaptureOptions.ContentProtectionOptions mOptions; @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @Nullable @@ -97,15 +73,32 @@ public class ContentProtectionEventProcessor { private int mResetLoginRemainingEventsToProcess; + private boolean mAnyGroupFound = false; + + // Ordered by priority + private final List<SearchGroup> mGroupsRequired; + + // Ordered by priority + private final List<SearchGroup> mGroupsOptional; + + // Ordered by priority + private final List<SearchGroup> mGroupsAll; + public ContentProtectionEventProcessor( @NonNull RingBuffer<ContentCaptureEvent> eventBuffer, @NonNull Handler handler, @NonNull IContentCaptureManager contentCaptureManager, - @NonNull String packageName) { + @NonNull String packageName, + @NonNull ContentCaptureOptions.ContentProtectionOptions options) { mEventBuffer = eventBuffer; mHandler = handler; mContentCaptureManager = contentCaptureManager; mPackageName = packageName; + mOptions = options; + mGroupsRequired = options.requiredGroups.stream().map(SearchGroup::new).toList(); + mGroupsOptional = options.optionalGroups.stream().map(SearchGroup::new).toList(); + mGroupsAll = + Stream.of(mGroupsRequired, mGroupsOptional).flatMap(Collection::stream).toList(); } /** Main entry point for {@link ContentCaptureEvent} processing. */ @@ -130,9 +123,31 @@ public class ContentProtectionEventProcessor { @UiThread private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) { - mPasswordFieldDetected |= isPasswordField(event); - mSuspiciousTextDetected |= isSuspiciousText(event); - if (mPasswordFieldDetected && mSuspiciousTextDetected) { + ViewNode viewNode = event.getViewNode(); + String eventText = ContentProtectionUtils.getEventTextLower(event); + String viewNodeText = ContentProtectionUtils.getViewNodeTextLower(viewNode); + String hintText = ContentProtectionUtils.getHintTextLower(viewNode); + + mGroupsAll.stream() + .filter(group -> !group.mFound) + .filter( + group -> + group.matches(eventText) + || group.matches(viewNodeText) + || group.matches(hintText)) + .findFirst() + .ifPresent( + group -> { + group.mFound = true; + mAnyGroupFound = true; + }); + + boolean loginDetected = + mGroupsRequired.stream().allMatch(group -> group.mFound) + && mGroupsOptional.stream().filter(group -> group.mFound).count() + >= mOptions.optionalGroupsThreshold; + + if (loginDetected) { loginDetected(); } else { maybeResetLoginFlags(); @@ -150,14 +165,13 @@ public class ContentProtectionEventProcessor { @UiThread private void resetLoginFlags() { - mPasswordFieldDetected = false; - mSuspiciousTextDetected = false; - mResetLoginRemainingEventsToProcess = 0; + mGroupsAll.forEach(group -> group.mFound = false); + mAnyGroupFound = false; } @UiThread private void maybeResetLoginFlags() { - if (mPasswordFieldDetected || mSuspiciousTextDetected) { + if (mAnyGroupFound) { if (mResetLoginRemainingEventsToProcess <= 0) { mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; } else { @@ -194,61 +208,21 @@ public class ContentProtectionEventProcessor { } } - private boolean isPasswordField(@NonNull ContentCaptureEvent event) { - return isPasswordField(event.getViewNode()); - } - - private boolean isPasswordField(@Nullable ViewNode viewNode) { - if (viewNode == null) { - return false; - } - return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode); - } - - private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) { - if (!isAndroidViewNode(viewNode)) { - return false; - } - int inputType = viewNode.getInputType(); - return PASSWORD_FIELD_INPUT_TYPES.stream() - .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0); - } + private static final class SearchGroup { - private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) { - if (viewNode.getClassName() != null) { - return false; - } - return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode)); - } + @NonNull private final List<String> mSearchStrings; - private boolean isAndroidViewNode(@NonNull ViewNode viewNode) { - String className = viewNode.getClassName(); - return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX); - } - - private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) { - return isSuspiciousText(ContentProtectionUtils.getEventText(event)) - || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event)); - } + public boolean mFound = false; - private boolean isSuspiciousText(@Nullable String text) { - if (text == null) { - return false; + SearchGroup(@NonNull List<String> searchStrings) { + mSearchStrings = searchStrings; } - if (isPasswordText(text)) { - return true; - } - String lowerCaseText = text.toLowerCase(); - return ADDITIONAL_SUSPICIOUS_TEXTS.stream() - .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText)); - } - private boolean isPasswordText(@Nullable String text) { - if (text == null) { - return false; + public boolean matches(@Nullable String text) { + if (text == null) { + return false; + } + return mSearchStrings.stream().anyMatch(text::contains); } - String lowerCaseText = text.toLowerCase(); - return PASSWORD_TEXTS.stream() - .anyMatch(passwordText -> lowerCaseText.contains(passwordText)); } } diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java index 9abf6f10d05d..1ecac7f188ab 100644 --- a/core/java/android/view/contentprotection/ContentProtectionUtils.java +++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java @@ -28,33 +28,39 @@ import android.view.contentcapture.ViewNode; */ public final class ContentProtectionUtils { - /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */ + /** Returns the lowercase text extracted from the {@link ContentCaptureEvent}, if set. */ @Nullable - public static String getEventText(@NonNull ContentCaptureEvent event) { + public static String getEventTextLower(@NonNull ContentCaptureEvent event) { CharSequence text = event.getText(); if (text == null) { return null; } - return text.toString(); + return text.toString().toLowerCase(); } - /** Returns the text extracted from the event's {@link ViewNode}, if set. */ + /** Returns the lowercase text extracted from the {@link ViewNode}, if set. */ @Nullable - public static String getViewNodeText(@NonNull ContentCaptureEvent event) { - ViewNode viewNode = event.getViewNode(); + public static String getViewNodeTextLower(@Nullable ViewNode viewNode) { if (viewNode == null) { return null; } - return getViewNodeText(viewNode); + CharSequence text = viewNode.getText(); + if (text == null) { + return null; + } + return text.toString().toLowerCase(); } - /** Returns the text extracted directly from the {@link ViewNode}, if set. */ + /** Returns the lowercase hint text extracted from the {@link ViewNode}, if set. */ @Nullable - public static String getViewNodeText(@NonNull ViewNode viewNode) { - CharSequence text = viewNode.getText(); + public static String getHintTextLower(@Nullable ViewNode viewNode) { + if (viewNode == null) { + return null; + } + String text = viewNode.getHint(); if (text == null) { return null; } - return text.toString(); + return text.toLowerCase(); } } diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig index 56b5fac1c64a..fd9689013af3 100644 --- a/core/java/android/view/flags/refresh_rate_flags.aconfig +++ b/core/java/android/view/flags/refresh_rate_flags.aconfig @@ -26,4 +26,11 @@ flag { namespace: "core_graphics" description: "Enable the `setFrameRate` callback" bug: "299946220" +} + +flag { + name: "wm_display_refresh_rate_test" + namespace: "core_graphics" + description: "Adds WindowManager display refresh rate fields to test API" + bug: "304475199" }
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 8159af3ddd4a..eeab005771f5 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -39,6 +39,7 @@ import android.Manifest; import android.annotation.DisplayContext; import android.annotation.DrawableRes; import android.annotation.DurationMillisLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1512,6 +1513,7 @@ public final class InputMethodManager { * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled. * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be * called and Stylus touch should continue as normal touch input. + * * @see #startStylusHandwriting(View) */ public boolean isStylusHandwritingAvailable() { @@ -1535,6 +1537,7 @@ public final class InputMethodManager { @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) @TestApi + @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS) @SuppressLint("UserHandle") public boolean isStylusHandwritingAvailableAsUser(@NonNull UserHandle user) { final Context fallbackContext = ActivityThread.currentApplication(); @@ -1655,6 +1658,7 @@ public final class InputMethodManager { @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) @TestApi + @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS) @SuppressLint("UserHandle") public List<InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull UserHandle user) { return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(user.getIdentifier()); @@ -1690,12 +1694,13 @@ public final class InputMethodManager { * {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required if this is * different from the calling process user ID. * @return {@link List} of {@link InputMethodSubtype}. - * @see #getEnabledInputMethodListAsUser(int) + * @see #getEnabledInputMethodListAsUser(UserHandle) * @hide */ @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) @TestApi + @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS) @SuppressLint("UserHandle") public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser( @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes, diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index c14b5104242a..1e8718ce42c0 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -14,4 +14,12 @@ flag { description: "Feature flag for adding EditorInfo#mStylusHandwritingEnabled" bug: "293898187" is_fixed_read_only: true +} + +flag { + name: "imm_userhandle_hostsidetests" + namespace: "input_method" + description: "Feature flag for replacing UserIdInt with UserHandle in some helper IMM functions" + bug: "301713309" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index e8281eac5928..17c82b63c443 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -15555,6 +15555,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void ensureIterableTextForAccessibilitySelectable() { if (!(mText instanceof Spannable)) { setText(mText, BufferType.SPANNABLE); + if (getLayout() == null) { + assumeLayout(); + } } } diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index 43fa0be6c1b7..4e0f9a51c0a0 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -88,6 +88,26 @@ public final class TaskFragmentOperation implements Parcelable { */ public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11; + /** + * Reorders the TaskFragment to be the bottom-most in the Task. Note that this op will bring the + * TaskFragment to the bottom of the Task below all the other Activities and TaskFragments. + * + * This is only allowed for system organizers. See + * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( + * ITaskFragmentOrganizer, boolean)} + */ + public static final int OP_TYPE_REORDER_TO_BOTTOM_OF_TASK = 12; + + /** + * Reorders the TaskFragment to be the top-most in the Task. Note that this op will bring the + * TaskFragment to the top of the Task above all the other Activities and TaskFragments. + * + * This is only allowed for system organizers. See + * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( + * ITaskFragmentOrganizer, boolean)} + */ + public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13; + @IntDef(prefix = { "OP_TYPE_" }, value = { OP_TYPE_UNKNOWN, OP_TYPE_CREATE_TASK_FRAGMENT, @@ -101,7 +121,9 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_SET_ANIMATION_PARAMS, OP_TYPE_SET_RELATIVE_BOUNDS, OP_TYPE_REORDER_TO_FRONT, - OP_TYPE_SET_ISOLATED_NAVIGATION + OP_TYPE_SET_ISOLATED_NAVIGATION, + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK, + OP_TYPE_REORDER_TO_TOP_OF_TASK, }) @Retention(RetentionPolicy.SOURCE) public @interface OperationType {} diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 61f340a856c4..1a2d202be934 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -402,6 +402,18 @@ public final class TransitionInfo implements Parcelable { @Override public String toString() { + return toString(""); + } + + /** + * Returns a string representation of this transition info. + * @hide + */ + public String toString(@NonNull String prefix) { + final boolean shouldPrettyPrint = !prefix.isEmpty() && !mChanges.isEmpty(); + final String innerPrefix = shouldPrettyPrint ? prefix + " " : ""; + final String changesLineStart = shouldPrettyPrint ? "\n" + prefix : ""; + final String perChangeLineStart = shouldPrettyPrint ? "\n" + innerPrefix : ""; StringBuilder sb = new StringBuilder(); sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType)) .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack) @@ -413,12 +425,15 @@ public final class TransitionInfo implements Parcelable { sb.append(mRoots.get(i).mDisplayId).append("@").append(mRoots.get(i).mOffset); } sb.append("] c=["); + sb.append(perChangeLineStart); for (int i = 0; i < mChanges.size(); ++i) { if (i > 0) { sb.append(','); + sb.append(perChangeLineStart); } sb.append(mChanges.get(i)); } + sb.append(changesLineStart); sb.append("]}"); return sb.toString(); } diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java index 932608a3b57b..bd54e14bc996 100644 --- a/core/java/android/window/TransitionRequestInfo.java +++ b/core/java/android/window/TransitionRequestInfo.java @@ -62,13 +62,16 @@ public final class TransitionRequestInfo implements Parcelable { /** The transition flags known at the time of the request. These may not be complete. */ private final int mFlags; + /** This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */ + private final int mDebugId; + /** constructor override */ public TransitionRequestInfo( @WindowManager.TransitionType int type, @Nullable ActivityManager.RunningTaskInfo triggerTask, @Nullable RemoteTransition remoteTransition) { this(type, triggerTask, null /* pipTask */, - remoteTransition, null /* displayChange */, 0 /* flags */); + remoteTransition, null /* displayChange */, 0 /* flags */, -1 /* debugId */); } /** constructor override */ @@ -78,16 +81,29 @@ public final class TransitionRequestInfo implements Parcelable { @Nullable RemoteTransition remoteTransition, int flags) { this(type, triggerTask, null /* pipTask */, - remoteTransition, null /* displayChange */, flags); + remoteTransition, null /* displayChange */, flags, -1 /* debugId */); } + /** constructor override */ public TransitionRequestInfo( @WindowManager.TransitionType int type, @Nullable ActivityManager.RunningTaskInfo triggerTask, @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange, int flags) { - this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags); + this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags, + -1 /* debugId */); + } + + /** constructor override */ + public TransitionRequestInfo( + @WindowManager.TransitionType int type, + @Nullable ActivityManager.RunningTaskInfo triggerTask, + @Nullable ActivityManager.RunningTaskInfo pipTask, + @Nullable RemoteTransition remoteTransition, + @Nullable TransitionRequestInfo.DisplayChange displayChange, + int flags) { + this(type, triggerTask, pipTask, remoteTransition, displayChange, flags, -1 /* debugId */); } /** @hide */ @@ -270,7 +286,7 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1695667226050L, + time = 1697564781403L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", inputSignatures = "private final int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate int mStartRotation\nprivate int mEndRotation\nprivate boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)") @@ -318,6 +334,8 @@ public final class TransitionRequestInfo implements Parcelable { * (if size is changing). * @param flags * The transition flags known at the time of the request. These may not be complete. + * @param debugId + * This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */ @DataClass.Generated.Member public TransitionRequestInfo( @@ -326,7 +344,8 @@ public final class TransitionRequestInfo implements Parcelable { @Nullable ActivityManager.RunningTaskInfo pipTask, @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange, - int flags) { + int flags, + int debugId) { this.mType = type; com.android.internal.util.AnnotationValidations.validate( WindowManager.TransitionType.class, null, mType); @@ -335,6 +354,7 @@ public final class TransitionRequestInfo implements Parcelable { this.mRemoteTransition = remoteTransition; this.mDisplayChange = displayChange; this.mFlags = flags; + this.mDebugId = debugId; // onConstructed(); // You can define this method to get a callback } @@ -392,6 +412,14 @@ public final class TransitionRequestInfo implements Parcelable { } /** + * This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! + */ + @DataClass.Generated.Member + public int getDebugId() { + return mDebugId; + } + + /** * If non-null, the task containing the activity whose lifecycle change (start or * finish) has caused this transition to occur. */ @@ -443,7 +471,8 @@ public final class TransitionRequestInfo implements Parcelable { "pipTask = " + mPipTask + ", " + "remoteTransition = " + mRemoteTransition + ", " + "displayChange = " + mDisplayChange + ", " + - "flags = " + mFlags + + "flags = " + mFlags + ", " + + "debugId = " + mDebugId + " }"; } @@ -465,6 +494,7 @@ public final class TransitionRequestInfo implements Parcelable { if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags); if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags); dest.writeInt(mFlags); + dest.writeInt(mDebugId); } @Override @@ -485,6 +515,7 @@ public final class TransitionRequestInfo implements Parcelable { RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR); TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR); int flags = in.readInt(); + int debugId = in.readInt(); this.mType = type; com.android.internal.util.AnnotationValidations.validate( @@ -494,6 +525,7 @@ public final class TransitionRequestInfo implements Parcelable { this.mRemoteTransition = remoteTransition; this.mDisplayChange = displayChange; this.mFlags = flags; + this.mDebugId = debugId; // onConstructed(); // You can define this method to get a callback } @@ -513,10 +545,10 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1695667226088L, + time = 1697564781438L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", - inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\n java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") + inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nprivate final int mDebugId\n java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 392aa1b13e0e..73778900399c 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -20,4 +20,20 @@ flag { description: "Refactor dim to fix flickers" bug: "281632483,295291019" is_fixed_read_only: true +} + +flag { + name: "transit_ready_tracking" + namespace: "windowing_frontend" + description: "Enable accurate transition readiness tracking" + bug: "294925498" +} + + +flag { + name: "wallpaper_offset_async" + namespace: "windowing_frontend" + description: "Do not synchronise the wallpaper offset" + bug: "293248754" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java index 6fd50180e78b..504928cd4347 100644 --- a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java +++ b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java @@ -74,16 +74,15 @@ public class WearGestureInterceptionDetector { return windowSwipeToDismiss; } - private boolean isPointerIndexValid(MotionEvent ev) { + private int getIndexForValidPointer(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; + return pointerIndex; } private void updateSwiping(MotionEvent ev) { @@ -98,7 +97,7 @@ public class WearGestureInterceptionDetector { } } - private void updateDiscardIntercept(MotionEvent ev) { + private void updateDiscardIntercept(MotionEvent ev, int pointerIndex) { if (!mSwiping) { // Don't look at canScroll until we have passed the touch slop return; @@ -107,8 +106,8 @@ public class WearGestureInterceptionDetector { return; } final boolean checkLeft = mDownX < ev.getRawX(); - final float x = ev.getX(mActivePointerId); - final float y = ev.getY(mActivePointerId); + final float x = ev.getX(pointerIndex); + final float y = ev.getY(pointerIndex); if (canScroll(mInstalledDecorView, false, checkLeft, x, y)) { mDiscardIntercept = true; } @@ -152,11 +151,12 @@ public class WearGestureInterceptionDetector { if (mDiscardIntercept) { break; } - if (!isPointerIndexValid(ev)) { + final int pointerIndex = getIndexForValidPointer(ev); + if (pointerIndex == -1) { break; } updateSwiping(ev); - updateDiscardIntercept(ev); + updateDiscardIntercept(ev, pointerIndex); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index deb138fda867..9c883d18a9ae 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -265,6 +265,18 @@ static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong s return mgr->isDataInjectionEnabled(); } +static jboolean nativeIsReplayDataInjectionEnabled(JNIEnv *_env, jclass _this, + jlong sensorManager) { + SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager); + return mgr->isReplayDataInjectionEnabled(); +} + +static jboolean nativeIsHalBypassReplayDataInjectionEnabled(JNIEnv *_env, jclass _this, + jlong sensorManager) { + SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager); + return mgr->isHalBypassReplayDataInjectionEnabled(); +} + static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager, jint deviceId, jlong size, jint channelType, jint fd, jobject hardwareBufferObj) { @@ -533,6 +545,11 @@ static const JNINativeMethod gSystemSensorManagerMethods[] = { {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled}, + {"nativeIsReplayDataInjectionEnabled", "(J)Z", (void *)nativeIsReplayDataInjectionEnabled}, + + {"nativeIsHalBypassReplayDataInjectionEnabled", "(J)Z", + (void *)nativeIsHalBypassReplayDataInjectionEnabled}, + {"nativeCreateDirectChannel", "(JIJIILandroid/hardware/HardwareBuffer;)I", (void *)nativeCreateDirectChannel}, diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto index eb14db070f9f..068f4dd07ccb 100644 --- a/core/proto/android/service/package.proto +++ b/core/proto/android/service/package.proto @@ -115,6 +115,9 @@ message PackageProto { // Only set if the app defined a monochrome icon. optional string monochrome_icon_bitmap_path = 3; + + // The component name of the original activity (pre-archival). + optional string original_component_name = 4; } /** Information about main activities. */ diff --git a/core/res/Android.bp b/core/res/Android.bp index b71995f899c5..4e686b7ad80c 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -104,6 +104,7 @@ genrule { android_app { name: "framework-res", + use_resource_processor: false, sdk_version: "core_platform", certificate: "platform", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 88b578ba772b..cffbaa75ab76 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6123,7 +6123,7 @@ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> <!-- @SystemApi @hide - @FlaggedApi("backstage_power.report_usage_stats_permission") + @FlaggedApi("android.app.usage.report_usage_stats_permission") Allows trusted system components to report events to UsageStatsManager --> <permission android:name="android.permission.REPORT_USAGE_STATS" android:protectionLevel="signature|module" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b211ac2fd316..929133f792c0 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3988,6 +3988,13 @@ limit is unknown. --> <item name="config_hapticChannelMaxVibrationAmplitude" format="float" type="dimen">0</item> + <!-- The fixed keyboard vibration strength in [0,1], or -1 to indicate the strength not fixed + and should depend on the touch feedback intensity user setting --> + <item name="config_keyboardHapticFeedbackFixedAmplitude" format="float" type="dimen">-1</item> + + <!-- The default value for keyboard vibration toggle in settings. --> + <bool name="config_defaultKeyboardVibrationEnabled">true</bool> + <!-- If the device should still vibrate even in low power mode, for certain priority vibrations (e.g. accessibility, alarms). This is mainly for Wear devices that don't have speakers. --> <bool name="config_allowPriorityVibrationsInLowPowerMode">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 76744ea67589..2b9194f8c54e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2053,6 +2053,8 @@ <java-symbol type="integer" name="config_previousVibrationsDumpAggregationTimeMillisLimit" /> <java-symbol type="integer" name="config_defaultVibrationAmplitude" /> <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" /> + <java-symbol type="dimen" name="config_keyboardHapticFeedbackFixedAmplitude" /> + <java-symbol type="bool" name="config_defaultKeyboardVibrationEnabled" /> <java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" /> <java-symbol type="bool" name="config_ignoreVibrationsOnWirelessCharger" /> <java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index af8c69ea1441..3a2e50aa06e8 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -54,6 +54,9 @@ <!-- Azerbaijan: 4-5 digits, known premium codes listed --> <shortcode country="az" pattern="\\d{4,5}" premium="330[12]|87744|901[234]|93(?:94|101)|9426|9525" /> + <!-- Bangladesh: 1-5 digits (standard system default, not country specific) --> + <shortcode country="bd" pattern="\\d{1,5}" free="16672" /> + <!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp --> <shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" /> @@ -145,7 +148,7 @@ <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" /> <!-- Indonesia: 1-5 digits (standard system default, not country specific) --> - <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363" /> + <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457" /> <!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU: http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf --> @@ -190,7 +193,7 @@ <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" /> <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101" /> + <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" /> <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf --> <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" /> @@ -205,7 +208,7 @@ <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" free="2171" /> <!-- New Zealand: 3-4 digits, known premium codes listed --> - <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> + <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|3876|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> <!-- Peru: 4-5 digits (not confirmed), known premium codes listed --> <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 2993a0e63228..445ddf52bf1c 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -65,6 +65,7 @@ android_test { "device-time-shell-utils", "testables", "com.android.text.flags-aconfig-java", + "flag-junit", ], libs: [ @@ -75,6 +76,7 @@ android_test { "framework", "ext", "framework-res", + "android.view.flags-aconfig-java", ], jni_libs: [ "libpowermanagertest_jni", diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java index 07dec5d9e222..b843ad75ac0f 100644 --- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java +++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java @@ -79,6 +79,8 @@ public class FaceManagerTest { private FaceManager.AuthenticationCallback mAuthCallback; @Mock private FaceManager.EnrollmentCallback mEnrollmentCallback; + @Mock + private FaceManager.FaceDetectionCallback mFaceDetectionCallback; @Captor private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mCaptor; @@ -191,6 +193,23 @@ public class FaceManagerTest { any(), anyString(), any(), any(), anyBoolean()); } + @Test + public void detectClient_onError() throws RemoteException { + ArgumentCaptor<IFaceServiceReceiver> argumentCaptor = + ArgumentCaptor.forClass(IFaceServiceReceiver.class); + + CancellationSignal cancellationSignal = new CancellationSignal(); + mFaceManager.detectFace(cancellationSignal, mFaceDetectionCallback, + new FaceAuthenticateOptions.Builder().build()); + + verify(mService).detectFace(any(), argumentCaptor.capture(), any()); + + argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */); + mLooper.dispatchAll(); + + verify(mFaceDetectionCallback).onDetectionError(anyInt()); + } + private void initializeProperties() throws RemoteException { verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture()); diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java index 625e2e3723a7..70313b8c9ea7 100644 --- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java +++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java @@ -74,6 +74,8 @@ public class FingerprintManagerTest { private FingerprintManager.AuthenticationCallback mAuthCallback; @Mock private FingerprintManager.EnrollmentCallback mEnrollCallback; + @Mock + private FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback; @Captor private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mCaptor; @@ -166,4 +168,21 @@ public class FingerprintManagerTest { anyString()); verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt()); } + + @Test + public void detectClient_onError() throws RemoteException { + ArgumentCaptor<IFingerprintServiceReceiver> argumentCaptor = + ArgumentCaptor.forClass(IFingerprintServiceReceiver.class); + + mFingerprintManager.detectFingerprint(new CancellationSignal(), + mFingerprintDetectionCallback, + new FingerprintAuthenticateOptions.Builder().build()); + + verify(mService).detectFingerprint(any(), argumentCaptor.capture(), any()); + + argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */); + mLooper.dispatchAll(); + + verify(mFingerprintDetectionCallback).onDetectionError(anyInt()); + } } diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 6a9fc04230f8..1a38decae604 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -17,6 +17,11 @@ package android.view; import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR; +import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; +import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; +import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; @@ -48,8 +53,12 @@ import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; import android.os.SystemProperties; 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.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; +import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; @@ -97,6 +106,10 @@ public class ViewRootImplTest { // state after the test completes. private static boolean sOriginalTouchMode; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @BeforeClass public static void setUpClass() { sContext = sInstrumentation.getTargetContext(); @@ -427,6 +440,129 @@ public class ViewRootImplTest { assertThat(result).isFalse(); } + /** + * Test the default values are properly set + */ + @UiThreadTest + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_getDefaultValues() { + ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, + sContext.getDisplayNoVerify()); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NO_PREFERENCE); + assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1); + } + + /** + * Test the value of the frame rate cateogry based on the visibility of a view + * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE + * Visible: FRAME_RATE_CATEGORY_NORMAL + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_visibility() { + View view = new View(sContext); + attachViewToWindow(view); + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + sInstrumentation.runOnMainSync(() -> { + view.setVisibility(View.INVISIBLE); + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NO_PREFERENCE); + }); + + sInstrumentation.runOnMainSync(() -> { + view.setVisibility(View.VISIBLE); + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NORMAL); + }); + } + + /** + * Test the value of the frame rate cateogry based on the size of a view. + * The current threshold value is 7% of the screen size + * <7%: FRAME_RATE_CATEGORY_LOW + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_smallSize() { + View view = new View(sContext); + WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check + wmlp.width = 1; + wmlp.height = 1; + + sInstrumentation.runOnMainSync(() -> { + WindowManager wm = sContext.getSystemService(WindowManager.class); + wm.addView(view, wmlp); + }); + sInstrumentation.waitForIdleSync(); + + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + sInstrumentation.runOnMainSync(() -> { + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); + }); + } + + /** + * Test the value of the frame rate cateogry based on the size of a view. + * The current threshold value is 7% of the screen size + * >=7% : FRAME_RATE_CATEGORY_NORMAL + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_normalSize() { + View view = new View(sContext); + WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check + + sInstrumentation.runOnMainSync(() -> { + WindowManager wm = sContext.getSystemService(WindowManager.class); + Display display = wm.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + wmlp.width = (int) (metrics.widthPixels * 0.9); + wmlp.height = (int) (metrics.heightPixels * 0.9); + wm.addView(view, wmlp); + }); + sInstrumentation.waitForIdleSync(); + + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + sInstrumentation.runOnMainSync(() -> { + view.invalidate(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); + }); + } + + /** + * Test how values of the frame rate cateogry are aggregated. + * It should take the max value among all of the voted categories per frame. + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) + public void votePreferredFrameRate_voteFrameRateCategory_aggregate() { + View view = new View(sContext); + attachViewToWindow(view); + sInstrumentation.runOnMainSync(() -> { + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NO_PREFERENCE); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + }); + } + @Test public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() { mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR); diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index e76d266c614c..d47d7891d0e4 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -115,6 +115,26 @@ public class MainContentCaptureSessionTest { new ContentCaptureOptions.ContentProtectionOptions( /* enableReceiver= */ true, -BUFFER_SIZE, + /* requiredGroups= */ List.of(List.of("a")), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); + MainContentCaptureSession session = createSession(options); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + + assertThat(session.mContentProtectionEventProcessor).isNull(); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + } + + @Test + public void onSessionStarted_contentProtectionNoGroups_processorNotCreated() { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + new ContentCaptureOptions.ContentProtectionOptions( + /* enableReceiver= */ true, + BUFFER_SIZE, /* requiredGroups= */ Collections.emptyList(), /* optionalGroups= */ Collections.emptyList(), /* optionalGroupsThreshold= */ 0)); @@ -320,7 +340,7 @@ public class MainContentCaptureSessionTest { new ContentCaptureOptions.ContentProtectionOptions( enableContentProtectionReceiver, BUFFER_SIZE, - /* requiredGroups= */ Collections.emptyList(), + /* requiredGroups= */ List.of(List.of("a")), /* optionalGroups= */ Collections.emptyList(), /* optionalGroupsThreshold= */ 0)); } diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java index 39a2e0e048e8..ba0dbf454ad2 100644 --- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java +++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java @@ -29,12 +29,13 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ContentCaptureOptions; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.Handler; -import android.os.Looper; -import android.text.InputType; +import android.os.test.TestLooper; import android.view.View; import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.IContentCaptureManager; @@ -57,6 +58,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.mockito.verification.VerificationMode; import java.time.Instant; import java.util.ArrayList; @@ -75,13 +77,25 @@ public class ContentProtectionEventProcessorTest { private static final String PACKAGE_NAME = "com.test.package.name"; - private static final String ANDROID_CLASS_NAME = "android.test.some.class.name"; + private static final String TEXT_REQUIRED1 = "TEXT REQUIRED1 TEXT"; - private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE"; + private static final String TEXT_REQUIRED2 = "TEXT REQUIRED2 TEXT"; - private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN"; + private static final String TEXT_OPTIONAL1 = "TEXT OPTIONAL1 TEXT"; - private static final String SAFE_TEXT = "SAFE TEXT"; + private static final String TEXT_OPTIONAL2 = "TEXT OPTIONAL2 TEXT"; + + private static final String TEXT_CONTAINS_OPTIONAL3 = "TEXTOPTIONAL3TEXT"; + + private static final String TEXT_SHARED = "TEXT SHARED TEXT"; + + private static final String TEXT_SAFE = "TEXT SAFE TEXT"; + + private static final List<List<String>> REQUIRED_GROUPS = + List.of(List.of("required1", "missing"), List.of("required2", "shared")); + + private static final List<List<String>> OPTIONAL_GROUPS = + List.of(List.of("optional1"), List.of("optional2", "optional3"), List.of("shared")); private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent(); @@ -91,7 +105,17 @@ public class ContentProtectionEventProcessorTest { private static final Set<Integer> EVENT_TYPES_TO_STORE = ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED); - private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; + private static final int BUFFER_SIZE = 150; + + private static final int OPTIONAL_GROUPS_THRESHOLD = 1; + + private static final ContentCaptureOptions.ContentProtectionOptions OPTIONS = + new ContentCaptureOptions.ContentProtectionOptions( + /* enableReceiver= */ true, + BUFFER_SIZE, + REQUIRED_GROUPS, + OPTIONAL_GROUPS, + OPTIONAL_GROUPS_THRESHOLD); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -101,16 +125,19 @@ public class ContentProtectionEventProcessorTest { private final Context mContext = ApplicationProvider.getApplicationContext(); - private ContentProtectionEventProcessor mContentProtectionEventProcessor; + private final TestLooper mTestLooper = new TestLooper(); + + @NonNull private ContentProtectionEventProcessor mContentProtectionEventProcessor; @Before public void setup() { mContentProtectionEventProcessor = new ContentProtectionEventProcessor( mMockEventBuffer, - new Handler(Looper.getMainLooper()), + new Handler(mTestLooper.getLooper()), mMockContentCaptureManager, - PACKAGE_NAME); + PACKAGE_NAME, + OPTIONS); } @Test @@ -156,347 +183,224 @@ public class ContentProtectionEventProcessorTest { } @Test - public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() { - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - - for (int type = -100; type <= 100; type++) { - if (type == TYPE_VIEW_APPEARED) { - continue; - } - - mContentProtectionEventProcessor.processEvent(createEvent(type)); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - } - - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } - - @Test - public void processEvent_loginDetected() throws Exception { + public void processEvent_loginDetected_true_eventText() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(); - } - - @Test - public void processEvent_loginDetected_passwordFieldNotDetected() { - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } - - @Test - public void processEvent_loginDetected_suspiciousTextNotDetected() { - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } - - @Test - public void processEvent_loginDetected_withoutViewNode() { - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ TEXT_REQUIRED1, + /* viewNodeText= */ null, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ TEXT_REQUIRED2, + /* viewNodeText= */ null, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ TEXT_OPTIONAL1, + /* viewNodeText= */ null, + /* hintText= */ null)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(); } @Test - public void processEvent_loginDetected_belowResetLimit() throws Exception { + public void processEvent_loginDetected_true_viewNodeText() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) { - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - } - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ TEXT_REQUIRED1, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ TEXT_REQUIRED2, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ TEXT_OPTIONAL1, + /* hintText= */ null)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(); + assertLoginDetected(); } @Test - public void processEvent_loginDetected_aboveResetLimit() throws Exception { - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) { - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - } - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); + public void processEvent_loginDetected_true_hintText() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ null, + /* hintText= */ TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ null, + /* hintText= */ TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ null, + /* hintText= */ TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); + assertLoginDetected(); } @Test - public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { + public void processEvent_loginDetected_true_differentOptionalGroup() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL2)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(); + assertLoginDetected(); } @Test - public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception { + public void processEvent_loginDetected_true_usesContains() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_CONTAINS_OPTIONAL3)); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, times(2)).clear(); - verify(mMockEventBuffer, times(2)).toArray(); - assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2); + assertLoginDetected(); } @Test - public void isPasswordField_android() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - mContentProtectionEventProcessor.processEvent(event); + public void processEvent_loginDetected_false_missingRequiredGroups() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_android_withoutClassName() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - mContentProtectionEventProcessor.processEvent(event); + public void processEvent_loginDetected_false_missingOptionalGroups() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_android_wrongClassName() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - "wrong.prefix" + ANDROID_CLASS_NAME, - InputType.TYPE_TEXT_VARIATION_PASSWORD); + public void processEvent_loginDetected_false_safeText() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); - mContentProtectionEventProcessor.processEvent(event); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_android_wrongInputType() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL); + public void processEvent_loginDetected_false_sharedTextOnce() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); - mContentProtectionEventProcessor.processEvent(event); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_webView() throws Exception { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent( - /* className= */ null, /* eventText= */ null, PASSWORD_TEXT); - when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event}); + public void processEvent_loginDetected_true_sharedTextMultiple() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(event, /* times= */ 1); + assertLoginDetected(); } @Test - public void isPasswordField_webView_withClassName() { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent( - /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT); + public void processEvent_loginDetected_false_inspectsOnlyTypeViewAppeared() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - mContentProtectionEventProcessor.processEvent(event); + for (int type = -100; type <= 100; type++) { + if (type == TYPE_VIEW_APPEARED) { + continue; + } + ContentCaptureEvent event = createEvent(type); + event.setText(TEXT_OPTIONAL1); + mContentProtectionEventProcessor.processEvent(event); + } - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_webView_withSafeViewNodeText() { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent( - /* className= */ null, /* eventText= */ null, SAFE_TEXT); + public void processEvent_loginDetected_true_belowResetLimit() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } + for (int i = 0; i < BUFFER_SIZE - 2; i++) { + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + } - @Test - public void isPasswordField_webView_withEventText() { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT); + assertLoginNotDetected(); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(); } @Test - public void isSuspiciousText_withSafeText() { - ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT); + public void processEvent_loginDetected_false_aboveResetLimit() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - mContentProtectionEventProcessor.processEvent(event); - - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } + for (int i = 0; i < BUFFER_SIZE - 1; i++) { + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + } - @Test - public void isSuspiciousText_eventText_suspiciousText() { - ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT); + assertLoginNotDetected(); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isSuspiciousText_viewNodeText_suspiciousText() { - ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT); + public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + for (int i = 0; i < 2; i++) { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + } - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(); } @Test - public void isSuspiciousText_eventText_passwordText() { - ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT); - - mContentProtectionEventProcessor.processEvent(event); + public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - @Test - public void isSuspiciousText_viewNodeText_passwordText() { - // Specify the class to differ from {@link isPasswordField_webView} test in this version - ContentCaptureEvent event = - createProcessEvent( - "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT); + mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(times(2)); } private static ContentCaptureEvent createEvent(int type) { @@ -511,20 +415,20 @@ public class ContentProtectionEventProcessorTest { return createEvent(TYPE_VIEW_APPEARED); } + private ContentCaptureEvent createProcessEvent(@Nullable String eventText) { + return createProcessEvent(eventText, /* viewNodeText= */ null, /* hintText= */ null); + } + private ContentCaptureEvent createProcessEvent( - @Nullable String className, - int inputType, - @Nullable String eventText, - @Nullable String viewNodeText) { + @Nullable String eventText, @Nullable String viewNodeText, @Nullable String hintText) { View view = new View(mContext); ViewStructureImpl viewStructure = new ViewStructureImpl(view); - if (className != null) { - viewStructure.setClassName(className); - } if (viewNodeText != null) { viewStructure.setText(viewNodeText); } - viewStructure.setInputType(inputType); + if (hintText != null) { + viewStructure.setHint(hintText); + } ContentCaptureEvent event = createProcessEvent(); event.setViewNode(viewStructure.getNode()); @@ -535,34 +439,28 @@ public class ContentProtectionEventProcessorTest { return event; } - private ContentCaptureEvent createAndroidPasswordFieldEvent( - @Nullable String className, int inputType) { - return createProcessEvent( - className, inputType, /* eventText= */ null, /* viewNodeText= */ null); - } - - private ContentCaptureEvent createWebViewPasswordFieldEvent( - @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) { - return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText); + private void assertLoginNotDetected() { + mTestLooper.dispatchAll(); + verify(mMockEventBuffer, never()).clear(); + verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } - private ContentCaptureEvent createSuspiciousTextEvent( - @Nullable String eventText, @Nullable String viewNodeText) { - return createProcessEvent( - /* className= */ null, /* inputType= */ 0, eventText, viewNodeText); + private void assertLoginDetected() throws Exception { + assertLoginDetected(times(1)); } - private void assertOnLoginDetected() throws Exception { - assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1); - } + private void assertLoginDetected(@NonNull VerificationMode verificationMode) throws Exception { + mTestLooper.dispatchAll(); + verify(mMockEventBuffer, verificationMode).clear(); + verify(mMockEventBuffer, verificationMode).toArray(); - private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception { ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class); - verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture()); + verify(mMockContentCaptureManager, verificationMode).onLoginDetected(captor.capture()); assertThat(captor.getValue()).isNotNull(); List<ContentCaptureEvent> actual = captor.getValue().getList(); assertThat(actual).isNotNull(); - assertThat(actual).containsExactly(event); + assertThat(actual).containsExactly(PROCESS_EVENT); } } diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java index 1459799adee5..fbe478e31888 100644 --- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java +++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 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. @@ -44,68 +44,74 @@ public class ContentProtectionUtilsTest { private static final String TEXT = "TEST_TEXT"; - private static final ContentCaptureEvent EVENT = createEvent(); - - private static final ViewNode VIEW_NODE = new ViewNode(); - - private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText(); + private static final String TEXT_LOWER = TEXT.toLowerCase(); @Test - public void event_getEventText_null() { - String actual = ContentProtectionUtils.getEventText(EVENT); + public void getEventTextLower_null() { + String actual = ContentProtectionUtils.getEventTextLower(createEvent()); assertThat(actual).isNull(); } @Test - public void event_getEventText_notNull() { - ContentCaptureEvent event = createEvent(); - event.setText(TEXT); - - String actual = ContentProtectionUtils.getEventText(event); + public void getEventTextLower_notNull() { + String actual = ContentProtectionUtils.getEventTextLower(createEventWithText()); - assertThat(actual).isEqualTo(TEXT); + assertThat(actual).isEqualTo(TEXT_LOWER); } @Test - public void event_getViewNodeText_null() { - String actual = ContentProtectionUtils.getViewNodeText(EVENT); + public void getViewNodeTextLower_null() { + String actual = ContentProtectionUtils.getViewNodeTextLower(new ViewNode()); assertThat(actual).isNull(); } @Test - public void event_getViewNodeText_notNull() { - ContentCaptureEvent event = createEvent(); - event.setViewNode(VIEW_NODE_WITH_TEXT); - - String actual = ContentProtectionUtils.getViewNodeText(event); + public void getViewNodeTextLower_notNull() { + String actual = ContentProtectionUtils.getViewNodeTextLower(createViewNodeWithText()); - assertThat(actual).isEqualTo(TEXT); + assertThat(actual).isEqualTo(TEXT_LOWER); } @Test - public void viewNode_getViewNodeText_null() { - String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE); + public void getHintTextLower_null() { + String actual = ContentProtectionUtils.getHintTextLower(new ViewNode()); assertThat(actual).isNull(); } @Test - public void viewNode_getViewNodeText_notNull() { - String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT); + public void getHintTextLower_notNull() { + String actual = ContentProtectionUtils.getHintTextLower(createViewNodeWithHint()); - assertThat(actual).isEqualTo(TEXT); + assertThat(actual).isEqualTo(TEXT_LOWER); } private static ContentCaptureEvent createEvent() { return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED); } - private static ViewNode createViewNodeWithText() { + private static ContentCaptureEvent createEventWithText() { + ContentCaptureEvent event = createEvent(); + event.setText(TEXT); + return event; + } + + private static ViewStructureImpl createViewStructureImpl() { View view = new View(ApplicationProvider.getApplicationContext()); - ViewStructureImpl viewStructure = new ViewStructureImpl(view); - viewStructure.setText(TEXT); - return viewStructure.getNode(); + return new ViewStructureImpl(view); + } + + private static ViewNode createViewNodeWithText() { + ViewStructureImpl viewStructureImpl = createViewStructureImpl(); + viewStructureImpl.setText(TEXT); + return viewStructureImpl.getNode(); + } + + private static ViewNode createViewNodeWithHint() { + ViewStructureImpl viewStructureImpl = createViewStructureImpl(); + viewStructureImpl.setHint(TEXT); + return viewStructureImpl.getNode(); } } diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java index 86f26e59e370..df212ebe1744 100644 --- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java +++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java @@ -17,7 +17,9 @@ package android.widget; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import android.content.Context; import android.platform.test.annotations.Presubmit; import android.util.PollingCheck; @@ -32,6 +34,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + @RunWith(AndroidJUnit4.class) @MediumTest @Presubmit @@ -49,23 +54,43 @@ public class HorizontalScrollViewFunctionalTest { } @Test - public void testScrollAfterFlingTop() { - mHorizontalScrollView.scrollTo(100, 0); - mHorizontalScrollView.fling(-10000); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f); + public void testScrollAfterFlingLeft() throws Throwable { + WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); + mHorizontalScrollView.mEdgeGlowLeft = edgeEffect; + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(100, 0)); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(-10000)); + assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS)); + mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- least one frame + PollingCheck.waitFor(() -> edgeEffect.getDistance() == 0f); assertEquals(0, mHorizontalScrollView.getScrollX()); } @Test - public void testScrollAfterFlingBottom() { + public void testScrollAfterFlingRight() throws Throwable { + WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); + mHorizontalScrollView.mEdgeGlowRight = edgeEffect; int childWidth = mHorizontalScrollView.getChildAt(0).getWidth(); int maxScroll = childWidth - mHorizontalScrollView.getWidth(); - mHorizontalScrollView.scrollTo(maxScroll - 100, 0); - mHorizontalScrollView.fling(10000); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(maxScroll - 100, 0)); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(10000)); + assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS)); + mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- at least one frame PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f); assertEquals(maxScroll, mHorizontalScrollView.getScrollX()); } + + static class WatchedEdgeEffect extends EdgeEffect { + public CountDownLatch onAbsorbLatch = new CountDownLatch(1); + + WatchedEdgeEffect(Context context) { + super(context); + } + + @Override + public void onAbsorb(int velocity) { + super.onAbsorb(velocity); + onAbsorbLatch.countDown(); + } + } } diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index f5e5803d4796..dc1773bd7290 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -39,12 +39,14 @@ public final class LineBreakConfig { /** * No hyphenation preference is specified. * + * <p> * This is a special value of hyphenation preference indicating no hyphenation preference is * specified. When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} * with {@link Builder#merge(LineBreakConfig)} function, the hyphenation preference of * overridden config will be kept if the hyphenation preference of overriding config is * {@link #HYPHENATION_UNSPECIFIED}. * + * <p> * <pre> * val override = LineBreakConfig.Builder() * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) @@ -57,6 +59,7 @@ public final class LineBreakConfig { * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style. * </pre> * + * <p> * This value is resolved to {@link #HYPHENATION_ENABLED} if this value is used for text * layout/rendering. */ @@ -89,6 +92,7 @@ public final class LineBreakConfig { /** * No line break style is specified. * + * <p> * This is a special value of line break style indicating no style value is specified. * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with * {@link Builder#merge(LineBreakConfig)} function, the line break style of overridden config @@ -107,6 +111,7 @@ public final class LineBreakConfig { * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style. * </pre> * + * <p> * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text * layout/rendering. */ @@ -270,6 +275,8 @@ public final class LineBreakConfig { /** * Resets this builder to the given config state. * + * @param config a config value used for resetting. {@code null} is allowed. If {@code null} + * is passed, all configs are reset to unspecified. * @return This {@code Builder}. * @hide */ diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 31c2eb2efaed..b7ea04fdfe07 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -128,25 +128,6 @@ public class AndroidKeyStoreMaintenance { } /** - * Queries user state from Keystore 2.0. - * - * @param userId - Android user id of the user. - * @return UserState enum variant as integer if successful or an error - */ - public static int getState(int userId) { - StrictMode.noteDiskRead(); - try { - return getService().getState(userId); - } catch (ServiceSpecificException e) { - Log.e(TAG, "getState failed", e); - return e.errorCode; - } catch (Exception e) { - Log.e(TAG, "Can not connect to keystore", e); - return SYSTEM_ERROR; - } - } - - /** * Informs Keystore 2.0 that an off body event was detected. */ public static void onDeviceOffBody() { diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 8045f55f6b4c..11b827117aa3 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -19,8 +19,6 @@ package android.security; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.StrictMode; -import android.os.UserHandle; -import android.security.maintenance.UserState; /** * @hide This should not be made public in its present form because it @@ -37,15 +35,6 @@ public class KeyStore { // Used for UID field to indicate the calling UID. public static final int UID_SELF = -1; - // States - public enum State { - @UnsupportedAppUsage - UNLOCKED, - @UnsupportedAppUsage - LOCKED, - UNINITIALIZED - }; - private static final KeyStore KEY_STORE = new KeyStore(); @UnsupportedAppUsage @@ -55,28 +44,6 @@ public class KeyStore { /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public State state(int userId) { - int userState = AndroidKeyStoreMaintenance.getState(userId); - switch (userState) { - case UserState.UNINITIALIZED: - return KeyStore.State.UNINITIALIZED; - case UserState.LSKF_UNLOCKED: - return KeyStore.State.UNLOCKED; - case UserState.LSKF_LOCKED: - return KeyStore.State.LOCKED; - default: - throw new AssertionError(userState); - } - } - - /** @hide */ - @UnsupportedAppUsage - public State state() { - return state(UserHandle.myUserId()); - } - - /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public byte[] get(String key) { return null; } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 96c257b304a0..1ba41b106f56 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -75,16 +75,18 @@ import javax.security.auth.x500.X500Principal; * {@link java.security.interfaces.ECPublicKey} or {@link java.security.interfaces.RSAPublicKey} * interfaces. * - * <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in - * the Android Keystore. This is because the {@link java.security.KeyStore} abstraction does not - * support storing key pairs without a certificate. The subject, serial number, and validity dates - * of the certificate can be customized in this spec. The self-signed certificate may be replaced at - * a later time by a certificate signed by a Certificate Authority (CA). + * <p>For asymmetric key pairs, a X.509 certificate will be also generated and stored in the Android + * Keystore. This is because the {@link java.security.KeyStore} abstraction does not support storing + * key pairs without a certificate. The subject, serial number, and validity dates of the + * certificate can be customized in this spec. The certificate may be replaced at a later time by a + * certificate signed by a Certificate Authority (CA). * - * <p>NOTE: If a private key is not authorized to sign the self-signed certificate, then the - * certificate will be created with an invalid signature which will not verify. Such a certificate - * is still useful because it provides access to the public key. To generate a valid signature for - * the certificate the key needs to be authorized for all of the following: + * <p>NOTE: If attestation is not requested using {@link Builder#setAttestationChallenge(byte[])}, + * generated certificate may be self-signed. If a private key is not authorized to sign the + * certificate, then the certificate will be created with an invalid signature which will not + * verify. Such a certificate is still useful because it provides access to the public key. To + * generate a valid signature for the certificate the key needs to be authorized for all of the + * following: * <ul> * <li>{@link KeyProperties#PURPOSE_SIGN},</li> * <li>operation without requiring the user to be authenticated (see @@ -989,12 +991,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be * used. Attempts to use the key for any other purpose will be rejected. * - * <p>If the set of purposes for which the key can be used does not contain - * {@link KeyProperties#PURPOSE_SIGN}, the self-signed certificate generated by - * {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an - * invalid signature. This is OK if the certificate is only used for obtaining the - * public key from Android KeyStore. - * * <p>See {@link KeyProperties}.{@code PURPOSE} flags. */ public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) { @@ -1140,7 +1136,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the subject used for the self-signed certificate of the generated key pair. + * Sets the subject used for the certificate of the generated key pair. * * <p>By default, the subject is {@code CN=fake}. */ @@ -1154,7 +1150,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the serial number used for the self-signed certificate of the generated key pair. + * Sets the serial number used for the certificate of the generated key pair. * * <p>By default, the serial number is {@code 1}. */ @@ -1168,8 +1164,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the start of the validity period for the self-signed certificate of the generated - * key pair. + * Sets the start of the validity period for the certificate of the generated key pair. * * <p>By default, this date is {@code Jan 1 1970}. */ @@ -1183,8 +1178,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the end of the validity period for the self-signed certificate of the generated key - * pair. + * Sets the end of the validity period for the certificate of the generated key pair. * * <p>By default, this date is {@code Jan 1 2048}. */ diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml index da8abde8407f..8d2e28b9492f 100644 --- a/libs/WindowManager/Shell/res/values-television/config.xml +++ b/libs/WindowManager/Shell/res/values-television/config.xml @@ -45,13 +45,13 @@ <integer name="config_pipForceCloseDelay">5000</integer> <!-- Animation duration when exit starting window: fade out icon --> - <integer name="starting_window_app_reveal_icon_fade_out_duration">500</integer> + <integer name="starting_window_app_reveal_icon_fade_out_duration">200</integer> <!-- Animation delay when exit starting window: reveal app --> - <integer name="starting_window_app_reveal_anim_delay">0</integer> + <integer name="starting_window_app_reveal_anim_delay">200</integer> <!-- Animation duration when exit starting window: reveal app --> - <integer name="starting_window_app_reveal_anim_duration">500</integer> + <integer name="starting_window_app_reveal_anim_duration">300</integer> <!-- Default animation type when hiding the starting window. The possible values are: - 0 for radial vanish + slide up diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index 06ce37148eaf..8cf869b175ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -87,33 +87,28 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle mTransitions.addHandler(this); } - @Override - public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - boolean containsEmbeddingSplit = false; - boolean containsNonEmbeddedChange = false; - final List<TransitionInfo.Change> changes = info.getChanges(); - for (int i = changes.size() - 1; i >= 0; i--) { - final TransitionInfo.Change change = changes.get(i); - if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { - containsNonEmbeddedChange = true; - } else if (!change.hasFlags(FLAG_FILLS_TASK)) { + /** Whether ActivityEmbeddingController should animate this transition. */ + public boolean shouldAnimate(@NonNull TransitionInfo info) { + boolean containsEmbeddingChange = false; + for (TransitionInfo.Change change : info.getChanges()) { + if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags( + FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { // Whether the Task contains any ActivityEmbedding split before or after the // transition. - containsEmbeddingSplit = true; + containsEmbeddingChange = true; } } - if (!containsEmbeddingSplit) { + if (!containsEmbeddingChange) { // Let the system to play the default animation if there is no ActivityEmbedding split // window. This allows to play the app customized animation when there is no embedding, // such as the device is in a folded state. return false; } - if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) { + + if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) { return false; } + final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); if (options != null // Scene-transition will be handled by app side. @@ -123,6 +118,17 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle return false; } + return true; + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + + if (!shouldAnimate(info)) return false; + // Start ActivityEmbedding animation. mTransitionCallbacks.put(transition, finishCallback); mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction); @@ -136,6 +142,16 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle mAnimationRunner.cancelAnimationFromMerge(); } + /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */ + private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) { + for (TransitionInfo.Change change : info.getChanges()) { + if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + return true; + } + } + return false; + } + private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) { final Rect nonClosingEmbeddedArea = new Rect(); for (int i = changes.size() - 1; i >= 0; i--) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 2241c343a208..ac5ba51ec139 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1784,13 +1784,14 @@ public class BubbleStackView extends FrameLayout mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition); mStackAnimationController.setStackPosition(startPosition); mExpandedAnimationController.setCollapsePoint(startPosition); - // Set the translation x so that this bubble will animate in from the same side they - // expand / collapse on. - bubble.getIconView().setTranslationX(startPosition.x); } else if (firstBubble) { mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); } + // Set the view translation x so that this bubble will animate in from the same side they + // expand / collapse on. + bubble.getIconView().setTranslationX(mStackAnimationController.getStackPosition().x); + mBubbleContainer.addView(bubble.getIconView(), 0, new FrameLayout.LayoutParams(mPositioner.getBubbleSize(), mPositioner.getBubbleSize())); 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 c51af46accdb..ea7b2e92fefb 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 @@ -605,6 +605,7 @@ public abstract class WMShellBaseModule { @Provides static Transitions provideTransitions(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, ShellTaskOrganizer organizer, TransactionPool pool, @@ -612,14 +613,13 @@ public abstract class WMShellBaseModule { @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler, @ShellAnimationThread ShellExecutor animExecutor, - ShellCommandHandler shellCommandHandler, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) { // TODO(b/238217847): Force override shell init if registration is disabled shellInit = new ShellInit(mainExecutor); } - return new Transitions(context, shellInit, shellController, organizer, pool, - displayController, mainExecutor, mainHandler, animExecutor, shellCommandHandler, + return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer, + pool, displayController, mainExecutor, mainHandler, animExecutor, rootTaskDisplayAreaOrganizer); } 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 14a040a40874..a533ca5fa8fb 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 @@ -32,6 +32,7 @@ import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; @@ -366,11 +367,12 @@ public abstract class WMShellModule { KeyguardTransitionHandler keyguardTransitionHandler, Optional<DesktopTasksController> desktopTasksController, Optional<UnfoldTransitionHandler> unfoldHandler, + Optional<ActivityEmbeddingController> activityEmbeddingController, Transitions transitions) { return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional, pipTransitionController, recentsTransitionHandler, keyguardTransitionHandler, desktopTasksController, - unfoldHandler); + unfoldHandler, activityEmbeddingController); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS new file mode 100644 index 000000000000..74a29ddad073 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS @@ -0,0 +1 @@ +hwwang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 8a6403705c1c..1898ea737729 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -17,19 +17,28 @@ package com.android.wm.shell.dagger.pip; import android.annotation.NonNull; +import android.content.Context; 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.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.pip2.phone.PipController; import com.android.wm.shell.pip2.phone.PipTransition; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import dagger.Module; import dagger.Provides; +import java.util.Optional; + /** * Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be * the successor of its sibling {@link Pip1Module}. @@ -42,8 +51,26 @@ public abstract class Pip2Module { @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, - PipBoundsAlgorithm pipBoundsAlgorithm) { + PipBoundsAlgorithm pipBoundsAlgorithm, + Optional<PipController> pipController) { return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, pipBoundsAlgorithm); } + + @WMSingleton + @Provides + static Optional<PipController> providePipController(Context context, + ShellInit shellInit, + ShellController shellController, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + PipDisplayLayoutState pipDisplayLayoutState) { + if (!PipUtils.isPip2ExperimentEnabled()) { + return Optional.empty(); + } else { + return Optional.ofNullable(PipController.create( + context, shellInit, shellController, displayController, displayInsetsController, + pipDisplayLayoutState)); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java new file mode 100644 index 000000000000..186cb615f4ec --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -0,0 +1,133 @@ +/* + * 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.wm.shell.pip2.phone; + +import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; + +import android.content.Context; +import android.content.res.Configuration; +import android.view.InsetsState; + +import com.android.internal.protolog.common.ProtoLog; +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.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; + +/** + * Manages the picture-in-picture (PIP) UI and states for Phones. + */ +public class PipController implements ConfigurationChangeListener, + DisplayController.OnDisplaysChangedListener { + private static final String TAG = PipController.class.getSimpleName(); + + private Context mContext; + private ShellController mShellController; + private DisplayController mDisplayController; + private DisplayInsetsController mDisplayInsetsController; + private PipDisplayLayoutState mPipDisplayLayoutState; + + private PipController(Context context, + ShellInit shellInit, + ShellController shellController, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + PipDisplayLayoutState pipDisplayLayoutState) { + mContext = context; + mShellController = shellController; + mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; + mPipDisplayLayoutState = pipDisplayLayoutState; + + if (PipUtils.isPip2ExperimentEnabled()) { + shellInit.addInitCallback(this::onInit, this); + } + } + + private void onInit() { + // Ensure that we have the display info in case we get calls to update the bounds before the + // listener calls back + mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId()); + DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay()); + mPipDisplayLayoutState.setDisplayLayout(layout); + + mShellController.addConfigurationChangeListener(this); + mDisplayController.addDisplayWindowListener(this); + mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(), + new DisplayInsetsController.OnInsetsChangedListener() { + @Override + public void insetsChanged(InsetsState insetsState) { + onDisplayChanged(mDisplayController + .getDisplayLayout(mPipDisplayLayoutState.getDisplayId())); + } + }); + } + + /** + * Instantiates {@link PipController}, returns {@code null} if the feature not supported. + */ + public static PipController create(Context context, + ShellInit shellInit, + ShellController shellController, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + PipDisplayLayoutState pipDisplayLayoutState) { + if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Device doesn't support Pip feature", TAG); + return null; + } + return new PipController(context, shellInit, shellController, displayController, + displayInsetsController, pipDisplayLayoutState); + } + + + @Override + public void onConfigurationChanged(Configuration newConfiguration) { + mPipDisplayLayoutState.onConfigurationChanged(); + } + + @Override + public void onThemeChanged() { + onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay())); + } + + @Override + public void onDisplayAdded(int displayId) { + if (displayId != mPipDisplayLayoutState.getDisplayId()) { + return; + } + onDisplayChanged(mDisplayController.getDisplayLayout(displayId)); + } + + @Override + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + if (displayId != mPipDisplayLayoutState.getDisplayId()) { + return; + } + onDisplayChanged(mDisplayController.getDisplayLayout(displayId)); + } + + private void onDisplayChanged(DisplayLayout layout) { + mPipDisplayLayoutState.setDisplayLayout(layout); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index d31476c63890..d277eef761e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -925,19 +925,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { if (toHome) wct.reorder(mRecentsTask, true /* toTop */); else wct.restoreTransientOrder(mRecentsTask); } - if (!toHome - // If a recents gesture starts on the 3p launcher, then the 3p launcher is the - // live tile (pausing app). If the gesture is "cancelled" we need to return to - // 3p launcher instead of "task-switching" away from it. - && (!mWillFinishToHome || mPausingSeparateHome) - && mPausingTasks != null && mState == STATE_NORMAL) { - if (mPausingSeparateHome) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - " returning to 3p home"); - } else { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - " returning to app"); - } + if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app"); // The gesture is returning to the pausing-task(s) rather than continuing with // recents, so end the transition by moving the app back to the top (and also // re-showing it's task). @@ -969,6 +958,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { wct.restoreTransientOrder(mRecentsTask); } } else { + if (mPausingSeparateHome) { + if (mOpeningTasks.isEmpty()) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " recents occluded 3p home"); + } else { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " switch task by recents on 3p home"); + } + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish"); // The general case: committing to recents, going home, or switching tasks. for (int i = 0; i < mOpeningTasks.size(); ++i) { 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 68ca2313f709..7a4834cb5adb 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 @@ -23,6 +23,7 @@ import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVIT import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; @@ -395,6 +396,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mMainStage.isActive(); } + /** @return whether this transition-request has the launch-adjacent flag. */ + public boolean requestHasLaunchAdjacentFlag(TransitionRequestInfo request) { + final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); + return triggerTask != null && triggerTask.baseIntent != null + && (triggerTask.baseIntent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0; + } + /** @return whether the transition-request implies entering pip from split. */ public boolean requestImpliesSplitToPip(TransitionRequestInfo request) { if (!isSplitActive() || !mMixedHandler.requestHasPipEnter(request)) { @@ -2459,10 +2467,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); } - // When split in the background, it should be only opening/dismissing transition and - // would keep out not empty. Prevent intercepting all transitions for split screen when - // it is in the background and not identify to handle it. - return (!out.isEmpty() || isSplitScreenVisible()) ? out : null; + if (!out.isEmpty()) { + // One of the cases above handled it + return out; + } else if (isSplitScreenVisible()) { + // If split is visible, only defer handling this transition if it's launching + // adjacent while there is already a split pair -- this may trigger PIP and + // that should be handled by the mixed handler. + final boolean deferTransition = requestHasLaunchAdjacentFlag(request) + && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0; + return !deferTransition ? out : null; + } + // Don't intercept the transition if we are not handling it as a part of one of the + // cases above and it is not already visible + return null; } else { if (isOpening && getStageOfTask(triggerTask) != null) { // One task is appearing into split, prepare to enter split screen. 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 451e61855943..918a5a4bd53e 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 @@ -21,7 +21,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; @@ -43,6 +45,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; @@ -74,6 +77,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, private final KeyguardTransitionHandler mKeyguardHandler; private DesktopTasksController mDesktopTasksController; private UnfoldTransitionHandler mUnfoldHandler; + private ActivityEmbeddingController mActivityEmbeddingController; private static class MixedTransition { static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; @@ -93,9 +97,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, /** Recents Transition while in desktop mode. */ static final int TYPE_RECENTS_DURING_DESKTOP = 6; - /** Fuld/Unfold transition. */ + /** Fold/Unfold transition. */ static final int TYPE_UNFOLD = 7; + /** Enter pip from one of the Activity Embedding windows. */ + static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 8; + /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -150,7 +157,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, Optional<RecentsTransitionHandler> recentsHandlerOptional, KeyguardTransitionHandler keyguardHandler, Optional<DesktopTasksController> desktopTasksControllerOptional, - Optional<UnfoldTransitionHandler> unfoldHandler) { + Optional<UnfoldTransitionHandler> unfoldHandler, + Optional<ActivityEmbeddingController> activityEmbeddingController) { mPlayer = player; mKeyguardHandler = keyguardHandler; if (Transitions.ENABLE_SHELL_TRANSITIONS @@ -170,6 +178,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } mDesktopTasksController = desktopTasksControllerOptional.orElse(null); mUnfoldHandler = unfoldHandler.orElse(null); + mActivityEmbeddingController = activityEmbeddingController.orElse(null); }, this); } } @@ -192,6 +201,16 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mPipHandler.augmentRequest(transition, request, out); mSplitHandler.addEnterOrExitIfNeeded(request, out); return out; + } else if (request.getType() == TRANSIT_PIP + && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && ( + mActivityEmbeddingController != null)) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + " Got a PiP-enter request from an Activity Embedding split"); + mActiveTransitions.add(new MixedTransition( + MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition)); + // Postpone transition splitting to later. + WindowContainerTransaction out = new WindowContainerTransaction(); + return out; } else if (request.getRemoteTransition() != null && TransitionUtil.isOpeningType(request.getType()) && (request.getTriggerTask() == null @@ -355,6 +374,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { + return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction, + finishTransaction, finishCallback); } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { return false; } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { @@ -400,6 +422,58 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } } + private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + + "entering PIP from an Activity Embedding window"); + // Split into two transitions (wct) + TransitionInfo.Change pipChange = null; + final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (mPipHandler.isEnteringPip(change, info.getType())) { + if (pipChange != null) { + throw new IllegalStateException("More than 1 pip-entering changes in one" + + " transition? " + info); + } + pipChange = change; + // going backwards, so remove-by-index is fine. + everythingElse.getChanges().remove(i); + } + } + + final Transitions.TransitionFinishCallback finishCB = (wct) -> { + --mixed.mInFlightSubAnimations; + mixed.joinFinishArgs(wct); + if (mixed.mInFlightSubAnimations > 0) return; + mActiveTransitions.remove(mixed); + finishCallback.onTransitionFinished(mixed.mFinishWCT); + }; + + if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) { + // Fallback to dispatching to other handlers. + return false; + } + + // PIP window should always be on the highest Z order. + if (pipChange != null) { + mixed.mInFlightSubAnimations = 2; + mPipHandler.startEnterAnimation( + pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE), + finishTransaction, + finishCB); + } else { + mixed.mInFlightSubAnimations = 1; + } + + mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse, + startTransaction, finishTransaction, finishCB); + return true; + } + private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -811,6 +885,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } else { mPipHandler.end(); } + } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { + mPipHandler.end(); + mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget, + finishCallback); } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { mPipHandler.end(); if (mixed.mLeftoversHandler != null) { @@ -851,6 +929,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (mixed == null) return; if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { mPipHandler.onTransitionConsumed(transition, aborted, finishT); + } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { + mPipHandler.onTransitionConsumed(transition, aborted, finishT); + mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { 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 8b050e524038..b1fc16ddf19b 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 @@ -63,7 +63,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { @NonNull Transitions.TransitionFinishCallback finishCallback) { if (mTransition != transition) return false; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote" - + " transition %s for #%d.", mRemote, info.getDebugId()); + + " transition %s for (#%d).", mRemote, info.getDebugId()); final IBinder.DeathRecipient remoteDied = () -> { Log.e(Transitions.TAG, "Remote transition died, finishing"); 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 592b22a47bc4..ca2c3b4fbff1 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 @@ -126,7 +126,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } } } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for #%d to %s", + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for (#%d) to %s", info.getDebugId(), pendingRemote); if (pendingRemote == null) return false; @@ -241,7 +241,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { if (remote == null) return null; mRequestedRemotes.put(transition, remote); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested" - + " for %s: %s", transition, remote); + + " for (#%d) %s: %s", request.getDebugId(), transition, remote); return new WindowContainerTransaction(); } 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 0d9a9e9f07ff..576bba96044c 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 @@ -179,6 +179,7 @@ public class Transitions implements RemoteCallable<Transitions>, private final DefaultTransitionHandler mDefaultTransitionHandler; private final RemoteTransitionHandler mRemoteTransitionHandler; private final DisplayController mDisplayController; + private final ShellCommandHandler mShellCommandHandler; private final ShellController mShellController; private final ShellTransitionImpl mImpl = new ShellTransitionImpl(); private final SleepHandler mSleepHandler = new SleepHandler(); @@ -188,9 +189,6 @@ public class Transitions implements RemoteCallable<Transitions>, /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */ private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>(); - @Nullable - private final ShellCommandHandler mShellCommandHandler; - private final ArrayList<TransitionObserver> mObservers = new ArrayList<>(); /** List of {@link Runnable} instances to run when the last active transition has finished. */ @@ -237,7 +235,7 @@ public class Transitions implements RemoteCallable<Transitions>, @Override public String toString() { if (mInfo != null && mInfo.getDebugId() >= 0) { - return "(#" + mInfo.getDebugId() + ")" + mToken + "@" + getTrack(); + return "(#" + mInfo.getDebugId() + ") " + mToken + "@" + getTrack(); } return mToken.toString() + "@" + getTrack(); } @@ -275,13 +273,14 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor) { - this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor, - mainHandler, animExecutor, null, + this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool, + displayController, mainExecutor, mainHandler, animExecutor, new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit)); } public Transitions(@NonNull Context context, @NonNull ShellInit shellInit, + @Nullable ShellCommandHandler shellCommandHandler, @NonNull ShellController shellController, @NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @@ -289,7 +288,6 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor, - @Nullable ShellCommandHandler shellCommandHandler, @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) { mOrganizer = organizer; mContext = context; @@ -300,13 +298,13 @@ public class Transitions implements RemoteCallable<Transitions>, mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit, displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer); mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor); + mShellCommandHandler = shellCommandHandler; mShellController = shellController; // The very last handler (0 in the list) should be the default one. mHandlers.add(mDefaultTransitionHandler); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default"); // Next lowest priority is remote transitions. mHandlers.add(mRemoteTransitionHandler); - mShellCommandHandler = shellCommandHandler; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote"); shellInit.addInitCallback(this::onInit, this); } @@ -339,9 +337,8 @@ public class Transitions implements RemoteCallable<Transitions>, TransitionMetrics.getInstance(); } - if (mShellCommandHandler != null) { - mShellCommandHandler.addCommandCallback("transitions", this, this); - } + mShellCommandHandler.addCommandCallback("transitions", this, this); + mShellCommandHandler.addDumpCallback(this::dump, this); } public boolean isRegistered() { @@ -655,8 +652,8 @@ public class Transitions implements RemoteCallable<Transitions>, void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady"); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", - transitionToken, info); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s", + info.getDebugId(), transitionToken, info); final int activeIdx = findByToken(mPendingTransitions, transitionToken); if (activeIdx < 0) { throw new IllegalStateException("Got transitionReady for non-pending transition " @@ -1073,8 +1070,8 @@ public class Transitions implements RemoteCallable<Transitions>, void requestStartTransition(@NonNull IBinder transitionToken, @Nullable TransitionRequestInfo request) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s", - transitionToken, request); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s", + request.getDebugId(), transitionToken, request); if (isTransitionKnown(transitionToken)) { throw new RuntimeException("Transition already started " + transitionToken); } @@ -1475,4 +1472,68 @@ public class Transitions implements RemoteCallable<Transitions>, pw.println(prefix + "tracing"); mTracer.printShellCommandHelp(pw, prefix + " "); } + + private void dump(@NonNull PrintWriter pw, String prefix) { + pw.println(prefix + TAG); + + final String innerPrefix = prefix + " "; + pw.println(prefix + "Handlers:"); + for (TransitionHandler handler : mHandlers) { + pw.print(innerPrefix); + pw.print(handler.getClass().getSimpleName()); + pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")"); + } + + pw.println(prefix + "Observers:"); + for (TransitionObserver observer : mObservers) { + pw.print(innerPrefix); + pw.println(observer.getClass().getSimpleName()); + } + + pw.println(prefix + "Pending Transitions:"); + for (ActiveTransition transition : mPendingTransitions) { + pw.print(innerPrefix + "token="); + pw.println(transition.mToken); + pw.print(innerPrefix + "id="); + pw.println(transition.mInfo != null + ? transition.mInfo.getDebugId() + : -1); + pw.print(innerPrefix + "handler="); + pw.println(transition.mHandler != null + ? transition.mHandler.getClass().getSimpleName() + : null); + } + if (mPendingTransitions.isEmpty()) { + pw.println(innerPrefix + "none"); + } + + pw.println(prefix + "Ready-during-sync Transitions:"); + for (ActiveTransition transition : mReadyDuringSync) { + pw.print(innerPrefix + "token="); + pw.println(transition.mToken); + pw.print(innerPrefix + "id="); + pw.println(transition.mInfo != null + ? transition.mInfo.getDebugId() + : -1); + pw.print(innerPrefix + "handler="); + pw.println(transition.mHandler != null + ? transition.mHandler.getClass().getSimpleName() + : null); + } + if (mReadyDuringSync.isEmpty()) { + pw.println(innerPrefix + "none"); + } + + pw.println(prefix + "Tracks:"); + for (int i = 0; i < mTracks.size(); i++) { + final ActiveTransition transition = mTracks.get(i).mActiveTransition; + pw.println(innerPrefix + "Track #" + i); + pw.print(innerPrefix + "active="); + pw.println(transition); + if (transition != null) { + pw.print(innerPrefix + "hander="); + pw.println(transition.mHandler); + } + } + } } 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 0548a8e751cc..d0e647b30a96 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 @@ -297,7 +297,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } // Task surface itself - float shadowRadius = loadDimension(resources, params.mShadowRadiusId); + float shadowRadius; final Point taskPosition = mTaskInfo.positionInParent; if (isFullscreen) { // Setting the task crop to the width/height stops input events from being sent to @@ -308,9 +308,12 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // drag-resized by the window decoration. startT.setWindowCrop(mTaskSurface, null); finishT.setWindowCrop(mTaskSurface, null); + // Shadow is not needed for fullscreen tasks + shadowRadius = 0; } else { startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); + shadowRadius = loadDimension(resources, params.mShadowRadiusId); } startT.setShadowRadius(mTaskSurface, shadowRadius) .show(mTaskSurface); diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 7eb0c76fbf4e..4a5b4f2de20f 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -74,8 +74,7 @@ interface IMediaRouterService { // Methods for MediaRouter2Manager List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager); - RoutingSessionInfo getSystemSessionInfoForPackage( - IMediaRouter2Manager manager, String packageName); + RoutingSessionInfo getSystemSessionInfoForPackage(String packageName); void registerManager(IMediaRouter2Manager manager, String packageName); void unregisterManager(IMediaRouter2Manager manager); void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 21690904fe42..159427bc2796 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -2059,9 +2059,7 @@ public final class MediaRouter2 { public RoutingSessionInfo getSystemSessionInfo() { RoutingSessionInfo result; try { - result = - mMediaRouterService.getSystemSessionInfoForPackage( - mClient, mClientPackageName); + result = mMediaRouterService.getSystemSessionInfoForPackage(mClientPackageName); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 3abfc6297b0b..830708cb38b2 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -377,7 +377,7 @@ public final class MediaRouter2Manager { @Nullable public RoutingSessionInfo getSystemRoutingSession(@Nullable String packageName) { try { - return mMediaRouterService.getSystemSessionInfoForPackage(mClient, packageName); + return mMediaRouterService.getSystemSessionInfoForPackage(packageName); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/media/java/android/media/RingtoneV1.java b/media/java/android/media/RingtoneV1.java index b761afaeaa67..3c54d4a0d166 100644 --- a/media/java/android/media/RingtoneV1.java +++ b/media/java/android/media/RingtoneV1.java @@ -16,14 +16,15 @@ package android.media; -import android.annotation.NonNull; import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.Resources.NotFoundException; import android.media.audiofx.HapticGenerator; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.RemoteException; import android.os.Trace; import android.os.VibrationEffect; @@ -61,7 +62,6 @@ class RingtoneV1 implements Ringtone.ApiInterface { private final Context mContext; private final AudioManager mAudioManager; - private final Ringtone.Injectables mInjectables; private VolumeShaper.Configuration mVolumeShaperConfig; private VolumeShaper mVolumeShaper; @@ -74,10 +74,12 @@ class RingtoneV1 implements Ringtone.ApiInterface { private final IRingtonePlayer mRemotePlayer; private final Binder mRemoteToken; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private MediaPlayer mLocalPlayer; private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener(); private HapticGenerator mHapticGenerator; + @UnsupportedAppUsage private Uri mUri; private String mTitle; @@ -92,15 +94,10 @@ class RingtoneV1 implements Ringtone.ApiInterface { private boolean mHapticGeneratorEnabled = false; private final Object mPlaybackSettingsLock = new Object(); - /** @hide */ + /** {@hide} */ + @UnsupportedAppUsage public RingtoneV1(Context context, boolean allowRemote) { - this(context, new Ringtone.Injectables(), allowRemote); - } - - /** @hide */ - RingtoneV1(Context context, @NonNull Ringtone.Injectables injectables, boolean allowRemote) { mContext = context; - mInjectables = injectables; mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mAllowRemote = allowRemote; mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null; @@ -203,7 +200,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { } destroyLocalPlayer(); // try opening uri locally before delegating to remote player - mLocalPlayer = mInjectables.newMediaPlayer(); + mLocalPlayer = new MediaPlayer(); try { mLocalPlayer.setDataSource(mContext, mUri); mLocalPlayer.setAudioAttributes(mAudioAttributes); @@ -243,7 +240,19 @@ class RingtoneV1 implements Ringtone.ApiInterface { */ public boolean hasHapticChannels() { // FIXME: support remote player, or internalize haptic channels support and remove entirely. - return mInjectables.hasHapticChannels(mLocalPlayer); + try { + android.os.Trace.beginSection("Ringtone.hasHapticChannels"); + if (mLocalPlayer != null) { + for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) { + if (trackInfo.hasHapticChannels()) { + return true; + } + } + } + } finally { + android.os.Trace.endSection(); + } + return false; } /** @@ -325,7 +334,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { * @see android.media.audiofx.HapticGenerator#isAvailable() */ public boolean setHapticGeneratorEnabled(boolean enabled) { - if (!mInjectables.isHapticGeneratorAvailable()) { + if (!HapticGenerator.isAvailable()) { return false; } synchronized (mPlaybackSettingsLock) { @@ -353,7 +362,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { mLocalPlayer.setVolume(mVolume); mLocalPlayer.setLooping(mIsLooping); if (mHapticGenerator == null && mHapticGeneratorEnabled) { - mHapticGenerator = mInjectables.createHapticGenerator(mLocalPlayer); + mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId()); } if (mHapticGenerator != null) { mHapticGenerator.setEnabled(mHapticGeneratorEnabled); @@ -388,6 +397,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { * * @hide */ + @UnsupportedAppUsage public void setUri(Uri uri) { setUri(uri, null); } @@ -415,6 +425,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { } /** {@hide} */ + @UnsupportedAppUsage public Uri getUri() { return mUri; } @@ -545,7 +556,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { Log.e(TAG, "Could not load fallback ringtone"); return false; } - mLocalPlayer = mInjectables.newMediaPlayer(); + mLocalPlayer = new MediaPlayer(); if (afd.getDeclaredLength() < 0) { mLocalPlayer.setDataSource(afd.getFileDescriptor()); } else { @@ -583,12 +594,12 @@ class RingtoneV1 implements Ringtone.ApiInterface { } public boolean isLocalOnly() { - return !mAllowRemote; + return mAllowRemote; } public boolean isUsingRemotePlayer() { // V2 testing api, but this is the v1 approximation. - return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null) && (mUri != null); + return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null); } class MyOnCompletionListener implements MediaPlayer.OnCompletionListener { diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java index bbbe7f683b05..c54bfce840aa 100644 --- a/media/java/android/media/midi/MidiUmpDeviceService.java +++ b/media/java/android/media/midi/MidiUmpDeviceService.java @@ -16,6 +16,9 @@ package android.media.midi; +import static com.android.media.midi.flags.Flags.FLAG_VIRTUAL_UMP; + +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Service; @@ -54,9 +57,11 @@ import java.util.List; * android:resource="@xml/device_info" /> * </service></pre> */ +@FlaggedApi(FLAG_VIRTUAL_UMP) public abstract class MidiUmpDeviceService extends Service { private static final String TAG = "MidiUmpDeviceService"; + @FlaggedApi(FLAG_VIRTUAL_UMP) public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService"; private IMidiManager mMidiManager; @@ -75,6 +80,7 @@ public abstract class MidiUmpDeviceService extends Service { } }; + @FlaggedApi(FLAG_VIRTUAL_UMP) @Override public void onCreate() { mMidiManager = IMidiManager.Stub.asInterface( @@ -112,6 +118,7 @@ public abstract class MidiUmpDeviceService extends Service { * The number of input and output ports must be equal and non-zero. * @return list of MidiReceivers */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public abstract @NonNull List<MidiReceiver> onGetInputPortReceivers(); /** @@ -120,6 +127,7 @@ public abstract class MidiUmpDeviceService extends Service { * The number of input and output ports must be equal and non-zero. * @return the list of MidiReceivers */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public final @NonNull List<MidiReceiver> getOutputPortReceivers() { if (mServer == null) { return new ArrayList<MidiReceiver>(); @@ -132,6 +140,7 @@ public abstract class MidiUmpDeviceService extends Service { * Returns the {@link MidiDeviceInfo} instance for this service * @return the MidiDeviceInfo of the virtual MIDI device if it was successfully created */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public final @Nullable MidiDeviceInfo getDeviceInfo() { return mDeviceInfo; } @@ -140,6 +149,7 @@ public abstract class MidiUmpDeviceService extends Service { * Called to notify when the {@link MidiDeviceStatus} has changed * @param status the current status of the MIDI device */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public void onDeviceStatusChanged(@NonNull MidiDeviceStatus status) { } @@ -147,9 +157,11 @@ public abstract class MidiUmpDeviceService extends Service { * Called to notify when the virtual MIDI device running in this service has been closed by * all its clients */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public void onClose() { } + @FlaggedApi(FLAG_VIRTUAL_UMP) @Override public @Nullable IBinder onBind(@NonNull Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) { diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index 80e22477efed..31e65eb13926 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -175,5 +175,5 @@ interface IMediaProjectionManager { @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); + oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource); } diff --git a/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java index 2c8daba86d19..3c0c6847f557 100644 --- a/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java @@ -14,22 +14,20 @@ * limitations under the License. */ -package com.android.media; +package com.android.mediaframeworktest.unit; import static android.media.Ringtone.MEDIA_SOUND; import static android.media.Ringtone.MEDIA_SOUND_AND_VIBRATION; import static android.media.Ringtone.MEDIA_VIBRATION; -import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerFallbackSetup; -import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerSetup; -import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStarted; -import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStopped; - import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; @@ -55,29 +53,34 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.testing.TestableContext; +import android.util.ArrayMap; +import android.util.ArraySet; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import com.android.framework.base.media.ringtone.tests.R; -import com.android.media.testing.RingtoneInjectablesTrackingTestRule; +import com.android.mediaframeworktest.R; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.Description; import org.junit.runner.RunWith; +import org.junit.runners.model.Statement; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.io.FileNotFoundException; +import java.util.ArrayDeque; +import java.util.Map; +import java.util.Queue; -/** - * Test behavior of {@link Ringtone} when it's created via {@link Ringtone.Builder}. - */ @RunWith(AndroidJUnit4.class) -public class RingtoneBuilderTest { +public class RingtoneTest { private static final Uri SOUND_URI = Uri.parse("content://fake-sound-uri"); @@ -90,8 +93,11 @@ public class RingtoneBuilderTest { private static final VibrationEffect VIBRATION_EFFECT = VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100}, -1); + private static final VibrationEffect VIBRATION_EFFECT_REPEATING = + VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100, 50}, 1); - @Rule public final RingtoneInjectablesTrackingTestRule + @Rule + public final RingtoneInjectablesTrackingTestRule mMediaPlayerRule = new RingtoneInjectablesTrackingTestRule(); @Captor private ArgumentCaptor<IBinder> mIBinderCaptor; @@ -116,7 +122,6 @@ public class RingtoneBuilderTest { mContext = spy(testContext); } - @Test public void testRingtone_fullLifecycleUsingLocalMediaPlayer() throws Exception { MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer(); @@ -137,14 +142,14 @@ public class RingtoneBuilderTest { assertThat(ringtone.isLocalOnly()).isFalse(); // Prepare - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES); verify(mockMediaPlayer).setVolume(1.0f); verify(mockMediaPlayer).setLooping(false); verify(mockMediaPlayer).prepare(); // Play ringtone.play(); - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); // Verify dynamic controls. ringtone.setVolume(0.8f); @@ -160,7 +165,7 @@ public class RingtoneBuilderTest { // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); // This test is intended to strictly verify all interactions with MediaPlayer in a local // playback case. This shouldn't be necessary in other tests that have the same basic @@ -194,16 +199,16 @@ public class RingtoneBuilderTest { assertThat(ringtone.getAudioAttributes()).isEqualTo(audioAttributes); // Prepare - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, audioAttributes); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, audioAttributes); verify(mockMediaPlayer).prepare(); // Play ringtone.play(); - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); verifyZeroInteractions(mMockRemotePlayer); verifyZeroInteractions(mMockVibrator); @@ -215,8 +220,8 @@ public class RingtoneBuilderTest { setupFileNotFound(mockMediaPlayer, SOUND_URI); Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES) - .setUri(SOUND_URI) - .build(); + .setUri(SOUND_URI) + .build(); assertThat(ringtone).isNotNull(); assertThat(ringtone.isUsingRemotePlayer()).isTrue(); @@ -279,7 +284,7 @@ public class RingtoneBuilderTest { // Prepare // Uses attributes with haptic channels enabled, but will use the effect when there aren't // any present. - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); verify(mockMediaPlayer).setVolume(1.0f); verify(mockMediaPlayer).setLooping(false); verify(mockMediaPlayer).prepare(); @@ -287,7 +292,7 @@ public class RingtoneBuilderTest { // Play ringtone.play(); - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES); // Verify dynamic controls. @@ -305,7 +310,7 @@ public class RingtoneBuilderTest { // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE); // This test is intended to strictly verify all interactions with MediaPlayer in a local @@ -383,7 +388,7 @@ public class RingtoneBuilderTest { // Prepare // Uses attributes with haptic channels enabled, but will abandon the MediaPlayer when it // knows there aren't any. - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted. verify(mockMediaPlayer).setLooping(false); verify(mockMediaPlayer).prepare(); @@ -438,7 +443,7 @@ public class RingtoneBuilderTest { // Prepare // Uses attributes with haptic channels enabled, but will use the effect when there aren't // any present. - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted. verify(mockMediaPlayer).setLooping(false); verify(mockMediaPlayer).prepare(); @@ -446,7 +451,7 @@ public class RingtoneBuilderTest { // Play ringtone.play(); // Vibrator.vibrate isn't called because the vibration comes from the sound. - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); // Verify dynamic controls (no-op without sound) ringtone.setVolume(0.8f); @@ -461,7 +466,7 @@ public class RingtoneBuilderTest { // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); // This test is intended to strictly verify all interactions with MediaPlayer in a local // playback case. This shouldn't be necessary in other tests that have the same basic @@ -491,17 +496,17 @@ public class RingtoneBuilderTest { // Prepare // The attributes here have haptic channels enabled (unlike above) - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); verify(mockMediaPlayer).prepare(); // Play ringtone.play(); when(mockMediaPlayer.isPlaying()).thenReturn(true); - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); verifyZeroInteractions(mMockRemotePlayer); // Nothing after the initial hasVibrator - it uses audio-coupled. @@ -531,7 +536,7 @@ public class RingtoneBuilderTest { // Prepare // The attributes here have haptic channels enabled (unlike above) - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); verify(mockMediaPlayer).prepare(); // Play @@ -554,7 +559,7 @@ public class RingtoneBuilderTest { @Test public void testRingtone_nullMediaOnBuilderUsesFallback() throws Exception { AssetFileDescriptor testResourceFd = - mContext.getResources().openRawResourceFd(R.raw.test_sound_file); + mContext.getResources().openRawResourceFd(R.raw.shortmp3); // Ensure it will flow as expected. assertThat(testResourceFd).isNotNull(); assertThat(testResourceFd.getDeclaredLength()).isAtLeast(0); @@ -570,18 +575,18 @@ public class RingtoneBuilderTest { // Delegates straight to fallback in local player. // Prepare - verifyPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES); + verifyLocalPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES); verify(mockMediaPlayer).setVolume(1.0f); verify(mockMediaPlayer).setLooping(false); verify(mockMediaPlayer).prepare(); // Play ringtone.play(); - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); verifyNoMoreInteractions(mockMediaPlayer); verifyNoMoreInteractions(mMockRemotePlayer); @@ -610,10 +615,24 @@ public class RingtoneBuilderTest { verifyNoMoreInteractions(mMockRemotePlayer); } + @Test + public void testRingtone_noMediaSetOnBuilderFallbackFailsAndNoRemote() throws Exception { + mContext.getOrCreateTestableResources() + .addOverride(com.android.internal.R.raw.fallbackring, null); + Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES) + .setUri(null) + .setLocalOnly() + .build(); + // Local player fallback fails as the resource isn't found (no media player creation is + // attempted), and since there is no local player, the ringtone ends up having nothing to + // do. + assertThat(ringtone).isNull(); + } + private Ringtone.Builder newBuilder(@Ringtone.RingtoneMedia int ringtoneMedia, AudioAttributes audioAttributes) { return new Ringtone.Builder(mContext, ringtoneMedia, audioAttributes) - .setInjectables(mMediaPlayerRule.getRingtoneTestInjectables()); + .setInjectables(mMediaPlayerRule.injectables); } private static AudioAttributes audioAttributes(int audioUsage) { @@ -628,4 +647,194 @@ public class RingtoneBuilderTest { doThrow(new FileNotFoundException("Fake file not found")) .when(mockMediaPlayer).setDataSource(any(Context.class), eq(uri)); } + + private void verifyLocalPlayerSetup(MediaPlayer mockPlayer, Uri expectedUri, + AudioAttributes expectedAudioAttributes) throws Exception { + verify(mockPlayer).setDataSource(mContext, expectedUri); + verify(mockPlayer).setAudioAttributes(expectedAudioAttributes); + verify(mockPlayer).setPreferredDevice(null); + verify(mockPlayer).prepare(); + } + + private void verifyLocalPlayerFallbackSetup(MediaPlayer mockPlayer, AssetFileDescriptor afd, + AudioAttributes expectedAudioAttributes) throws Exception { + // This is very specific but it's a simple way to test that the test resource matches. + if (afd.getDeclaredLength() < 0) { + verify(mockPlayer).setDataSource(afd.getFileDescriptor()); + } else { + verify(mockPlayer).setDataSource(afd.getFileDescriptor(), + afd.getStartOffset(), + afd.getDeclaredLength()); + } + verify(mockPlayer).setAudioAttributes(expectedAudioAttributes); + verify(mockPlayer).setPreferredDevice(null); + verify(mockPlayer).prepare(); + } + + private void verifyLocalPlay(MediaPlayer mockMediaPlayer) { + verify(mockMediaPlayer).setOnCompletionListener(any()); + verify(mockMediaPlayer).start(); + } + + private void verifyLocalStop(MediaPlayer mockMediaPlayer) { + verify(mockMediaPlayer).stop(); + verify(mockMediaPlayer).setOnCompletionListener(isNull()); + verify(mockMediaPlayer).reset(); + verify(mockMediaPlayer).release(); + } + + /** + * This rule ensures that all expected media player creations from the factory do actually + * occur. The reason for this level of control is that creating a media player is fairly + * expensive and blocking, so we do want unit tests of this class to "declare" interactions + * of all created media players. + * + * This needs to be a TestRule so that the teardown assertions can be skipped if the test has + * failed (and media player assertions may just be a distracting side effect). Otherwise, the + * teardown failures hide the real test ones. + */ + public static class RingtoneInjectablesTrackingTestRule implements TestRule { + public Ringtone.Injectables injectables = new TestInjectables(); + public boolean hapticGeneratorAvailable = true; + + // Queue of (local) media players, in order of expected creation. Enqueue using + // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone. + // This queue is asserted to be empty at the end of the test. + private Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>(); + + // Similar to media players, but for haptic generator, which also needs releasing. + private Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>(); + + // Media players with haptic channels. + private ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>(); + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + base.evaluate(); + // Only assert if the test didn't fail (base.evaluate() would throw). + assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed") + .that(mMockMediaPlayerQueue).isEmpty(); + // Only assert if the test didn't fail (base.evaluate() would throw). + assertWithMessage( + "Test setup an expectLocalHapticGenerator but it wasn't consumed") + .that(mMockHapticGeneratorMap).isEmpty(); + } + }; + } + + private TestMediaPlayer expectLocalMediaPlayer() { + TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class); + // Delegate to simulated methods. This means they can be verified but also reflect + // realistic transitions from the TestMediaPlayer. + doCallRealMethod().when(mockMediaPlayer).start(); + doCallRealMethod().when(mockMediaPlayer).stop(); + doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean()); + when(mockMediaPlayer.isLooping()).thenCallRealMethod(); + when(mockMediaPlayer.isLooping()).thenCallRealMethod(); + mMockMediaPlayerQueue.add(mockMediaPlayer); + return mockMediaPlayer; + } + + private HapticGenerator expectHapticGenerator(MediaPlayer mockMediaPlayer) { + HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class); + // A test should never want this. + assertWithMessage("Can't expect a second haptic generator created " + + "for one media player") + .that(mMockHapticGeneratorMap.put(mockMediaPlayer, mockHapticGenerator)) + .isNull(); + return mockHapticGenerator; + } + + private void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) { + if (hasHapticChannels) { + mHapticChannels.add(mp); + } else { + mHapticChannels.remove(mp); + } + } + + private class TestInjectables extends Ringtone.Injectables { + @Override + public MediaPlayer newMediaPlayer() { + assertWithMessage( + "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer") + .that(mMockMediaPlayerQueue) + .isNotEmpty(); + return mMockMediaPlayerQueue.remove(); + } + + @Override + public boolean isHapticGeneratorAvailable() { + return hapticGeneratorAvailable; + } + + @Override + public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) { + HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer); + assertWithMessage("Unexpected HapticGenerator creation. " + + "Bug or need expectHapticGenerator") + .that(mockHapticGenerator) + .isNotNull(); + return mockHapticGenerator; + } + + @Override + public boolean isHapticPlaybackSupported() { + return true; + } + + @Override + public boolean hasHapticChannels(MediaPlayer mp) { + return mHapticChannels.contains(mp); + } + } + } + + /** + * MediaPlayer relies on a native backend and so its necessary to intercept calls from + * fake usage hitting them. + * + * Mocks don't work directly on native calls, but if they're overridden then it does work. + * Some basic state faking is also done to make the mocks more realistic. + */ + private static class TestMediaPlayer extends MediaPlayer { + private boolean mIsPlaying = false; + private boolean mIsLooping = false; + + @Override + public void start() { + mIsPlaying = true; + } + + @Override + public void stop() { + mIsPlaying = false; + } + + @Override + public void setLooping(boolean value) { + mIsLooping = value; + } + + @Override + public boolean isLooping() { + return mIsLooping; + } + + @Override + public boolean isPlaying() { + return mIsPlaying; + } + + void simulatePlayingFinished() { + if (!mIsPlaying) { + throw new IllegalStateException( + "Attempted to pretend playing finished when not playing"); + } + mIsPlaying = false; + } + } } diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp index 8d1e5e3a5bab..55b98c4704b1 100644 --- a/media/tests/ringtone/Android.bp +++ b/media/tests/ringtone/Android.bp @@ -9,24 +9,15 @@ android_test { srcs: ["src/**/*.java"], libs: [ - "android.test.base", - "android.test.mock", "android.test.runner", + "android.test.base", ], static_libs: [ - "androidx.test.ext.junit", - "androidx.test.ext.truth", "androidx.test.rules", - "frameworks-base-testutils", - "mockito-target-inline-minus-junit4", - "testables", "testng", - ], - - jni_libs: [ - "libdexmakerjvmtiagent", - "libstaticjvmtiagent", + "androidx.test.ext.truth", + "frameworks-base-testutils", ], test_suites: [ diff --git a/media/tests/ringtone/OWNERS b/media/tests/ringtone/OWNERS deleted file mode 100644 index 93b44f4788c5..000000000000 --- a/media/tests/ringtone/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -# Bug component: 345036 - -include /services/core/java/com/android/server/vibrator/OWNERS diff --git a/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java b/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java deleted file mode 100644 index e97e1173a1ea..000000000000 --- a/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.media.testing; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.media.AudioAttributes; -import android.media.MediaPlayer; -import android.net.Uri; - -/** - * Helper class with assertion methods on mock {@link MediaPlayer} instances. - */ -public final class MediaPlayerTestHelper { - - /** Verify this local media player mock instance was started. */ - public static void verifyPlayerStarted(MediaPlayer mockMediaPlayer) { - verify(mockMediaPlayer).setOnCompletionListener(any()); - verify(mockMediaPlayer).start(); - } - - /** Verify this local media player mock instance was stopped and released. */ - public static void verifyPlayerStopped(MediaPlayer mockMediaPlayer) { - verify(mockMediaPlayer).stop(); - verify(mockMediaPlayer).setOnCompletionListener(isNull()); - verify(mockMediaPlayer).reset(); - verify(mockMediaPlayer).release(); - } - - /** Verify this local media player mock instance was setup with given attributes. */ - public static void verifyPlayerSetup(Context context, MediaPlayer mockPlayer, - Uri expectedUri, AudioAttributes expectedAudioAttributes) throws Exception { - verify(mockPlayer).setDataSource(context, expectedUri); - verify(mockPlayer).setAudioAttributes(expectedAudioAttributes); - verify(mockPlayer).setPreferredDevice(null); - verify(mockPlayer).prepare(); - } - - /** Verify this local media player mock instance was setup with given fallback attributes. */ - public static void verifyPlayerFallbackSetup(MediaPlayer mockPlayer, - AssetFileDescriptor afd, AudioAttributes expectedAudioAttributes) throws Exception { - // This is very specific but it's a simple way to test that the test resource matches. - if (afd.getDeclaredLength() < 0) { - verify(mockPlayer).setDataSource(afd.getFileDescriptor()); - } else { - verify(mockPlayer).setDataSource(afd.getFileDescriptor(), - afd.getStartOffset(), - afd.getDeclaredLength()); - } - verify(mockPlayer).setAudioAttributes(expectedAudioAttributes); - verify(mockPlayer).setPreferredDevice(null); - verify(mockPlayer).prepare(); - } - - private MediaPlayerTestHelper() { - } -} diff --git a/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java b/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java deleted file mode 100644 index 25752ce83e5c..000000000000 --- a/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * 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.media.testing; - -import static com.google.common.truth.Truth.assertWithMessage; - -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.doCallRealMethod; -import static org.mockito.Mockito.when; - -import android.media.MediaPlayer; -import android.media.Ringtone; -import android.media.audiofx.HapticGenerator; -import android.util.ArrayMap; -import android.util.ArraySet; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.mockito.Mockito; - -import java.util.ArrayDeque; -import java.util.Map; -import java.util.Queue; - -/** - * This rule ensures that all expected media player creations from the factory do actually - * occur. The reason for this level of control is that creating a media player is fairly - * expensive and blocking, so we do want unit tests of this class to "declare" interactions - * of all created media players. - * <p> - * This needs to be a TestRule so that the teardown assertions can be skipped if the test has - * failed (and media player assertions may just be a distracting side effect). Otherwise, the - * teardown failures hide the real test ones. - */ -public class RingtoneInjectablesTrackingTestRule implements TestRule { - - private final Ringtone.Injectables mRingtoneTestInjectables = new TestInjectables(); - - // Queue of (local) media players, in order of expected creation. Enqueue using - // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone. - // This queue is asserted to be empty at the end of the test. - private final Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>(); - - // Similar to media players, but for haptic generator, which also needs releasing. - private final Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>(); - - // Media players with haptic channels. - private final ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>(); - - private boolean mHapticGeneratorAvailable = true; - - @Override - public Statement apply(Statement base, Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - base.evaluate(); - // Only assert if the test didn't fail (base.evaluate() would throw). - assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed") - .that(mMockMediaPlayerQueue).isEmpty(); - // Only assert if the test didn't fail (base.evaluate() would throw). - assertWithMessage( - "Test setup an expectLocalHapticGenerator but it wasn't consumed") - .that(mMockHapticGeneratorMap).isEmpty(); - } - }; - } - - /** The {@link Ringtone.Injectables} to be used for creating a testable {@link Ringtone}. */ - public Ringtone.Injectables getRingtoneTestInjectables() { - return mRingtoneTestInjectables; - } - - /** - * Create a test {@link MediaPlayer} that will be provided to the {@link Ringtone} instance - * created with {@link #getRingtoneTestInjectables()}. - * - * <p>If a media player is not created during the test execution after this method is called - * then the test will fail. It will also fail if the ringtone attempts to create one without - * this method being called first. - */ - public TestMediaPlayer expectLocalMediaPlayer() { - TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class); - // Delegate to simulated methods. This means they can be verified but also reflect - // realistic transitions from the TestMediaPlayer. - doCallRealMethod().when(mockMediaPlayer).start(); - doCallRealMethod().when(mockMediaPlayer).stop(); - doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean()); - when(mockMediaPlayer.isLooping()).thenCallRealMethod(); - mMockMediaPlayerQueue.add(mockMediaPlayer); - return mockMediaPlayer; - } - - /** - * Create a test {@link HapticGenerator} that will be provided to the {@link Ringtone} instance - * created with {@link #getRingtoneTestInjectables()}. - * - * <p>If a haptic generator is not created during the test execution after this method is called - * then the test will fail. It will also fail if the ringtone attempts to create one without - * this method being called first. - */ - public HapticGenerator expectHapticGenerator(MediaPlayer mediaPlayer) { - HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class); - // A test should never want this. - assertWithMessage("Can't expect a second haptic generator created " - + "for one media player") - .that(mMockHapticGeneratorMap.put(mediaPlayer, mockHapticGenerator)) - .isNull(); - return mockHapticGenerator; - } - - /** - * Configures the {@link MediaPlayer} to always return given flag when - * {@link Ringtone.Injectables#hasHapticChannels(MediaPlayer)} is called. - */ - public void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) { - if (hasHapticChannels) { - mHapticChannels.add(mp); - } else { - mHapticChannels.remove(mp); - } - } - - /** Test implementation of {@link Ringtone.Injectables} that uses the test rule setup. */ - private class TestInjectables extends Ringtone.Injectables { - @Override - public MediaPlayer newMediaPlayer() { - assertWithMessage( - "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer") - .that(mMockMediaPlayerQueue) - .isNotEmpty(); - return mMockMediaPlayerQueue.remove(); - } - - @Override - public boolean isHapticGeneratorAvailable() { - return mHapticGeneratorAvailable; - } - - @Override - public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) { - HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer); - assertWithMessage("Unexpected HapticGenerator creation. " - + "Bug or need expectHapticGenerator") - .that(mockHapticGenerator) - .isNotNull(); - return mockHapticGenerator; - } - - @Override - public boolean isHapticPlaybackSupported() { - return true; - } - - @Override - public boolean hasHapticChannels(MediaPlayer mp) { - return mHapticChannels.contains(mp); - } - } - - /** - * MediaPlayer relies on a native backend and so its necessary to intercept calls from - * fake usage hitting them. - * <p> - * Mocks don't work directly on native calls, but if they're overridden then it does work. - * Some basic state faking is also done to make the mocks more realistic. - */ - public static class TestMediaPlayer extends MediaPlayer { - private boolean mIsPlaying = false; - private boolean mIsLooping = false; - - @Override - public void start() { - mIsPlaying = true; - } - - @Override - public void stop() { - mIsPlaying = false; - } - - @Override - public void setLooping(boolean value) { - mIsLooping = value; - } - - @Override - public boolean isLooping() { - return mIsLooping; - } - - @Override - public boolean isPlaying() { - return mIsPlaying; - } - - /** - * Updates {@link #isPlaying()} result to false, if it's set to true. - * - * @throws IllegalStateException is {@link #isPlaying()} is already false - */ - public void simulatePlayingFinished() { - if (!mIsPlaying) { - throw new IllegalStateException( - "Attempted to pretend playing finished when not playing"); - } - mIsPlaying = false; - } - } -} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 01596d2bc004..d62b4907e96c 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -40,6 +40,7 @@ import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider +import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider import com.android.settingslib.spa.gallery.preference.PreferencePageProvider @@ -74,6 +75,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { PreferencePageProvider, SwitchPreferencePageProvider, MainSwitchPreferencePageProvider, + ListPreferencePageProvider, TwoTargetSwitchPreferencePageProvider, ArgumentPageProvider, SliderPageProvider, diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt new file mode 100644 index 000000000000..43b6d0b05696 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt @@ -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.settingslib.spa.gallery.preference + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.preference.ListPreference +import com.android.settingslib.spa.widget.preference.ListPreferenceModel +import com.android.settingslib.spa.widget.preference.ListPreferenceOption +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow + +private const val TITLE = "Sample ListPreference" + +object ListPreferencePageProvider : SettingsPageProvider { + override val name = "ListPreference" + private val owner = createSettingsPage() + + override fun buildEntry(arguments: Bundle?) = listOf( + SettingsEntryBuilder.create("ListPreference", owner) + .setUiLayoutFn { + SampleListPreference() + }.build(), + SettingsEntryBuilder.create("ListPreference not changeable", owner) + .setUiLayoutFn { + SampleNotChangeableListPreference() + }.build(), + ) + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + } + + override fun getTitle(arguments: Bundle?) = TITLE +} + +@Composable +private fun SampleListPreference() { + val selectedId = rememberSaveable { mutableIntStateOf(1) } + ListPreference(remember { + object : ListPreferenceModel { + override val title = "Preferred network type" + override val options = listOf( + ListPreferenceOption(id = 1, text = "5G (recommended)"), + ListPreferenceOption(id = 2, text = "LTE"), + ListPreferenceOption(id = 3, text = "3G"), + ) + override val selectedId = selectedId + override val onIdSelected: (id: Int) -> Unit = { selectedId.intValue = it } + } + }) +} + +@Composable +private fun SampleNotChangeableListPreference() { + val selectedId = rememberSaveable { mutableIntStateOf(1) } + val enableFlow = flow { + var enabled = true + while (true) { + delay(3.seconds) + enabled = !enabled + emit(enabled) + } + } + val enabled = enableFlow.collectAsStateWithLifecycle(initialValue = true) + ListPreference(remember { + object : ListPreferenceModel { + override val title = "Preferred network type" + override val enabled = enabled + override val options = listOf( + ListPreferenceOption(id = 1, text = "5G (recommended)"), + ListPreferenceOption(id = 2, text = "LTE"), + ListPreferenceOption(id = 3, text = "3G"), + ) + override val selectedId = selectedId + override val onIdSelected: (id: Int) -> Unit = { selectedId.intValue = it } + } + }) +} + +@Preview +@Composable +private fun ListPreferencePagePreview() { + SettingsTheme { + ListPreferencePageProvider.Page(null) + } +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt index eddede752d06..ce9678bab684 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt @@ -36,6 +36,7 @@ object PreferenceMainPageProvider : SettingsPageProvider { PreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), MainSwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), TwoTargetSwitchPreferencePageProvider.buildInjectEntry() .setLink(fromPage = owner).build(), ) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index 7962e601999a..4088ffd43986 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -40,8 +40,18 @@ object SettingsDimension { /** The size when app icon is displayed in App info page. */ val appIconInfoSize = 48.dp + /** The vertical padding for buttons. */ + val buttonPaddingVertical = 12.dp + /** The [PaddingValues] for buttons. */ - val buttonPadding = PaddingValues(horizontal = itemPaddingEnd, vertical = 12.dp) + val buttonPadding = PaddingValues(horizontal = itemPaddingEnd, vertical = buttonPaddingVertical) + + /** The horizontal padding for dialog items. */ + val dialogItemPaddingHorizontal = itemPaddingStart + + /** The [PaddingValues] for dialog items. */ + val dialogItemPadding = + PaddingValues(horizontal = dialogItemPaddingHorizontal, vertical = buttonPaddingVertical) /** The sizes info of illustration widget. */ val illustrationMaxWidth = 412.dp diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt index c8faef6d6703..a9cd0e9cd2e9 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt @@ -16,10 +16,15 @@ package com.android.settingslib.spa.framework.theme +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha + object SettingsOpacity { const val Full = 1f const val Disabled = 0.38f const val Divider = 0.2f const val SurfaceTone = 0.14f const val Hint = 0.9f + + fun Modifier.alphaForEnabled(enabled: Boolean) = alpha(if (enabled) Full else Disabled) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt index c66e20a335ef..8c862d401c6b 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt @@ -22,5 +22,5 @@ import androidx.compose.ui.unit.dp object SettingsShape { val CornerMedium = RoundedCornerShape(12.dp) - val CornerLarge = RoundedCornerShape(24.dp) + val CornerExtraLarge = RoundedCornerShape(28.dp) } 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 1ad075c11985..979cf3bddae6 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 @@ -65,7 +65,7 @@ fun ActionButtons(actionButtons: List<ActionButton>) { Row( Modifier .padding(SettingsDimension.buttonPadding) - .clip(SettingsShape.CornerLarge) + .clip(SettingsShape.CornerExtraLarge) .height(IntrinsicSize.Min) ) { for ((index, actionButton) in actionButtons.withIndex()) { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt new file mode 100644 index 000000000000..8b172da08dd2 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt @@ -0,0 +1,46 @@ +/* + * 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.settingslib.spa.widget.dialog + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.window.Dialog +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsShape +import com.android.settingslib.spa.widget.ui.SettingsTitle + +@Composable +fun SettingsDialog( + title: String, + onDismissRequest: () -> Unit, + content: @Composable () -> Unit, +) { + Dialog(onDismissRequest = onDismissRequest) { + Card(shape = SettingsShape.CornerExtraLarge) { + Column(modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingAround)) { + Box(modifier = Modifier.padding(SettingsDimension.dialogItemPadding)) { + SettingsTitle(title = title, useMediumWeight = true) + } + content() + } + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt index 6330ddf5bea4..4d42fbae24fd 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt @@ -29,13 +29,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.compose.toState import com.android.settingslib.spa.framework.theme.SettingsDimension -import com.android.settingslib.spa.framework.theme.SettingsOpacity +import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.widget.ui.SettingsTitle @@ -57,8 +56,7 @@ internal fun BaseLayout( .padding(end = paddingEnd), verticalAlignment = Alignment.CenterVertically, ) { - val alphaModifier = - Modifier.alpha(if (enabled.value) SettingsOpacity.Full else SettingsOpacity.Disabled) + val alphaModifier = Modifier.alphaForEnabled(enabled.value) BaseIcon(icon, alphaModifier, paddingStart) Titles( title = title, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt new file mode 100644 index 000000000000..19779f67ca48 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt @@ -0,0 +1,138 @@ +/* + * 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.settingslib.spa.widget.preference + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.selection.selectableGroup +import androidx.compose.material3.RadioButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.IntState +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.widget.dialog.SettingsDialog +import com.android.settingslib.spa.widget.ui.SettingsDialogItem + +data class ListPreferenceOption( + val id: Int, + val text: String, +) + +/** + * The widget model for [ListPreference] widget. + */ +interface ListPreferenceModel { + /** + * The title of this [ListPreference]. + */ + val title: String + + /** + * The icon of this [ListPreference]. + * + * Default is `null` which means no icon. + */ + val icon: (@Composable () -> Unit)? + get() = null + + /** + * Indicates whether this [ListPreference] is enabled. + * + * Disabled [ListPreference] will be displayed in disabled style. + */ + val enabled: State<Boolean> + get() = stateOf(true) + + val options: List<ListPreferenceOption> + + val selectedId: IntState + + val onIdSelected: (id: Int) -> Unit +} + +@Composable +fun ListPreference(model: ListPreferenceModel) { + var dialogOpened by rememberSaveable { mutableStateOf(false) } + if (dialogOpened) { + SettingsDialog( + title = model.title, + onDismissRequest = { dialogOpened = false }, + ) { + Column(modifier = Modifier.selectableGroup()) { + for (option in model.options) { + Radio(option, model.selectedId, model.enabled) { + dialogOpened = false + model.onIdSelected(it) + } + } + } + } + } + Preference(model = remember(model) { + object : PreferenceModel { + override val title = model.title + override val summary = derivedStateOf { + model.options.find { it.id == model.selectedId.intValue }?.text ?: "" + } + override val icon = model.icon + override val enabled = model.enabled + override val onClick = { dialogOpened = true }.takeIf { model.options.isNotEmpty() } + } + }) +} + +@Composable +private fun Radio( + option: ListPreferenceOption, + selectedId: IntState, + enabledState: State<Boolean>, + onIdSelected: (id: Int) -> Unit, +) { + val selected = option.id == selectedId.intValue + val enabled = enabledState.value + Row( + modifier = Modifier + .fillMaxWidth() + .selectable( + selected = selected, + enabled = enabled, + onClick = { onIdSelected(option.id) }, + role = Role.RadioButton, + ) + .padding(SettingsDimension.dialogItemPadding), + verticalAlignment = Alignment.CenterVertically, + ) { + RadioButton(selected = selected, onClick = null, enabled = enabled) + Spacer(modifier = Modifier.width(SettingsDimension.itemPaddingEnd)) + SettingsDialogItem(text = option.text, enabled = enabled) + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt index 3e04b16f08cf..0c16c8bc7229 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt @@ -39,7 +39,7 @@ fun MainSwitchPreference(model: SwitchPreferenceModel) { true -> MaterialTheme.colorScheme.primaryContainer else -> MaterialTheme.colorScheme.secondaryContainer }, - shape = SettingsShape.CornerLarge, + shape = SettingsShape.CornerExtraLarge, ) { InternalSwitchPreference( title = model.title, 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 6ef45900a103..5f320f7ade3f 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,19 +18,14 @@ package com.android.settingslib.spa.widget.scaffold import androidx.appcompat.R import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.FindInPage import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.draw.scale -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 @@ -55,7 +50,6 @@ private fun BackAction(contentDescription: String, onClick: () -> Unit) { Icon( imageVector = Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = contentDescription, - modifier = Modifier.autoMirrored(), ) } } @@ -81,10 +75,3 @@ internal fun ClearAction(onClick: () -> Unit) { ) } } - -private fun Modifier.autoMirrored() = composed { - when (LocalLayoutDirection.current) { - LayoutDirection.Rtl -> scale(scaleX = -1f, scaleY = 1f) - else -> this - } -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt index 57319e760c69..7f1acffe7a8a 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.framework.theme.toMediumWeight @@ -48,6 +49,17 @@ fun SettingsTitle(title: String, useMediumWeight: Boolean = false) { } @Composable +fun SettingsDialogItem(text: String, enabled: Boolean = true) { + Text( + text = text, + modifier = Modifier.alphaForEnabled(enabled), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + overflow = TextOverflow.Ellipsis, + ) +} + +@Composable fun SettingsBody( body: String, maxLines: Int = Int.MAX_VALUE, @@ -82,6 +94,9 @@ fun PlaceholderTitle(title: String) { private fun BasePreferencePreview() { SettingsTheme { Column(Modifier.width(100.dp)) { + SettingsTitle( + title = "Title", + ) SettingsBody( body = "Long long long long long long text", ) diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt new file mode 100644 index 000000000000..c7582b2601af --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt @@ -0,0 +1,57 @@ +/* + * 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.settingslib.spa.widget.dialog + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.onDialogText +import com.android.settingslib.spa.widget.ui.SettingsDialogItem +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SettingsDialogTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun title_displayed() { + composeTestRule.setContent { + SettingsDialog(title = TITLE, onDismissRequest = {}) {} + } + + composeTestRule.onDialogText(TITLE).assertIsDisplayed() + } + + @Test + fun text_displayed() { + composeTestRule.setContent { + SettingsDialog(title = "", onDismissRequest = {}) { + SettingsDialogItem(text = TEXT) + } + } + + composeTestRule.onDialogText(TEXT).assertIsDisplayed() + } + + private companion object { + const val TITLE = "Title" + const val TEXT = "Text" + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt new file mode 100644 index 000000000000..997a02369d9f --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt @@ -0,0 +1,177 @@ +/* + * 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.settingslib.spa.widget.preference + +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.testutils.onDialogText +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ListPreferenceTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun title_displayed() { + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val options = emptyList<ListPreferenceOption>() + override val selectedId = mutableIntStateOf(0) + override val onIdSelected: (Int) -> Unit = {} + } + }) + } + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed() + } + + @Test + fun summary_showSelectedText() { + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val options = listOf(ListPreferenceOption(id = 1, text = "A")) + override val selectedId = mutableIntStateOf(1) + override val onIdSelected: (Int) -> Unit = {} + } + }) + } + + composeTestRule.onNodeWithText("A").assertIsDisplayed() + } + + @Test + fun click_optionsIsEmpty_notShowDialog() { + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val options = emptyList<ListPreferenceOption>() + override val selectedId = mutableIntStateOf(0) + override val onIdSelected: (Int) -> Unit = {} + } + }) + } + + composeTestRule.onNodeWithText(TITLE).performClick() + + composeTestRule.onDialogText(TITLE).assertDoesNotExist() + } + + @Test + fun click_notEnabled_notShowDialog() { + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val enabled = stateOf(false) + override val options = listOf(ListPreferenceOption(id = 1, text = "A")) + override val selectedId = mutableIntStateOf(1) + override val onIdSelected: (Int) -> Unit = {} + } + }) + } + + composeTestRule.onNodeWithText(TITLE).performClick() + + composeTestRule.onDialogText(TITLE).assertDoesNotExist() + } + + @Test + fun click_optionsNotEmpty_showDialog() { + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val options = listOf(ListPreferenceOption(id = 1, text = "A")) + override val selectedId = mutableIntStateOf(1) + override val onIdSelected: (Int) -> Unit = {} + } + }) + } + + composeTestRule.onNodeWithText(TITLE).performClick() + + composeTestRule.onDialogText(TITLE).assertIsDisplayed() + } + + @Test + fun select() { + val selectedId = mutableIntStateOf(1) + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val options = listOf( + ListPreferenceOption(id = 1, text = "A"), + ListPreferenceOption(id = 2, text = "B"), + ) + override val selectedId = selectedId + override val onIdSelected = { id: Int -> selectedId.intValue = id } + } + }) + } + + composeTestRule.onNodeWithText(TITLE).performClick() + composeTestRule.onDialogText("B").performClick() + + composeTestRule.onNodeWithText("B").assertIsDisplayed() + } + + @Test + fun select_dialogOpenThenDisable_itemAlsoDisabled() { + val selectedId = mutableIntStateOf(1) + val enabledState = mutableStateOf(true) + composeTestRule.setContent { + ListPreference(remember { + object : ListPreferenceModel { + override val title = TITLE + override val enabled = enabledState + override val options = listOf( + ListPreferenceOption(id = 1, text = "A"), + ListPreferenceOption(id = 2, text = "B"), + ) + override val selectedId = selectedId + override val onIdSelected = { id: Int -> selectedId.intValue = id } + } + }) + } + + composeTestRule.onNodeWithText(TITLE).performClick() + enabledState.value = false + + composeTestRule.onDialogText("B").assertIsDisplayed().assertIsNotEnabled() + } + + private companion object { + const val TITLE = "Title" + } +} diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java new file mode 100644 index 000000000000..a9fd380c2733 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java @@ -0,0 +1,46 @@ +/* + * 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.settingslib.testutils.shadow; + +import android.Manifest; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.hardware.display.ColorDisplayManager; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(ColorDisplayManager.class) +public class ShadowColorDisplayManager extends org.robolectric.shadows.ShadowColorDisplayManager { + + private boolean mIsReduceBrightColorsActivated; + + @Implementation + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setReduceBrightColorsActivated(boolean activated) { + mIsReduceBrightColorsActivated = activated; + return true; + } + + @Implementation + @SystemApi + public boolean isReduceBrightColorsActivated() { + return mIsReduceBrightColorsActivated; + } + +} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index fe39c4febc80..59c3cd38a97d 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -84,6 +84,7 @@ public class SystemSettings { Settings.System.RING_VIBRATION_INTENSITY, Settings.System.HAPTIC_FEEDBACK_INTENSITY, Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, + Settings.System.KEYBOARD_VIBRATION_ENABLED, Settings.System.HAPTIC_FEEDBACK_ENABLED, Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE Settings.System.DISPLAY_COLOR_MODE, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index eba74ab14f3d..572303a813bf 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -138,6 +138,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); + VALIDATORS.put(System.KEYBOARD_VIBRATION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.HAPTIC_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.RINGTONE, URI_VALIDATOR); VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 46cd725ad582..4e2fad0bece2 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3243,7 +3243,7 @@ public class SettingsProvider extends ContentProvider { } if (success && criticalSettings != null && criticalSettings.contains(name)) { - settingsState.persistSyncLocked(); + settingsState.persistSettingsLocked(); } if (forceNotify || success) { @@ -3294,7 +3294,7 @@ public class SettingsProvider extends ContentProvider { } if (success && criticalSettings != null && criticalSettings.contains(name)) { - settingsState.persistSyncLocked(); + settingsState.persistSettingsLocked(); } if (forceNotify || success) { @@ -3319,7 +3319,7 @@ public class SettingsProvider extends ContentProvider { } if (success && criticalSettings != null && criticalSettings.contains(name)) { - settingsState.persistSyncLocked(); + settingsState.persistSettingsLocked(); } if (forceNotify || success) { @@ -3385,7 +3385,7 @@ public class SettingsProvider extends ContentProvider { } } if (someSettingChanged) { - settingsState.persistSyncLocked(); + settingsState.persistSettingsLocked(); success = true; } } @@ -3407,7 +3407,7 @@ public class SettingsProvider extends ContentProvider { } } if (someSettingChanged) { - settingsState.persistSyncLocked(); + settingsState.persistSettingsLocked(); success = true; } } @@ -3435,7 +3435,7 @@ public class SettingsProvider extends ContentProvider { } } if (someSettingChanged) { - settingsState.persistSyncLocked(); + settingsState.persistSettingsLocked(); success = true; } } @@ -3460,7 +3460,7 @@ public class SettingsProvider extends ContentProvider { logSettingChanged(userId, name, type, CHANGE_TYPE_DELETE); } if (someSettingChanged) { - settingsState.persistSyncLocked(); + settingsState.persistSettingsLocked(); success = true; } } @@ -3559,7 +3559,7 @@ public class SettingsProvider extends ContentProvider { ensureSettingsStateLocked(systemKey); SettingsState systemSettings = mSettingsStates.get(systemKey); migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM); - systemSettings.persistSyncLocked(); + systemSettings.persistSettingsLocked(); // Move over the secure settings. // Do this after System settings, since this is the first thing we check when deciding @@ -3569,7 +3569,7 @@ public class SettingsProvider extends ContentProvider { SettingsState secureSettings = mSettingsStates.get(secureKey); migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE); ensureSecureSettingAndroidIdSetLocked(secureSettings); - secureSettings.persistSyncLocked(); + secureSettings.persistSettingsLocked(); // Move over the global settings if owner. // Do this last, since this is the first thing we check when deciding @@ -3585,7 +3585,7 @@ public class SettingsProvider extends ContentProvider { mSettingsCreationBuildId, null, true, SettingsState.SYSTEM_PACKAGE_NAME); } - globalSettings.persistSyncLocked(); + globalSettings.persistSettingsLocked(); } // Drop the database as now all is moved and persisted. @@ -4404,16 +4404,16 @@ public class SettingsProvider extends ContentProvider { if (userId == UserHandle.USER_SYSTEM) { SettingsState globalSettings = getGlobalSettingsLocked(); ensureLegacyDefaultValueAndSystemSetUpdatedLocked(globalSettings, userId); - globalSettings.persistSyncLocked(); + globalSettings.persistSettingsLocked(); } SettingsState secureSettings = getSecureSettingsLocked(mUserId); ensureLegacyDefaultValueAndSystemSetUpdatedLocked(secureSettings, userId); - secureSettings.persistSyncLocked(); + secureSettings.persistSettingsLocked(); SettingsState systemSettings = getSystemSettingsLocked(mUserId); ensureLegacyDefaultValueAndSystemSetUpdatedLocked(systemSettings, userId); - systemSettings.persistSyncLocked(); + systemSettings.persistSettingsLocked(); currentVersion = 146; } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index e9533e598336..7cec99d4189f 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -72,6 +72,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CountDownLatch; /** * This class contains the state for one type of settings. It is responsible @@ -589,9 +590,10 @@ final class SettingsState { } // The settings provider must hold its lock when calling here. - public void persistSyncLocked() { + public void persistSettingsLocked() { mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS); - doWriteState(); + // schedule a write operation right away + mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS).sendToTarget(); } // The settings provider must hold its lock when calling here. @@ -1725,4 +1727,20 @@ final class SettingsState { return mPackageToMemoryUsage.getOrDefault(packageName, 0); } } + + /** + * Allow tests to wait for the handler to finish handling all the remaining messages + */ + @VisibleForTesting + public void waitForHandler() { + final CountDownLatch latch = new CountDownLatch(1); + synchronized (mLock) { + mHandler.post(latch::countDown); + } + try { + latch.await(); + } catch (InterruptedException e) { + // ignored + } + } } diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index df4d2a1ed89f..02a7bc1646ba 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -76,6 +76,14 @@ public class SettingsStateTest extends AndroidTestCase { mSettingsFile.delete(); } + @Override + protected void tearDown() throws Exception { + if (mSettingsFile != null) { + mSettingsFile.delete(); + } + super.tearDown(); + } + public void testIsBinary() { assertFalse(SettingsState.isBinary(" abc 日本語")); @@ -149,11 +157,10 @@ public class SettingsStateTest extends AndroidTestCase { * Make sure settings can be written to a file and also can be read. */ public void testReadWrite() { - final File file = new File(getContext().getCacheDir(), "setting.xml"); - file.delete(); final Object lock = new Object(); - final SettingsState ssWriter = new SettingsState(getContext(), lock, file, 1, + assertFalse(mSettingsFile.exists()); + final SettingsState ssWriter = new SettingsState(getContext(), lock, mSettingsFile, 1, SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); ssWriter.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING); @@ -162,11 +169,13 @@ public class SettingsStateTest extends AndroidTestCase { ssWriter.insertSettingLocked("k3", null, null, false, "p2"); ssWriter.insertSettingLocked("k4", CRAZY_STRING, null, false, "p3"); synchronized (lock) { - ssWriter.persistSyncLocked(); + ssWriter.persistSettingsLocked(); } - - final SettingsState ssReader = new SettingsState(getContext(), lock, file, 1, + ssWriter.waitForHandler(); + assertTrue(mSettingsFile.exists()); + final SettingsState ssReader = new SettingsState(getContext(), lock, mSettingsFile, 1, SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + synchronized (lock) { assertEquals("\u0000", ssReader.getSettingLocked("k1").getValue()); assertEquals("abc", ssReader.getSettingLocked("k2").getValue()); @@ -179,10 +188,8 @@ public class SettingsStateTest extends AndroidTestCase { * In version 120, value "null" meant {code NULL}. */ public void testUpgrade() throws Exception { - final File file = new File(getContext().getCacheDir(), "setting.xml"); - file.delete(); final Object lock = new Object(); - final PrintStream os = new PrintStream(new FileOutputStream(file)); + final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile)); os.print( "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" + "<settings version=\"120\">" + @@ -192,7 +199,7 @@ public class SettingsStateTest extends AndroidTestCase { "</settings>"); os.close(); - final SettingsState ss = new SettingsState(getContext(), lock, file, 1, + final SettingsState ss = new SettingsState(getContext(), lock, mSettingsFile, 1, SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); synchronized (lock) { SettingsState.Setting s; @@ -213,7 +220,8 @@ public class SettingsStateTest extends AndroidTestCase { public void testInitializeSetting_preserveFlagNotSet() { SettingsState settingsWriter = getSettingStateObject(); settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE); - settingsWriter.persistSyncLocked(); + settingsWriter.persistSettingsLocked(); + settingsWriter.waitForHandler(); SettingsState settingsReader = getSettingStateObject(); assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore()); @@ -223,7 +231,8 @@ public class SettingsStateTest extends AndroidTestCase { SettingsState settingsWriter = getSettingStateObject(); settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE); settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, TEST_PACKAGE); - settingsWriter.persistSyncLocked(); + settingsWriter.persistSettingsLocked(); + settingsWriter.waitForHandler(); SettingsState settingsReader = getSettingStateObject(); assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore()); @@ -234,7 +243,8 @@ public class SettingsStateTest extends AndroidTestCase { settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE); settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE, /* overrideableByRestore */ true); - settingsWriter.persistSyncLocked(); + settingsWriter.persistSettingsLocked(); + settingsWriter.waitForHandler(); SettingsState settingsReader = getSettingStateObject(); assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore()); @@ -250,7 +260,8 @@ public class SettingsStateTest extends AndroidTestCase { // already been set to true. settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE, /* overrideableByRestore */ true); - settingsWriter.persistSyncLocked(); + settingsWriter.persistSettingsLocked(); + settingsWriter.waitForHandler(); SettingsState settingsReader = getSettingStateObject(); assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore()); @@ -481,8 +492,11 @@ public class SettingsStateTest extends AndroidTestCase { settingsState.insertSettingLocked( FLAG_NAME_1_STAGED, VALUE1, null, false, TEST_PACKAGE); settingsState.insertSettingLocked(FLAG_NAME_2, VALUE2, null, false, TEST_PACKAGE); - settingsState.persistSyncLocked(); + settingsState.persistSettingsLocked(); + } + settingsState.waitForHandler(); + synchronized (lock) { assertEquals(VALUE1, settingsState.getSettingLocked(FLAG_NAME_1_STAGED).getValue()); assertEquals(VALUE2, settingsState.getSettingLocked(FLAG_NAME_2).getValue()); } @@ -522,7 +536,10 @@ public class SettingsStateTest extends AndroidTestCase { synchronized (lock) { settingsState.insertSettingLocked(INVALID_STAGED_FLAG_1, VALUE2, null, false, TEST_PACKAGE); - settingsState.persistSyncLocked(); + settingsState.persistSettingsLocked(); + } + settingsState.waitForHandler(); + synchronized (lock) { assertEquals(VALUE2, settingsState.getSettingLocked(INVALID_STAGED_FLAG_1).getValue()); } diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp index 235e6724f91d..2c89d6dbce56 100644 --- a/packages/SoundPicker/Android.bp +++ b/packages/SoundPicker/Android.bp @@ -7,40 +7,22 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -android_library { - name: "SoundPickerLib", - srcs: [ - "src/**/*.java", +android_app { + name: "SoundPicker", + defaults: ["platform_app_defaults"], + manifest: "AndroidManifest.xml", + + static_libs: [ + "androidx.appcompat_appcompat", ], resource_dirs: [ "res", ], - static_libs: [ - "androidx.appcompat_appcompat", - "hilt_android", - "guava", - "androidx.recyclerview_recyclerview", - "androidx-constraintlayout_constraintlayout", - "androidx.viewpager2_viewpager2", - "com.google.android.material_material", + srcs: [ + "src/**/*.java", ], -} -android_app { - name: "SoundPicker", - defaults: ["platform_app_defaults"], - manifest: "AndroidManifest.xml", - static_libs: ["SoundPickerLib"], platform_apis: true, certificate: "media", privileged: true, - - optimize: { - enabled: true, - optimize: true, - shrink: true, - shrink_resources: true, - obfuscate: false, - proguard_compatibility: false, - }, } diff --git a/packages/SoundPicker/AndroidManifest.xml b/packages/SoundPicker/AndroidManifest.xml index 934b003c605c..44295a5cbdd2 100644 --- a/packages/SoundPicker/AndroidManifest.xml +++ b/packages/SoundPicker/AndroidManifest.xml @@ -1,6 +1,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.soundpicker" - android:sharedUserId="android.media"> + package="com.android.soundpicker" + android:sharedUserId="android.media"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> @@ -9,16 +9,12 @@ <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> - <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> - <application - android:name=".RingtonePickerApplication" - android:allowBackup="false" - android:label="@string/app_label" - android:theme="@style/Theme.AppCompat" - android:supportsRtl="true"> + android:allowBackup="false" + android:label="@string/app_label" + android:supportsRtl="true"> <receiver android:name="RingtoneReceiver" - android:exported="true"> + android:exported="true"> <intent-filter> <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/> </intent-filter> @@ -27,17 +23,14 @@ <service android:name="RingtoneOverlayService" /> <activity android:name="RingtonePickerActivity" - android:theme="@style/Theme.AppCompat.Dialog" - android:enabled="@*android:bool/config_defaultRingtonePickerEnabled" - android:excludeFromRecents="true" - android:exported="true"> + android:theme="@style/PickerDialogTheme" + android:enabled="@*android:bool/config_defaultRingtonePickerEnabled" + android:excludeFromRecents="true" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.RINGTONE_PICKER" /> <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" /> - <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" /> - <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" /> </intent-filter> </activity> </application> -</manifest> +</manifest>
\ No newline at end of file diff --git a/packages/SoundPicker/res/layout/add_new_sound_item.xml b/packages/SoundPicker/res/layout/add_new_sound_item.xml index 024b97ef23be..57b70d7db0a9 100644 --- a/packages/SoundPicker/res/layout/add_new_sound_item.xml +++ b/packages/SoundPicker/res/layout/add_new_sound_item.xml @@ -19,9 +19,7 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_vertical" - android:background="?android:attr/selectableItemBackground" - android:focusable="true" - android:clickable="true"> + android:background="?android:attr/selectableItemBackground"> <ImageView android:layout_width="24dp" @@ -31,19 +29,19 @@ android:scaleType="centerCrop" android:layout_marginRight="24dp" android:layout_marginLeft="24dp" - android:src="@drawable/ic_add"/> + android:src="@drawable/ic_add" /> - <TextView - android:id="@+id/add_new_sound_text" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeightSmall" - android:text="@null" - android:textColor="?android:attr/colorAccent" - android:textAppearance="?android:attr/textAppearanceMedium" - android:maxLines="3" - android:gravity="center_vertical" - android:paddingEnd="?android:attr/dialogPreferredPadding" - android:drawablePadding="20dp" - android:ellipsize="marquee"/> + <TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/add_new_sound_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:text="@null" + android:textColor="?android:attr/colorAccent" + android:textAppearance="?android:attr/textAppearanceMedium" + android:maxLines="3" + android:gravity="center_vertical" + android:paddingEnd="?android:attr/dialogPreferredPadding" + android:drawablePadding="20dp" + android:ellipsize="marquee" /> </LinearLayout>
\ No newline at end of file diff --git a/packages/SoundPicker/res/layout/radio_with_work_badge.xml b/packages/SoundPicker/res/layout/radio_with_work_badge.xml index 36ac93ed630b..2e44b6f4dd7a 100644 --- a/packages/SoundPicker/res/layout/radio_with_work_badge.xml +++ b/packages/SoundPicker/res/layout/radio_with_work_badge.xml @@ -14,14 +14,12 @@ limitations under the License. --> -<com.android.soundpicker.CheckedListItem - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:gravity="center_vertical" - android:background="?android:attr/selectableItemBackground" - android:focusable="true" - android:clickable="true"> +<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:background="?android:attr/selectableItemBackground" + > <CheckedTextView android:id="@+id/checked_text_view" @@ -37,7 +35,7 @@ android:drawablePadding="20dp" android:ellipsize="marquee" android:layout_toLeftOf="@+id/work_icon" - android:maxLines="3"/> + android:maxLines="3" /> <ImageView android:id="@id/work_icon" @@ -46,5 +44,5 @@ android:layout_alignParentRight="true" android:layout_centerVertical="true" android:scaleType="centerCrop" - android:layout_marginRight="20dp"/> -</com.android.soundpicker.CheckedListItem> + android:layout_marginRight="20dp" /> +</com.android.soundpicker.CheckedListItem>
\ No newline at end of file diff --git a/packages/SoundPicker/res/values/strings.xml b/packages/SoundPicker/res/values/strings.xml index ab7b95a09028..04a2c2bb83c3 100644 --- a/packages/SoundPicker/res/values/strings.xml +++ b/packages/SoundPicker/res/values/strings.xml @@ -40,8 +40,4 @@ <!-- Text for the name of the app. [CHAR LIMIT=12] --> <string name="app_label">Sounds</string> - - <string name="empty_list">The list is empty</string> - <string name="sound_page_title">Sound</string> - <string name="vibration_page_title">Vibration</string> </resources> diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java index 90a14f9717db..ea46c0cee6b9 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java +++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java @@ -16,19 +16,43 @@ package com.android.soundpicker; +import android.content.ContentProvider; +import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.media.AudioAttributes; +import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; import android.os.UserHandle; +import android.os.UserManager; +import android.provider.MediaStore; +import android.provider.Settings; import android.util.Log; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentTransaction; -import androidx.lifecycle.ViewModelProvider; - -import dagger.hilt.android.AndroidEntryPoint; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.CursorAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +import java.io.IOException; +import java.util.regex.Pattern; /** * The {@link RingtonePickerActivity} allows the user to choose one from all of the @@ -36,183 +60,727 @@ import dagger.hilt.android.AndroidEntryPoint; * * @see RingtoneManager#ACTION_RINGTONE_PICKER */ -@AndroidEntryPoint(AppCompatActivity.class) -public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity { +public final class RingtonePickerActivity extends AlertActivity implements + AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener, + AlertController.AlertParams.OnPrepareListViewListener { + + private static final int POS_UNKNOWN = -1; private static final String TAG = "RingtonePickerActivity"; - // TODO: Use the extra keys from RingtoneManager once they're added. - private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY"; - private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT"; - private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI"; - private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT"; - private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI"; - private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false; - - private RingtonePickerViewModel mRingtonePickerViewModel; + + private static final int DELAY_MS_SELECTION_PLAYED = 300; + + private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE; + + private static final String SAVE_CLICKED_POS = "clicked_pos"; + + private static final String SOUND_NAME_RES_PREFIX = "sound_name_"; + + private static final int ADD_FILE_REQUEST_CODE = 300; + + private RingtoneManager mRingtoneManager; + private int mType; + + private Cursor mCursor; + private Handler mHandler; + private BadgedRingtoneAdapter mAdapter; + + /** The position in the list of the 'Silent' item. */ + private int mSilentPos = POS_UNKNOWN; + + /** The position in the list of the 'Default' item. */ + private int mDefaultRingtonePos = POS_UNKNOWN; + + /** The position in the list of the ringtone to sample. */ + private int mSampleRingtonePos = POS_UNKNOWN; + + /** Whether this list has the 'Silent' item. */ + private boolean mHasSilentItem; + + /** The Uri to place a checkmark next to. */ + private Uri mExistingUri; + + /** The number of static items in the list. */ + private int mStaticItemCount; + + /** Whether this list has the 'Default' item. */ + private boolean mHasDefaultItem; + + /** The Uri to play when the 'Default' item is clicked. */ + private Uri mUriForDefaultItem; + + /** Id of the user to which the ringtone picker should list the ringtones */ + private int mPickerUserId; + + /** Context of the user specified by mPickerUserId */ + private Context mTargetContext; + + /** + * A Ringtone for the default ringtone. In most cases, the RingtoneManager + * will stop the previous ringtone. However, the RingtoneManager doesn't + * manage the default ringtone for us, so we should stop this one manually. + */ + private Ringtone mDefaultRingtone; + + /** + * The ringtone that's currently playing, unless the currently playing one is the default + * ringtone. + */ + private Ringtone mCurrentRingtone; + + /** + * Stable ID for the ringtone that is currently checked (may be -1 if no ringtone is checked). + */ + private long mCheckedItemId = -1; + private int mAttributesFlags; + private boolean mShowOkCancelButtons; + + /** + * Keep the currently playing ringtone around when changing orientation, so that it + * can be stopped later, after the activity is recreated. + */ + private static Ringtone sPlayingRingtone; + + private DialogInterface.OnClickListener mRingtoneClickListener = + new DialogInterface.OnClickListener() { + + /* + * On item clicked + */ + public void onClick(DialogInterface dialog, int which) { + if (which == mCursor.getCount() + mStaticItemCount) { + // The "Add new ringtone" item was clicked. Start a file picker intent to select + // only audio files (MIME type "audio/*") + final Intent chooseFile = getMediaFilePickerIntent(); + startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE); + return; + } + + // Save the position of most recently clicked item + setCheckedItem(which); + + // In the buttonless (watch-only) version, preemptively set our result since we won't + // have another chance to do so before the activity closes. + if (!mShowOkCancelButtons) { + setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri()); + } + + // Play clip + playRingtone(which, 0); + } + + }; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_ringtone_picker); - mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class); + mHandler = new Handler(); Intent intent = getIntent(); - /** - * Id of the user to which the ringtone picker should list the ringtones - */ - int pickerUserId = UserHandle.myUserId(); + mPickerUserId = UserHandle.myUserId(); + mTargetContext = this; // Get the types of ringtones to show - int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, - RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN); + mType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1); + initRingtoneManager(); + /* + * Get whether to show the 'Default' item, and the URI to play when the + * default is clicked + */ + mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI); + if (mUriForDefaultItem == null) { + if (mType == RingtoneManager.TYPE_NOTIFICATION) { + mUriForDefaultItem = Settings.System.DEFAULT_NOTIFICATION_URI; + } else if (mType == RingtoneManager.TYPE_ALARM) { + mUriForDefaultItem = Settings.System.DEFAULT_ALARM_ALERT_URI; + } else if (mType == RingtoneManager.TYPE_RINGTONE) { + mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI; + } else { + // or leave it null for silence. + mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI; + } + } + + // Get whether to show the 'Silent' item + mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); // AudioAttributes flags mAttributesFlags |= intent.getIntExtra( RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS, 0 /*defaultValue == no flags*/); - boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons); + mShowOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons); + + // The volume keys will control the stream that we are choosing a ringtone for + setVolumeControlStream(mRingtoneManager.inferStreamType()); + + // Get the URI whose list item should have a checkmark + mExistingUri = intent + .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI); - String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE); - if (title == null) { - title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType)); + // Create the list of ringtones and hold on to it so we can update later. + mAdapter = new BadgedRingtoneAdapter(this, mCursor, + /* isManagedProfile = */ UserManager.get(this).isManagedProfile(mPickerUserId)); + if (savedInstanceState != null) { + setCheckedItem(savedInstanceState.getInt(SAVE_CLICKED_POS, POS_UNKNOWN)); } - String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY); - RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType( - ringtonePickerCategory); - RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent, - ringtoneType); - RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent); + final AlertController.AlertParams p = mAlertParams; + p.mAdapter = mAdapter; + p.mOnClickListener = mRingtoneClickListener; + p.mLabelColumn = COLUMN_LABEL; + p.mIsSingleChoice = true; + p.mOnItemSelectedListener = this; + if (mShowOkCancelButtons) { + p.mPositiveButtonText = getString(com.android.internal.R.string.ok); + p.mPositiveButtonListener = this; + p.mNegativeButtonText = getString(com.android.internal.R.string.cancel); + p.mPositiveButtonListener = this; + } + p.mOnPrepareListViewListener = this; + + p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE); + if (p.mTitle == null) { + if (mType == RingtoneManager.TYPE_ALARM) { + p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title_alarm); + } else if (mType == RingtoneManager.TYPE_NOTIFICATION) { + p.mTitle = + getString(com.android.internal.R.string.ringtone_picker_title_notification); + } else { + p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title); + } + } - RingtonePickerViewModel.Config pickerConfig = - new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType, - showOkCancelButtons, mAttributesFlags, pickerType); + setupAlert(); - mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig); + ListView listView = mAlert.getListView(); + if (listView != null) { + // List view needs to gain focus in order for RSB to work. + if (!listView.requestFocus()) { + Log.e(TAG, "Unable to gain focus! RSB may not work properly."); + } + } + } + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(SAVE_CLICKED_POS, getCheckedItem()); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_OK) { + // Add the custom ringtone in a separate thread + final AsyncTask<Uri, Void, Uri> installTask = new AsyncTask<Uri, Void, Uri>() { + @Override + protected Uri doInBackground(Uri... params) { + try { + return mRingtoneManager.addCustomExternalRingtone(params[0], mType); + } catch (IOException | IllegalArgumentException e) { + Log.e(TAG, "Unable to add new ringtone", e); + } + return null; + } + + @Override + protected void onPostExecute(Uri ringtoneUri) { + if (ringtoneUri != null) { + requeryForAdapter(); + } else { + // Ringtone was not added, display error Toast + Toast.makeText(RingtonePickerActivity.this, R.string.unable_to_add_ringtone, + Toast.LENGTH_SHORT).show(); + } + } + }; + installTask.execute(data.getData()); + } + } - if (savedInstanceState == null) { - TabbedDialogFragment dialogFragment = new TabbedDialogFragment(); + // Disabled because context menus aren't Material Design :( + /* + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { + int position = ((AdapterContextMenuInfo) menuInfo).position; + + Ringtone ringtone = getRingtone(getRingtoneManagerPosition(position)); + if (ringtone != null && mRingtoneManager.isCustomRingtone(ringtone.getUri())) { + // It's a custom ringtone so we display the context menu + menu.setHeaderTitle(ringtone.getTitle(this)); + menu.add(Menu.NONE, Menu.FIRST, Menu.NONE, R.string.delete_ringtone_text); + } + } - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG); - if (prev != null) { - ft.remove(prev); + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Menu.FIRST: { + int deletedRingtonePos = ((AdapterContextMenuInfo) item.getMenuInfo()).position; + Uri deletedRingtoneUri = getRingtone( + getRingtoneManagerPosition(deletedRingtonePos)).getUri(); + if(mRingtoneManager.deleteExternalRingtone(deletedRingtoneUri)) { + requeryForAdapter(); + } else { + Toast.makeText(this, R.string.unable_to_delete_ringtone, Toast.LENGTH_SHORT) + .show(); + } + return true; + } + default: { + return false; } - ft.addToBackStack(null); - dialogFragment.show(ft, TabbedDialogFragment.TAG); } + } + */ - // The volume keys will control the stream that we are choosing a ringtone for - setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType()); + @Override + public void onDestroy() { + if (mHandler != null) { + mHandler.removeCallbacksAndMessages(null); + } + if (mCursor != null) { + mCursor.close(); + mCursor = null; + } + super.onDestroy(); } - private RingtoneListHandler.Config getSoundListConfig( - RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) { - if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER - && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) { - // This ringtone picker does not require a sound picker. - return null; + public void onPrepareListView(ListView listView) { + // Reset the static item count, as this method can be called multiple times + mStaticItemCount = 0; + + if (mHasDefaultItem) { + mDefaultRingtonePos = addDefaultRingtoneItem(listView); + + if (getCheckedItem() == POS_UNKNOWN && RingtoneManager.isDefault(mExistingUri)) { + setCheckedItem(mDefaultRingtonePos); + } } - // Get whether to show the 'Default' sound item, and the URI to play when it's clicked - boolean hasDefaultSoundItem = - intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + if (mHasSilentItem) { + mSilentPos = addSilentItem(listView); - // The Uri to play when the 'Default' sound item is clicked. - Uri uriForDefaultSoundItem = - intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI); - if (uriForDefaultSoundItem == null) { - uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType); + // The 'Silent' item should use a null Uri + if (getCheckedItem() == POS_UNKNOWN && mExistingUri == null) { + setCheckedItem(mSilentPos); + } } - // Get whether this list has the 'Silent' sound item. - boolean hasSilentSoundItem = - intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); + if (getCheckedItem() == POS_UNKNOWN) { + setCheckedItem(getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri))); + } - // AudioAttributes flags - mAttributesFlags |= intent.getIntExtra( - RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS, - 0 /*defaultValue == no flags*/); + // In the buttonless (watch-only) version, preemptively set our result since we won't + // have another chance to do so before the activity closes. + if (!mShowOkCancelButtons) { + setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri()); + } + // If external storage is available, add a button to install sounds from storage. + if (resolvesMediaFilePicker() + && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + addNewSoundItem(listView); + } - // Get the sound URI whose list item should have a checkmark - Uri existingSoundUri = intent - .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI); + // Enable context menu in ringtone items + registerForContextMenu(listView); + } - return new RingtoneListHandler.Config(hasDefaultSoundItem, - uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri); + /** + * Re-query RingtoneManager for the most recent set of installed ringtones. May move the + * selected item position to match the new position of the chosen sound. + * + * This should only need to happen after adding or removing a ringtone. + */ + private void requeryForAdapter() { + // Refresh and set a new cursor, closing the old one. + initRingtoneManager(); + mAdapter.changeCursor(mCursor); + + // Update checked item location. + int checkedPosition = POS_UNKNOWN; + for (int i = 0; i < mAdapter.getCount(); i++) { + if (mAdapter.getItemId(i) == mCheckedItemId) { + checkedPosition = getListPosition(i); + break; + } + } + if (mHasSilentItem && checkedPosition == POS_UNKNOWN) { + checkedPosition = mSilentPos; + } + setCheckedItem(checkedPosition); + setupAlert(); } - private RingtoneListHandler.Config getVibrationListConfig( - RingtonePickerViewModel.PickerType pickerType, Intent intent) { - if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER - && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) { - // This ringtone picker does not require a vibration picker. + /** + * Adds a static item to the top of the list. A static item is one that is not from the + * RingtoneManager. + * + * @param listView The ListView to add to. + * @param textResId The resource ID of the text for the item. + * @return The position of the inserted item. + */ + private int addStaticItem(ListView listView, int textResId) { + TextView textView = (TextView) getLayoutInflater().inflate( + com.android.internal.R.layout.select_dialog_singlechoice_material, listView, false); + textView.setText(textResId); + listView.addHeaderView(textView); + mStaticItemCount++; + return listView.getHeaderViewsCount() - 1; + } + + private int addDefaultRingtoneItem(ListView listView) { + if (mType == RingtoneManager.TYPE_NOTIFICATION) { + return addStaticItem(listView, R.string.notification_sound_default); + } else if (mType == RingtoneManager.TYPE_ALARM) { + return addStaticItem(listView, R.string.alarm_sound_default); + } + + return addStaticItem(listView, R.string.ringtone_default); + } + + private int addSilentItem(ListView listView) { + return addStaticItem(listView, com.android.internal.R.string.ringtone_silent); + } + + private void addNewSoundItem(ListView listView) { + View view = getLayoutInflater().inflate(R.layout.add_new_sound_item, listView, + false /* attachToRoot */); + TextView text = (TextView)view.findViewById(R.id.add_new_sound_text); + + if (mType == RingtoneManager.TYPE_ALARM) { + text.setText(R.string.add_alarm_text); + } else if (mType == RingtoneManager.TYPE_NOTIFICATION) { + text.setText(R.string.add_notification_text); + } else { + text.setText(R.string.add_ringtone_text); + } + listView.addFooterView(view); + } + + private void initRingtoneManager() { + // Reinstantiate the RingtoneManager. Cursor.requery() was deprecated and calling it + // causes unexpected behavior. + mRingtoneManager = new RingtoneManager(mTargetContext, /* includeParentRingtones */ true); + if (mType != -1) { + mRingtoneManager.setType(mType); + } + mCursor = new LocalizedCursor(mRingtoneManager.getCursor(), getResources(), COLUMN_LABEL); + } + + private Ringtone getRingtone(int ringtoneManagerPosition) { + if (ringtoneManagerPosition < 0) { return null; } + return mRingtoneManager.getRingtone(ringtoneManagerPosition); + } + + private int getCheckedItem() { + return mAlertParams.mCheckedItem; + } - // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked - boolean hasDefaultVibrationItem = - intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false); + private void setCheckedItem(int pos) { + mAlertParams.mCheckedItem = pos; + mCheckedItemId = mAdapter.getItemId(getRingtoneManagerPosition(pos)); + } - // The Uri to play when the 'Default' vibration item is clicked. - Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI); + /* + * On click of Ok/Cancel buttons + */ + public void onClick(DialogInterface dialog, int which) { + boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE; - // Get whether this list has the 'Silent' vibration item. - boolean hasSilentVibrationItem = - intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true); + // Stop playing the previous ringtone + mRingtoneManager.stopPreviousRingtone(); - // Get the vibration URI whose list item should have a checkmark - Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI); + if (positiveResult) { + setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri()); + } else { + setResult(RESULT_CANCELED); + } - return new RingtoneListHandler.Config( - hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem, - existingVibrationUri); + finish(); } - @Override - public void onDestroy() { - mRingtonePickerViewModel.cancelPendingAsyncTasks(); - super.onDestroy(); + /* + * On item selected via keys + */ + public void onItemSelected(AdapterView parent, View view, int position, long id) { + // footer view + if (position >= mCursor.getCount() + mStaticItemCount) { + return; + } + + playRingtone(position, DELAY_MS_SELECTION_PLAYED); + + // In the buttonless (watch-only) version, preemptively set our result since we won't + // have another chance to do so before the activity closes. + if (!mShowOkCancelButtons) { + setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri()); + } + } + + public void onNothingSelected(AdapterView parent) { + } + + private void playRingtone(int position, int delayMs) { + mHandler.removeCallbacks(this); + mSampleRingtonePos = position; + mHandler.postDelayed(this, delayMs); + } + + public void run() { + stopAnyPlayingRingtone(); + if (mSampleRingtonePos == mSilentPos) { + return; + } + + Ringtone ringtone; + if (mSampleRingtonePos == mDefaultRingtonePos) { + if (mDefaultRingtone == null) { + mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem); + } + /* + * Stream type of mDefaultRingtone is not set explicitly here. + * It should be set in accordance with mRingtoneManager of this Activity. + */ + if (mDefaultRingtone != null) { + mDefaultRingtone.setStreamType(mRingtoneManager.inferStreamType()); + } + ringtone = mDefaultRingtone; + mCurrentRingtone = null; + } else { + ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos)); + mCurrentRingtone = ringtone; + } + + if (ringtone != null) { + if (mAttributesFlags != 0) { + ringtone.setAudioAttributes( + new AudioAttributes.Builder(ringtone.getAudioAttributes()) + .setFlags(mAttributesFlags) + .build()); + } + ringtone.play(); + } } @Override protected void onStop() { super.onStop(); - mRingtonePickerViewModel.onStop(isChangingConfigurations()); + + if (!isChangingConfigurations()) { + stopAnyPlayingRingtone(); + } else { + saveAnyPlayingRingtone(); + } } @Override protected void onPause() { super.onPause(); - mRingtonePickerViewModel.onPause(isChangingConfigurations()); + if (!isChangingConfigurations()) { + stopAnyPlayingRingtone(); + } } - /** - * Maps the ringtone picker category to the appropriate PickerType. - * If the category is null or the feature is still not released, then it defaults to sound - * picker. - * - * @param category the ringtone picker category. - * @return the corresponding picker type. - */ - private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) { - if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) { - return RingtonePickerViewModel.PickerType.SOUND_PICKER; + private void setSuccessResultWithRingtone(Uri ringtoneUri) { + setResult(RESULT_OK, + new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri)); + } + + private Uri getCurrentlySelectedRingtoneUri() { + if (getCheckedItem() == POS_UNKNOWN) { + // When the getCheckItem is POS_UNKNOWN, it is not the case we expected. + // We return null for this case. + return null; + } else if (getCheckedItem() == mDefaultRingtonePos) { + // Use the default Uri that they originally gave us. + return mUriForDefaultItem; + } else if (getCheckedItem() == mSilentPos) { + // Use a null Uri for the 'Silent' item. + return null; + } else { + return mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem())); + } + } + + private void saveAnyPlayingRingtone() { + if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) { + sPlayingRingtone = mDefaultRingtone; + } else if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) { + sPlayingRingtone = mCurrentRingtone; + } + } + + private void stopAnyPlayingRingtone() { + if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) { + sPlayingRingtone.stop(); + } + sPlayingRingtone = null; + + if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) { + mDefaultRingtone.stop(); } - switch (category) { - case "android.intent.category.RINGTONE_PICKER_RINGTONE": - return RingtonePickerViewModel.PickerType.RINGTONE_PICKER; - case "android.intent.category.RINGTONE_PICKER_SOUND": - return RingtonePickerViewModel.PickerType.SOUND_PICKER; - case "android.intent.category.RINGTONE_PICKER_VIBRATION": - return RingtonePickerViewModel.PickerType.VIBRATION_PICKER; - default: - Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker."); - return RingtonePickerViewModel.PickerType.SOUND_PICKER; + if (mRingtoneManager != null) { + mRingtoneManager.stopPreviousRingtone(); + } + } + + private int getRingtoneManagerPosition(int listPos) { + return listPos - mStaticItemCount; + } + + private int getListPosition(int ringtoneManagerPos) { + + // If the manager position is -1 (for not found), return that + if (ringtoneManagerPos < 0) return ringtoneManagerPos; + + return ringtoneManagerPos + mStaticItemCount; + } + + private Intent getMediaFilePickerIntent() { + final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT); + chooseFile.setType("audio/*"); + chooseFile.putExtra(Intent.EXTRA_MIME_TYPES, + new String[] { "audio/*", "application/ogg" }); + return chooseFile; + } + + private boolean resolvesMediaFilePicker() { + return getMediaFilePickerIntent().resolveActivity(getPackageManager()) != null; + } + + private static class LocalizedCursor extends CursorWrapper { + + final int mTitleIndex; + final Resources mResources; + String mNamePrefix; + final Pattern mSanitizePattern; + + LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) { + super(cursor); + mTitleIndex = mCursor.getColumnIndex(columnLabel); + mResources = resources; + mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]"); + if (mTitleIndex == -1) { + Log.e(TAG, "No index for column " + columnLabel); + mNamePrefix = null; + } else { + try { + // Build the prefix for the name of the resource to look up + // format is: "ResourcePackageName::ResourceTypeName/" + // (the type name is expected to be "string" but let's not hardcode it). + // Here we use an existing resource "notification_sound_default" which is + // always expected to be found. + mNamePrefix = String.format("%s:%s/%s", + mResources.getResourcePackageName(R.string.notification_sound_default), + mResources.getResourceTypeName(R.string.notification_sound_default), + SOUND_NAME_RES_PREFIX); + } catch (NotFoundException e) { + mNamePrefix = null; + } + } + } + + /** + * Process resource name to generate a valid resource name. + * @param input + * @return a non-null String + */ + private String sanitize(String input) { + if (input == null) { + return ""; + } + return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase(); + } + + @Override + public String getString(int columnIndex) { + final String defaultName = mCursor.getString(columnIndex); + if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) { + return defaultName; + } + TypedValue value = new TypedValue(); + try { + // the name currently in the database is used to derive a name to match + // against resource names in this package + mResources.getValue(mNamePrefix + sanitize(defaultName), value, false); + } catch (NotFoundException e) { + // no localized string, use the default string + return defaultName; + } + if ((value != null) && (value.type == TypedValue.TYPE_STRING)) { + Log.d(TAG, String.format("Replacing name %s with %s", + defaultName, value.string.toString())); + return value.string.toString(); + } else { + Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName); + return defaultName; + } + } + } + + private class BadgedRingtoneAdapter extends CursorAdapter { + private final boolean mIsManagedProfile; + + public BadgedRingtoneAdapter(Context context, Cursor cursor, boolean isManagedProfile) { + super(context, cursor); + mIsManagedProfile = isManagedProfile; + } + + @Override + public long getItemId(int position) { + if (position < 0) { + return position; + } + return super.getItemId(position); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + LayoutInflater inflater = LayoutInflater.from(context); + return inflater.inflate(R.layout.radio_with_work_badge, parent, false); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + // Set text as the title of the ringtone + ((TextView) view.findViewById(R.id.checked_text_view)) + .setText(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX)); + + boolean isWorkRingtone = false; + if (mIsManagedProfile) { + /* + * Display the work icon if the ringtone belongs to a work profile. We can tell that + * a ringtone belongs to a work profile if the picker user is a managed profile, the + * ringtone Uri is in external storage, and either the uri has no user id or has the + * id of the picker user + */ + Uri currentUri = mRingtoneManager.getRingtoneUri(cursor.getPosition()); + int uriUserId = ContentProvider.getUserIdFromUri(currentUri, mPickerUserId); + Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri); + + if (uriUserId == mPickerUserId && uriWithoutUserId.toString() + .startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) { + isWorkRingtone = true; + } + } + + ImageView workIcon = (ImageView) view.findViewById(R.id.work_icon); + if(isWorkRingtone) { + workIcon.setImageDrawable(getPackageManager().getUserBadgeForDensityNoBackground( + UserHandle.of(mPickerUserId), -1 /* density */)); + workIcon.setVisibility(View.VISIBLE); + } else { + workIcon.setVisibility(View.GONE); + } } } -} +}
\ No newline at end of file diff --git a/packages/SoundPicker2/Android.bp b/packages/SoundPicker2/Android.bp new file mode 100644 index 000000000000..f4d8bf2c76b5 --- /dev/null +++ b/packages/SoundPicker2/Android.bp @@ -0,0 +1,46 @@ +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"], +} + +android_library { + name: "SoundPicker2Lib", + srcs: [ + "src/**/*.java", + ], + resource_dirs: [ + "res", + ], + static_libs: [ + "androidx.appcompat_appcompat", + "hilt_android", + "guava", + "androidx.recyclerview_recyclerview", + "androidx-constraintlayout_constraintlayout", + "androidx.viewpager2_viewpager2", + "com.google.android.material_material", + ], +} + +android_app { + name: "SoundPicker2", + defaults: ["platform_app_defaults"], + manifest: "AndroidManifest.xml", + static_libs: ["SoundPicker2Lib"], + platform_apis: true, + certificate: "media", + privileged: true, + + optimize: { + enabled: true, + optimize: true, + shrink: true, + shrink_resources: true, + obfuscate: false, + proguard_compatibility: false, + }, +} diff --git a/packages/SoundPicker2/AndroidManifest.xml b/packages/SoundPicker2/AndroidManifest.xml new file mode 100644 index 000000000000..934b003c605c --- /dev/null +++ b/packages/SoundPicker2/AndroidManifest.xml @@ -0,0 +1,43 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.soundpicker" + android:sharedUserId="android.media"> + + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> + <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> + + <application + android:name=".RingtonePickerApplication" + android:allowBackup="false" + android:label="@string/app_label" + android:theme="@style/Theme.AppCompat" + android:supportsRtl="true"> + <receiver android:name="RingtoneReceiver" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/> + </intent-filter> + </receiver> + + <service android:name="RingtoneOverlayService" /> + + <activity android:name="RingtonePickerActivity" + android:theme="@style/Theme.AppCompat.Dialog" + android:enabled="@*android:bool/config_defaultRingtonePickerEnabled" + android:excludeFromRecents="true" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.RINGTONE_PICKER" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" /> + <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" /> + <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/packages/SoundPicker2/OWNERS b/packages/SoundPicker2/OWNERS new file mode 100644 index 000000000000..5bf46e039e96 --- /dev/null +++ b/packages/SoundPicker2/OWNERS @@ -0,0 +1,2 @@ +# Haptics team works on the SoundPicker +include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS diff --git a/packages/SoundPicker2/res/drawable/ic_add.xml b/packages/SoundPicker2/res/drawable/ic_add.xml new file mode 100644 index 000000000000..22b3fe9176e5 --- /dev/null +++ b/packages/SoundPicker2/res/drawable/ic_add.xml @@ -0,0 +1,24 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:fillColor="?android:attr/colorAccent" + android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/> +</vector>
\ No newline at end of file diff --git a/packages/SoundPicker2/res/drawable/ic_add_padded.xml b/packages/SoundPicker2/res/drawable/ic_add_padded.xml new file mode 100644 index 000000000000..c376867896d0 --- /dev/null +++ b/packages/SoundPicker2/res/drawable/ic_add_padded.xml @@ -0,0 +1,22 @@ +<!-- + 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. +--> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/ic_add" + android:insetTop="4dp" + android:insetRight="4dp" + android:insetBottom="4dp" + android:insetLeft="4dp"/> diff --git a/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml new file mode 100644 index 000000000000..edfc0aba5be7 --- /dev/null +++ b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- + Currently, no file manager app on watch could handle ACTION_GET_CONTENT intent. + Make the visibility to "gone" to prevent failures. + --> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/add_new_sound_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@null" + android:textColor="?android:attr/colorAccent" + android:gravity="center_vertical" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:drawableStart="@drawable/ic_add_padded" + android:drawablePadding="8dp" + android:ellipsize="marquee" + android:visibility="gone" /> diff --git a/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml new file mode 100644 index 000000000000..ee29a3710143 --- /dev/null +++ b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:background="?android:attr/selectableItemBackground" + > + + <CheckedTextView + android:id="@+id/checked_text_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:attr/textColorAlertDialogListItem" + android:gravity="center_vertical" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:drawableStart="?android:attr/listChoiceIndicatorSingle" + android:drawablePadding="8dp" + android:ellipsize="marquee" + android:layout_toLeftOf="@+id/work_icon" + android:maxLines="3" /> + + <ImageView + android:id="@id/work_icon" + android:layout_width="18dp" + android:layout_height="18dp" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:scaleType="centerCrop" + android:layout_marginRight="20dp" /> +</com.android.soundpicker.CheckedListItem> diff --git a/packages/SoundPicker/res/layout/activity_ringtone_picker.xml b/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml index 6fc60801ad3a..6fc60801ad3a 100644 --- a/packages/SoundPicker/res/layout/activity_ringtone_picker.xml +++ b/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml diff --git a/packages/SoundPicker2/res/layout/add_new_sound_item.xml b/packages/SoundPicker2/res/layout/add_new_sound_item.xml new file mode 100644 index 000000000000..024b97ef23be --- /dev/null +++ b/packages/SoundPicker2/res/layout/add_new_sound_item.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:background="?android:attr/selectableItemBackground" + android:focusable="true" + android:clickable="true"> + + <ImageView + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:scaleType="centerCrop" + android:layout_marginRight="24dp" + android:layout_marginLeft="24dp" + android:src="@drawable/ic_add"/> + + <TextView + android:id="@+id/add_new_sound_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:text="@null" + android:textColor="?android:attr/colorAccent" + android:textAppearance="?android:attr/textAppearanceMedium" + android:maxLines="3" + android:gravity="center_vertical" + android:paddingEnd="?android:attr/dialogPreferredPadding" + android:drawablePadding="20dp" + android:ellipsize="marquee"/> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SoundPicker/res/layout/fragment_ringtone_picker.xml b/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml index 787f92ec06d6..787f92ec06d6 100644 --- a/packages/SoundPicker/res/layout/fragment_ringtone_picker.xml +++ b/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml diff --git a/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml b/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml index 7efd91191b79..7efd91191b79 100644 --- a/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml +++ b/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml diff --git a/packages/SoundPicker2/res/layout/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml new file mode 100644 index 000000000000..36ac93ed630b --- /dev/null +++ b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<com.android.soundpicker.CheckedListItem + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:background="?android:attr/selectableItemBackground" + android:focusable="true" + android:clickable="true"> + + <CheckedTextView + android:id="@+id/checked_text_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:attr/textColorAlertDialogListItem" + android:gravity="center_vertical" + android:paddingStart="20dp" + android:paddingEnd="?android:attr/dialogPreferredPadding" + android:drawableStart="?android:attr/listChoiceIndicatorSingle" + android:drawablePadding="20dp" + android:ellipsize="marquee" + android:layout_toLeftOf="@+id/work_icon" + android:maxLines="3"/> + + <ImageView + android:id="@id/work_icon" + android:layout_width="18dp" + android:layout_height="18dp" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:scaleType="centerCrop" + android:layout_marginRight="20dp"/> +</com.android.soundpicker.CheckedListItem> diff --git a/packages/SoundPicker2/res/raw/default_alarm_alert.ogg b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg diff --git a/packages/SoundPicker2/res/raw/default_notification_sound.ogg b/packages/SoundPicker2/res/raw/default_notification_sound.ogg new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/packages/SoundPicker2/res/raw/default_notification_sound.ogg diff --git a/packages/SoundPicker2/res/raw/default_ringtone.ogg b/packages/SoundPicker2/res/raw/default_ringtone.ogg new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/packages/SoundPicker2/res/raw/default_ringtone.ogg diff --git a/packages/SoundPicker2/res/values/config.xml b/packages/SoundPicker2/res/values/config.xml new file mode 100644 index 000000000000..4e237a2f1644 --- /dev/null +++ b/packages/SoundPicker2/res/values/config.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. Do not translate. + + NOTE: The naming convention is "config_camelCaseValue". --> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the + ringtone will be automatically selected when the picker is closed. --> + <bool name="config_showOkCancelButtons">true</bool> +</resources> diff --git a/packages/SoundPicker2/res/values/strings.xml b/packages/SoundPicker2/res/values/strings.xml new file mode 100644 index 000000000000..ab7b95a09028 --- /dev/null +++ b/packages/SoundPicker2/res/values/strings.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Choice in the ringtone picker. If chosen, the default ringtone will be used. --> + <string name="ringtone_default">Default ringtone</string> + + <!-- Choice in the notification sound picker. If chosen, the default notification sound will be + used. --> + <string name="notification_sound_default">Default notification sound</string> + + <!-- Choice in the alarm sound picker. If chosen, the default alarm sound will be used. --> + <string name="alarm_sound_default">Default alarm sound</string> + + <!-- Text for the RingtonePicker item that allows adding a new ringtone. --> + <string name="add_ringtone_text">Add ringtone</string> + <!-- Text for the RingtonePicker item that allows adding a new alarm. --> + <string name="add_alarm_text">Add alarm</string> + <!-- Text for the RingtonePicker item that allows adding a new notification. --> + <string name="add_notification_text">Add notification</string> + <!-- Text for the RingtonePicker item ContextMenu that allows deleting a custom ringtone. --> + <string name="delete_ringtone_text">Delete</string> + <!-- Text for the Toast displayed when adding a custom ringtone fails. --> + <string name="unable_to_add_ringtone">Unable to add custom ringtone</string> + <!-- Text for the Toast displayed when deleting a custom ringtone fails. --> + <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string> + + <!-- Text for the name of the app. [CHAR LIMIT=12] --> + <string name="app_label">Sounds</string> + + <string name="empty_list">The list is empty</string> + <string name="sound_page_title">Sound</string> + <string name="vibration_page_title">Vibration</string> +</resources> diff --git a/packages/SoundPicker2/res/values/styles.xml b/packages/SoundPicker2/res/values/styles.xml new file mode 100644 index 000000000000..d22d9c43d0fb --- /dev/null +++ b/packages/SoundPicker2/res/values/styles.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + + <style name="PickerDialogTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog"> + </style> + +</resources> diff --git a/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java index 4fc2a86537c1..4fc2a86537c1 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java diff --git a/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java new file mode 100644 index 000000000000..819ae987269d --- /dev/null +++ b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.soundpicker; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Checkable; +import android.widget.CheckedTextView; +import android.widget.RelativeLayout; + +/** + * The {@link CheckedListItem} is a layout item that represents a ringtone, and is used in + * {@link RingtonePickerActivity}. It contains the ringtone's name, and a work badge to right of the + * name if the ringtone belongs to a work profile. + */ +public class CheckedListItem extends RelativeLayout implements Checkable { + + public CheckedListItem(Context context) { + super(context); + } + + public CheckedListItem(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void setChecked(boolean checked) { + getCheckedTextView().setChecked(checked); + } + + @Override + public boolean isChecked() { + return getCheckedTextView().isChecked(); + } + + @Override + public void toggle() { + getCheckedTextView().toggle(); + } + + private CheckedTextView getCheckedTextView() { + return (CheckedTextView) findViewById(R.id.checked_text_view); + } + +} diff --git a/packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java index afdbf053ac22..afdbf053ac22 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java diff --git a/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java b/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java index 83d04a345f8b..83d04a345f8b 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java index 6817f534c00b..6817f534c00b 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java index bb38e0e2ecaa..bb38e0e2ecaa 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java index 4ca8943b5fd4..4ca8943b5fd4 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java index f08eb24ec20d..f08eb24ec20d 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java new file mode 100644 index 000000000000..b94ebebd825b --- /dev/null +++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java @@ -0,0 +1,113 @@ +/* + * 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.soundpicker; + +import android.app.Service; +import android.content.Intent; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Environment; +import android.os.FileUtils; +import android.os.IBinder; +import android.provider.MediaStore; +import android.provider.Settings.System; +import android.util.Log; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Service to copy and set customization of default sounds + */ +public class RingtoneOverlayService extends Service { + private static final String TAG = "RingtoneOverlayService"; + private static final boolean DEBUG = false; + + @Override + public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) { + AsyncTask.execute(() -> { + updateRingtones(); + stopSelf(); + }); + + // Try again later if we are killed before we finish. + return Service.START_REDELIVER_INTENT; + } + + @Override + public IBinder onBind(@Nullable final Intent intent) { + return null; + } + + private void updateRingtones() { + copyResourceAndSetAsSound(R.raw.default_ringtone, + System.RINGTONE, Environment.DIRECTORY_RINGTONES); + copyResourceAndSetAsSound(R.raw.default_notification_sound, + System.NOTIFICATION_SOUND, Environment.DIRECTORY_NOTIFICATIONS); + copyResourceAndSetAsSound(R.raw.default_alarm_alert, + System.ALARM_ALERT, Environment.DIRECTORY_ALARMS); + } + + /* If the resource contains any data, copy a resource to the file system, scan it, and set the + * file URI as the default for a sound. */ + private void copyResourceAndSetAsSound(@IdRes final int id, @NonNull final String name, + @NonNull final String subPath) { + final File destDir = Environment.getExternalStoragePublicDirectory(subPath); + if (!destDir.exists() && !destDir.mkdirs()) { + Log.e(TAG, "can't create " + destDir.getAbsolutePath()); + return; + } + + final File dest = new File(destDir, "default_" + name + ".ogg"); + try ( + InputStream is = getResources().openRawResource(id); + FileOutputStream os = new FileOutputStream(dest); + ) { + if (is.available() > 0) { + FileUtils.copy(is, os); + final Uri uri = scanFile(dest); + if (uri != null) { + set(name, uri); + } + } else { + // TODO Shall we remove any former copied resource in this case and unset + // the defaults if we use this event a second time to clear the data? + if (DEBUG) Log.d(TAG, "Resource for " + name + " has no overlay"); + } + } catch (IOException e) { + Log.e(TAG, "Unable to open resource for " + name + ": " + e); + } + } + + private Uri scanFile(@NonNull final File file) { + return MediaStore.scanFile(getContentResolver(), file); + } + + private void set(@NonNull final String name, @NonNull final Uri uri) { + final Uri settingUri = System.getUriFor(name); + RingtoneManager.setActualDefaultRingtoneUri(this, + RingtoneManager.getDefaultType(settingUri), uri); + System.putInt(getContentResolver(), name + "_set", 1); + } +} diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java new file mode 100644 index 000000000000..90a14f9717db --- /dev/null +++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 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 com.android.soundpicker; + +import android.content.Intent; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.UserHandle; +import android.util.Log; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.ViewModelProvider; + +import dagger.hilt.android.AndroidEntryPoint; + +/** + * The {@link RingtonePickerActivity} allows the user to choose one from all of the + * available ringtones. The chosen ringtone's URI will be persisted as a string. + * + * @see RingtoneManager#ACTION_RINGTONE_PICKER + */ +@AndroidEntryPoint(AppCompatActivity.class) +public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity { + + private static final String TAG = "RingtonePickerActivity"; + // TODO: Use the extra keys from RingtoneManager once they're added. + private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY"; + private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT"; + private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI"; + private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT"; + private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI"; + private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false; + + private RingtonePickerViewModel mRingtonePickerViewModel; + private int mAttributesFlags; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ringtone_picker); + + mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class); + + Intent intent = getIntent(); + /** + * Id of the user to which the ringtone picker should list the ringtones + */ + int pickerUserId = UserHandle.myUserId(); + + // Get the types of ringtones to show + int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, + RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN); + + // AudioAttributes flags + mAttributesFlags |= intent.getIntExtra( + RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS, + 0 /*defaultValue == no flags*/); + + boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons); + + String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE); + if (title == null) { + title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType)); + } + String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY); + RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType( + ringtonePickerCategory); + + RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent, + ringtoneType); + RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent); + + RingtonePickerViewModel.Config pickerConfig = + new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType, + showOkCancelButtons, mAttributesFlags, pickerType); + + mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig); + + if (savedInstanceState == null) { + TabbedDialogFragment dialogFragment = new TabbedDialogFragment(); + + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG); + if (prev != null) { + ft.remove(prev); + } + ft.addToBackStack(null); + dialogFragment.show(ft, TabbedDialogFragment.TAG); + } + + // The volume keys will control the stream that we are choosing a ringtone for + setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType()); + } + + private RingtoneListHandler.Config getSoundListConfig( + RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) { + if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER + && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) { + // This ringtone picker does not require a sound picker. + return null; + } + + // Get whether to show the 'Default' sound item, and the URI to play when it's clicked + boolean hasDefaultSoundItem = + intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + + // The Uri to play when the 'Default' sound item is clicked. + Uri uriForDefaultSoundItem = + intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI); + if (uriForDefaultSoundItem == null) { + uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType); + } + + // Get whether this list has the 'Silent' sound item. + boolean hasSilentSoundItem = + intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); + + // AudioAttributes flags + mAttributesFlags |= intent.getIntExtra( + RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS, + 0 /*defaultValue == no flags*/); + + // Get the sound URI whose list item should have a checkmark + Uri existingSoundUri = intent + .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI); + + return new RingtoneListHandler.Config(hasDefaultSoundItem, + uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri); + } + + private RingtoneListHandler.Config getVibrationListConfig( + RingtonePickerViewModel.PickerType pickerType, Intent intent) { + if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER + && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) { + // This ringtone picker does not require a vibration picker. + return null; + } + + // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked + boolean hasDefaultVibrationItem = + intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false); + + // The Uri to play when the 'Default' vibration item is clicked. + Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI); + + // Get whether this list has the 'Silent' vibration item. + boolean hasSilentVibrationItem = + intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true); + + // Get the vibration URI whose list item should have a checkmark + Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI); + + return new RingtoneListHandler.Config( + hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem, + existingVibrationUri); + } + + @Override + public void onDestroy() { + mRingtonePickerViewModel.cancelPendingAsyncTasks(); + super.onDestroy(); + } + + @Override + protected void onStop() { + super.onStop(); + mRingtonePickerViewModel.onStop(isChangingConfigurations()); + } + + @Override + protected void onPause() { + super.onPause(); + mRingtonePickerViewModel.onPause(isChangingConfigurations()); + } + + /** + * Maps the ringtone picker category to the appropriate PickerType. + * If the category is null or the feature is still not released, then it defaults to sound + * picker. + * + * @param category the ringtone picker category. + * @return the corresponding picker type. + */ + private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) { + if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) { + return RingtonePickerViewModel.PickerType.SOUND_PICKER; + } + + switch (category) { + case "android.intent.category.RINGTONE_PICKER_RINGTONE": + return RingtonePickerViewModel.PickerType.RINGTONE_PICKER; + case "android.intent.category.RINGTONE_PICKER_SOUND": + return RingtonePickerViewModel.PickerType.SOUND_PICKER; + case "android.intent.category.RINGTONE_PICKER_VIBRATION": + return RingtonePickerViewModel.PickerType.VIBRATION_PICKER; + default: + Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker."); + return RingtonePickerViewModel.PickerType.SOUND_PICKER; + } + } +} diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java index 48fd4fe2f15e..48fd4fe2f15e 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java index 2c0971121ccd..2c0971121ccd 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java new file mode 100644 index 000000000000..6a349366e744 --- /dev/null +++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 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 com.android.soundpicker; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class RingtoneReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) { + initResourceRingtones(context); + } + } + + private void initResourceRingtones(Context context) { + context.startService( + new Intent(context, RingtoneOverlayService.class)); + } +} diff --git a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java index a37191f33668..a37191f33668 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java diff --git a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java index 50ea9d7d3056..50ea9d7d3056 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java diff --git a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java index 7412c1995b5a..7412c1995b5a 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java diff --git a/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java index 179068e9f20f..179068e9f20f 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java +++ b/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java diff --git a/packages/SoundPicker/tests/Android.bp b/packages/SoundPicker2/tests/Android.bp index c38426fa6746..d88d442afa17 100644 --- a/packages/SoundPicker/tests/Android.bp +++ b/packages/SoundPicker2/tests/Android.bp @@ -17,7 +17,7 @@ package { } android_test { - name: "SoundPickerTests", + name: "SoundPicker2Tests", certificate: "platform", libs: [ "android.test.runner", @@ -30,7 +30,7 @@ android_test { "androidx.test.ext.truth", "mockito-target-minus-junit4", "guava-android-testlib", - "SoundPickerLib", + "SoundPicker2Lib", ], srcs: [ "src/**/*.java", diff --git a/packages/SoundPicker/tests/AndroidManifest.xml b/packages/SoundPicker2/tests/AndroidManifest.xml index 295aeb1faa55..295aeb1faa55 100644 --- a/packages/SoundPicker/tests/AndroidManifest.xml +++ b/packages/SoundPicker2/tests/AndroidManifest.xml diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java index 80e71e200a53..80e71e200a53 100644 --- a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java +++ b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java index cde6c76d27ff..cde6c76d27ff 100644 --- a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java +++ b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 2509cfd4af40..211af908a877 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -29,3 +29,10 @@ flag { "Notification Manager Service" bug: "299448097" } + +flag { + name: "scene_container" + namespace: "systemui" + description: "Enables the scene container framework go/flexiglass." + bug: "283121968" +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index ce96bbfc7976..abc62c4682cc 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.round import com.android.compose.animation.scene.transformation.PropertyTransformation +import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.modifiers.thenIf import com.android.compose.ui.util.lerp @@ -196,29 +197,44 @@ private fun shouldDrawElement( state.fromScene == state.toScene || !layoutImpl.isTransitionReady(state) || state.fromScene !in element.sceneValues || - state.toScene !in element.sceneValues || - !isSharedElementEnabled(layoutImpl, state, element.key) + state.toScene !in element.sceneValues ) { return true } - val otherScene = - layoutImpl.scenes.getValue( - if (scene.key == state.fromScene) { - state.toScene - } else { - state.fromScene - } - ) - - // When the element is shared, draw the one in the highest scene unless it is a background, i.e. - // it is usually drawn below everything else. - val isHighestScene = scene.zIndex > otherScene.zIndex - return if (element.key.isBackground) { - !isHighestScene - } else { - isHighestScene + val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key) + if (sharedTransformation?.enabled == false) { + return true } + + return shouldDrawOrComposeSharedElement( + layoutImpl, + state, + scene.key, + element.key, + sharedTransformation, + ) +} + +internal fun shouldDrawOrComposeSharedElement( + layoutImpl: SceneTransitionLayoutImpl, + transition: TransitionState.Transition, + scene: SceneKey, + element: ElementKey, + sharedTransformation: SharedElementTransformation? +): Boolean { + val scenePicker = sharedTransformation?.scenePicker ?: DefaultSharedElementScenePicker + val fromScene = transition.fromScene + val toScene = transition.toScene + + return scenePicker.sceneDuringTransition( + element = element, + fromScene = fromScene, + toScene = toScene, + progress = transition::progress, + fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex, + toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex, + ) == scene } private fun isSharedElementEnabled( @@ -226,6 +242,14 @@ private fun isSharedElementEnabled( transition: TransitionState.Transition, element: ElementKey, ): Boolean { + return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true +} + +internal fun sharedElementTransformation( + layoutImpl: SceneTransitionLayoutImpl, + transition: TransitionState.Transition, + element: ElementKey, +): SharedElementTransformation? { val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene) val sharedInFromScene = spec.transformations(element, transition.fromScene).shared val sharedInToScene = spec.transformations(element, transition.toScene).shared @@ -238,7 +262,7 @@ private fun isSharedElementEnabled( ) } - return sharedInFromScene?.enabled ?: true + return sharedInFromScene } /** 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 index 6dbeb69ff450..fa385d014ccb 100644 --- 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 @@ -22,6 +22,8 @@ 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.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.ui.Modifier @@ -60,7 +62,16 @@ internal fun MovableElement( // which case we still need to draw it. val picture = remember { Picture() } - if (shouldComposeMovableElement(layoutImpl, scene.key, element)) { + // Whether we should compose the movable element here. The scene picker logic to know in + // which scene we should compose/draw a movable element might depend on the current + // transition progress, so we put this in a derivedStateOf to prevent many recompositions + // during the transition. + val shouldComposeMovableElement by + remember(layoutImpl, scene.key, element) { + derivedStateOf { shouldComposeMovableElement(layoutImpl, scene.key, element) } + } + + if (shouldComposeMovableElement) { Box( Modifier.drawWithCache { val width = size.width.toInt() @@ -172,14 +183,13 @@ private fun shouldComposeMovableElement( 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 - } + return shouldDrawOrComposeSharedElement( + layoutImpl, + transitionState, + scene, + element.key, + sharedElementTransformation(layoutImpl, transitionState, element.key), + ) } private class MovableElementScopeImpl( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index 49669775fedd..7b7ddfa5ec4e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -120,8 +120,14 @@ interface TransitionBuilder : PropertyTransformationBuilder { * * @param enabled whether the matched element(s) should actually be shared in this transition. * Defaults to true. + * @param scenePicker the [SharedElementScenePicker] to use when deciding in which scene we + * should draw or compose this shared element. */ - fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true) + fun sharedElement( + matcher: ElementMatcher, + enabled: Boolean = true, + scenePicker: SharedElementScenePicker = DefaultSharedElementScenePicker, + ) /** * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and @@ -144,6 +150,44 @@ interface TransitionBuilder : PropertyTransformationBuilder { fun reversed(builder: TransitionBuilder.() -> Unit) } +interface SharedElementScenePicker { + /** + * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or + * composed (when using `MovableElement(key)`) during the transition from [fromScene] to + * [toScene]. + */ + fun sceneDuringTransition( + element: ElementKey, + fromScene: SceneKey, + toScene: SceneKey, + progress: () -> Float, + fromSceneZIndex: Float, + toSceneZIndex: Float, + ): SceneKey +} + +object DefaultSharedElementScenePicker : SharedElementScenePicker { + override fun sceneDuringTransition( + element: ElementKey, + fromScene: SceneKey, + toScene: SceneKey, + progress: () -> Float, + fromSceneZIndex: Float, + toSceneZIndex: Float + ): SceneKey { + // By default shared elements are drawn in the highest scene possible, unless it is a + // background. + return if ( + (fromSceneZIndex > toSceneZIndex && !element.isBackground) || + (fromSceneZIndex < toSceneZIndex && element.isBackground) + ) { + fromScene + } else { + toScene + } + } +} + @TransitionDsl interface PropertyTransformationBuilder { /** diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index f1c27178391c..d2bfd91842ae 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -111,8 +111,12 @@ internal class TransitionBuilderImpl : TransitionBuilder { range = null } - override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) { - transformations.add(SharedElementTransformation(matcher, enabled)) + override fun sharedElement( + matcher: ElementMatcher, + enabled: Boolean, + scenePicker: SharedElementScenePicker, + ) { + transformations.add(SharedElementTransformation(matcher, enabled, scenePicker)) } override fun timestampRange( diff --git a/packages/SystemUI/compose/scene/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..0db8469466ef 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -21,6 +21,7 @@ import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.Scene import com.android.compose.animation.scene.SceneTransitionLayoutImpl +import com.android.compose.animation.scene.SharedElementScenePicker import com.android.compose.animation.scene.TransitionState /** A transformation applied to one or more elements during a transition. */ @@ -48,6 +49,7 @@ sealed interface Transformation { internal class SharedElementTransformation( override val matcher: ElementMatcher, internal val enabled: Boolean, + internal val scenePicker: SharedElementScenePicker, ) : Transformation /** A transformation that is applied on the element during the whole transition. */ diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt index 27f0948d5377..790665aebe3e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt @@ -116,9 +116,14 @@ private fun Grid( if (sizeCache.rowHeights.size != rows) { sizeCache.rowHeights = IntArray(rows) { 0 } + } else { + repeat(rows) { i -> sizeCache.rowHeights[i] = 0 } } + if (sizeCache.columnWidths.size != columns) { sizeCache.columnWidths = IntArray(columns) { 0 } + } else { + repeat(columns) { i -> sizeCache.columnWidths[i] = 0 } } val totalHorizontalSpacingBetweenChildren = 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 index 4204cd5f0da0..83af630ab098 100644 --- 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 @@ -144,7 +144,36 @@ class MovableElementTest { 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) }, + transition = { + spec = tween(durationMillis = 16 * 4, easing = LinearEasing) + sharedElement( + TestElements.Foo, + scenePicker = + object : SharedElementScenePicker { + override fun sceneDuringTransition( + element: ElementKey, + fromScene: SceneKey, + toScene: SceneKey, + progress: () -> Float, + fromSceneZIndex: Float, + toSceneZIndex: Float + ): SceneKey { + assertThat(fromScene).isEqualTo(TestScenes.SceneA) + assertThat(toScene).isEqualTo(TestScenes.SceneB) + assertThat(fromSceneZIndex).isEqualTo(0) + assertThat(toSceneZIndex).isEqualTo(1) + + // Compose Foo in Scene A if progress < 0.65f, otherwise compose it + // in Scene B. + return if (progress() < 0.65f) { + TestScenes.SceneA + } else { + TestScenes.SceneB + } + } + } + ) + }, fromScene = TestScenes.SceneA, toScene = TestScenes.SceneB, ) { @@ -170,9 +199,12 @@ class MovableElementTest { at(32) { // During the transition, there is a single counter that is moved, with the current - // value. + // value. Given that progress = 0.5f, it is currently composed in SceneA. rule - .onNode(hasText("count: 3")) + .onNode( + hasText("count: 3") and + hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA)) + ) .assertIsDisplayed() .assertSizeIsEqualTo(75.dp, 75.dp) @@ -186,6 +218,26 @@ class MovableElementTest { .isEqualTo(1) } + at(48) { + // During the transition, there is a single counter that is moved, with the current + // value. Given that progress = 0.75f, it is currently composed in SceneB. + rule + .onNode( + hasText("count: 3") and + hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB)) + ) + .assertIsDisplayed() + + // 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 diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 75e71e414262..9ac1e9f58dbc 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -730,6 +730,9 @@ <!-- Whether the communal service should be enabled --> <bool name="config_communalServiceEnabled">false</bool> + <!-- Component names of allowed communal widgets --> + <string-array name="config_communalWidgetAllowlist" translatable="false" /> + <!-- Component name of communal source service --> <string name="config_communalSourceComponent" translatable="false">@null</string> diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt index c4f1ce8f5d3b..b186018ba78a 100644 --- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt +++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt @@ -33,11 +33,11 @@ import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD import android.util.Log -import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.settings.SecureSettings import java.io.PrintWriter import javax.inject.Inject @@ -50,6 +50,7 @@ class ActiveUnlockConfig @Inject constructor( @Main private val handler: Handler, private val secureSettings: SecureSettings, private val contentResolver: ContentResolver, + private val selectedUserInteractor: SelectedUserInteractor, dumpManager: DumpManager ) : Dumpable { @@ -134,7 +135,7 @@ class ActiveUnlockConfig @Inject constructor( ) ) - onChange(true, ArrayList(), 0, getCurrentUser()) + onChange(true, ArrayList(), 0, selectedUserInteractor.getSelectedUserId()) } private fun registerUri(uris: Collection<Uri>) { @@ -153,29 +154,31 @@ class ActiveUnlockConfig @Inject constructor( flags: Int, userId: Int ) { - if (getCurrentUser() != userId) { + if (selectedUserInteractor.getSelectedUserId() != userId) { return } if (selfChange || uris.contains(wakeUri)) { requestActiveUnlockOnWakeup = secureSettings.getIntForUser( - ACTIVE_UNLOCK_ON_WAKE, 0, getCurrentUser()) == 1 + ACTIVE_UNLOCK_ON_WAKE, 0, selectedUserInteractor.getSelectedUserId()) == 1 } if (selfChange || uris.contains(unlockIntentUri)) { requestActiveUnlockOnUnlockIntent = secureSettings.getIntForUser( - ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0, getCurrentUser()) == 1 + ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0, + selectedUserInteractor.getSelectedUserId()) == 1 } if (selfChange || uris.contains(bioFailUri)) { requestActiveUnlockOnBioFail = secureSettings.getIntForUser( - ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, getCurrentUser()) == 1 + ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, + selectedUserInteractor.getSelectedUserId()) == 1 } if (selfChange || uris.contains(faceErrorsUri)) { processStringArray( secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS, - getCurrentUser()), + selectedUserInteractor.getSelectedUserId()), faceErrorsToTriggerBiometricFailOn, setOf(FACE_ERROR_TIMEOUT)) } @@ -183,7 +186,7 @@ class ActiveUnlockConfig @Inject constructor( if (selfChange || uris.contains(faceAcquireInfoUri)) { processStringArray( secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, - getCurrentUser()), + selectedUserInteractor.getSelectedUserId()), faceAcquireInfoToTriggerBiometricFailOn, emptySet()) } @@ -192,7 +195,7 @@ class ActiveUnlockConfig @Inject constructor( processStringArray( secureSettings.getStringForUser( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, - getCurrentUser()), + selectedUserInteractor.getSelectedUserId()), onUnlockIntentWhenBiometricEnrolled, setOf(BiometricType.NONE.intValue)) } @@ -201,7 +204,7 @@ class ActiveUnlockConfig @Inject constructor( processStringArray( secureSettings.getStringForUser( ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, - getCurrentUser()), + selectedUserInteractor.getSelectedUserId()), wakeupsConsideredUnlockIntents, setOf(WAKE_REASON_UNFOLD_DEVICE)) } @@ -210,7 +213,7 @@ class ActiveUnlockConfig @Inject constructor( processStringArray( secureSettings.getStringForUser( ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, - getCurrentUser()), + selectedUserInteractor.getSelectedUserId()), wakeupsToForceDismissKeyguard, setOf(WAKE_REASON_UNFOLD_DEVICE)) } @@ -316,7 +319,8 @@ class ActiveUnlockConfig @Inject constructor( keyguardUpdateMonitor?.let { val anyFaceEnrolled = it.isFaceEnrolled val anyFingerprintEnrolled = - it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser()) + it.getCachedIsUnlockWithFingerprintPossible( + selectedUserInteractor.getSelectedUserId()) val udfpsEnrolled = it.isUdfpsEnrolled if (!anyFaceEnrolled && !anyFingerprintEnrolled) { @@ -371,7 +375,8 @@ class ActiveUnlockConfig @Inject constructor( "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}") pw.println(" faceEnrolled=${it.isFaceEnrolled}") pw.println(" fpEnrolled=${ - it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())}") + it.getCachedIsUnlockWithFingerprintPossible( + selectedUserInteractor.getSelectedUserId())}") pw.println(" udfpsEnrolled=${it.isUdfpsEnrolled}") } ?: pw.println(" keyguardUpdateMonitor is uninitialized") } diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java index 207f3440d38b..58bbdeb35ea0 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -45,6 +45,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.keyguard.dagger.KeyguardBouncerScope; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import java.util.NoSuchElementException; @@ -63,6 +64,7 @@ public class AdminSecondaryLockScreenController { private Handler mHandler; private IKeyguardClient mClient; private KeyguardSecurityCallback mKeyguardCallback; + private SelectedUserInteractor mSelectedUserInteractor; private final ServiceConnection mConnection = new ServiceConnection() { @Override @@ -76,7 +78,7 @@ public class AdminSecondaryLockScreenController { } catch (RemoteException e) { // Failed to link to death, just dismiss and unbind the service for now. Log.e(TAG, "Lost connection to secondary lockscreen service", e); - dismiss(KeyguardUpdateMonitor.getCurrentUser()); + dismiss(mSelectedUserInteractor.getSelectedUserId()); } } } @@ -110,7 +112,7 @@ public class AdminSecondaryLockScreenController { mView.setChildSurfacePackage(surfacePackage); } else { mHandler.post(() -> { - dismiss(KeyguardUpdateMonitor.getCurrentUser()); + dismiss(mSelectedUserInteractor.getSelectedUserId()); }); } } @@ -131,7 +133,7 @@ public class AdminSecondaryLockScreenController { protected SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { - final int userId = KeyguardUpdateMonitor.getCurrentUser(); + final int userId = mSelectedUserInteractor.getSelectedUserId(); mUpdateMonitor.registerCallback(mUpdateCallback); if (mClient != null) { @@ -158,7 +160,7 @@ public class AdminSecondaryLockScreenController { private AdminSecondaryLockScreenController(Context context, KeyguardSecurityContainer parent, KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback, - @Main Handler handler) { + @Main Handler handler, SelectedUserInteractor selectedUserInteractor) { mContext = context; mHandler = handler; mParent = parent; @@ -166,6 +168,7 @@ public class AdminSecondaryLockScreenController { mKeyguardCallback = callback; mView = new AdminSecurityView(mContext, mSurfaceHolderCallback); mView.setId(View.generateViewId()); + mSelectedUserInteractor = selectedUserInteractor; } /** @@ -218,13 +221,13 @@ public class AdminSecondaryLockScreenController { } } catch (RemoteException e) { Log.e(TAG, "Error in onCreateKeyguardSurface", e); - dismiss(KeyguardUpdateMonitor.getCurrentUser()); + dismiss(mSelectedUserInteractor.getSelectedUserId()); } } private void dismiss(int userId) { mHandler.removeCallbacksAndMessages(null); - if (mView.isAttachedToWindow() && userId == KeyguardUpdateMonitor.getCurrentUser()) { + if (mView.isAttachedToWindow() && userId == mSelectedUserInteractor.getSelectedUserId()) { hide(); if (mKeyguardCallback != null) { mKeyguardCallback.dismiss(/* securityVerified= */ true, userId, @@ -265,19 +268,24 @@ public class AdminSecondaryLockScreenController { private final KeyguardSecurityContainer mParent; private final KeyguardUpdateMonitor mUpdateMonitor; private final Handler mHandler; + private final SelectedUserInteractor mSelectedUserInteractor; @Inject - public Factory(Context context, KeyguardSecurityContainer parent, - KeyguardUpdateMonitor updateMonitor, @Main Handler handler) { + public Factory(Context context, + KeyguardSecurityContainer parent, + KeyguardUpdateMonitor updateMonitor, + @Main Handler handler, + SelectedUserInteractor selectedUserInteractor) { mContext = context; mParent = parent; mUpdateMonitor = updateMonitor; mHandler = handler; + mSelectedUserInteractor = selectedUserInteractor; } public AdminSecondaryLockScreenController create(KeyguardSecurityCallback callback) { return new AdminSecondaryLockScreenController(mContext, mParent, mUpdateMonitor, - callback, mHandler); + callback, mHandler, mSelectedUserInteractor); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java index f7e8eb492584..5de370f22eb8 100644 --- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java +++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java @@ -43,6 +43,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.EmergencyDialerConstants; import com.android.systemui.util.ViewController; @@ -67,6 +68,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { private LockPatternUtils mLockPatternUtils; private Executor mMainExecutor; private Executor mBackgroundExecutor; + private SelectedUserInteractor mSelectedUserInteractor; private final KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { @@ -96,7 +98,8 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { ShadeController shadeController, @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils, - Executor mainExecutor, Executor backgroundExecutor) { + Executor mainExecutor, Executor backgroundExecutor, + SelectedUserInteractor selectedUserInteractor) { super(view); mConfigurationController = configurationController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -109,6 +112,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { mLockPatternUtils = lockPatternUtils; mMainExecutor = mainExecutor; mBackgroundExecutor = backgroundExecutor; + mSelectedUserInteractor = selectedUserInteractor; } @Override @@ -142,7 +146,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { mBackgroundExecutor.execute(() -> { boolean isInCall = mTelecomManager != null && mTelecomManager.isInCall(); boolean isSecure = mLockPatternUtils - .isSecure(KeyguardUpdateMonitor.getCurrentUser()); + .isSecure(mSelectedUserInteractor.getSelectedUserId()); mMainExecutor.execute(() -> mView.updateEmergencyCallButton( /* isInCall= */ isInCall, /* hasTelephonyRadio= */ getContext().getPackageManager() @@ -192,7 +196,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { getContext().startActivityAsUser(emergencyDialIntent, ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(), - new UserHandle(KeyguardUpdateMonitor.getCurrentUser())); + new UserHandle(mSelectedUserInteractor.getSelectedUserId())); } }); }); @@ -218,6 +222,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { private final LockPatternUtils mLockPatternUtils; private final Executor mMainExecutor; private final Executor mBackgroundExecutor; + private final SelectedUserInteractor mSelectedUserInteractor; @Inject public Factory(ConfigurationController configurationController, @@ -227,7 +232,8 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils, @Main Executor mainExecutor, - @Background Executor backgroundExecutor) { + @Background Executor backgroundExecutor, + SelectedUserInteractor selectedUserInteractor) { mConfigurationController = configurationController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -240,6 +246,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { mLockPatternUtils = lockPatternUtils; mMainExecutor = mainExecutor; mBackgroundExecutor = backgroundExecutor; + mSelectedUserInteractor = selectedUserInteractor; } /** Construct an {@link com.android.keyguard.EmergencyButtonController}. */ @@ -247,7 +254,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { return new EmergencyButtonController(view, mConfigurationController, mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager, mShadeController, mTelecomManager, mMetricsLogger, mLockPatternUtils, - mMainExecutor, mBackgroundExecutor); + mMainExecutor, mBackgroundExecutor, mSelectedUserInteractor); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index 167bd59d4e8d..dad440083f70 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -34,10 +34,11 @@ import com.android.internal.widget.LockscreenCredential; import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback; import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.res.R; import com.android.systemui.classifier.FalsingClassifier; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import java.util.HashMap; import java.util.Map; @@ -80,9 +81,9 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) { super(view, securityMode, keyguardSecurityCallback, emergencyButtonController, - messageAreaControllerFactory, featureFlags); + messageAreaControllerFactory, featureFlags, selectedUserInteractor); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -104,7 +105,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback); // if the user is currently locked out, enforce it. long deadline = mLockPatternUtils.getLockoutAttemptDeadline( - KeyguardUpdateMonitor.getCurrentUser()); + mSelectedUserInteractor.getSelectedUserId()); if (shouldLockout(deadline)) { handleAttemptLockout(deadline); } @@ -175,7 +176,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey } void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) { - boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; + boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId; if (matched) { getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0); if (dismissKeyguard) { @@ -212,7 +213,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey mPendingLockCheck.cancel(false); } - final int userId = KeyguardUpdateMonitor.getCurrentUser(); + final int userId = mSelectedUserInteractor.getSelectedUserId(); if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) { // to avoid accidental lockout, only count attempts that are long enough to be a // real password. This may require some tweaking. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt index e6a2bfa1af12..d26caa985431 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt @@ -29,6 +29,7 @@ import com.android.keyguard.KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEv import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.SessionTracker +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import java.io.PrintWriter import javax.inject.Inject @@ -42,7 +43,8 @@ import javax.inject.Inject class KeyguardBiometricLockoutLogger @Inject constructor( private val uiEventLogger: UiEventLogger, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val sessionTracker: SessionTracker + private val sessionTracker: SessionTracker, + private val selectedUserInteractor: SelectedUserInteractor ) : CoreStartable { private var fingerprintLockedOut = false private var faceLockedOut = false @@ -52,7 +54,7 @@ class KeyguardBiometricLockoutLogger @Inject constructor( override fun start() { mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged( - KeyguardUpdateMonitor.getCurrentUser()) + selectedUserInteractor.getSelectedUserId()) keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback) } @@ -79,7 +81,7 @@ class KeyguardBiometricLockoutLogger @Inject constructor( } override fun onStrongAuthStateChanged(userId: Int) { - if (userId != KeyguardUpdateMonitor.getCurrentUser()) { + if (userId != selectedUserInteractor.getSelectedUserId()) { return } val strongAuthFlags = keyguardUpdateMonitor.strongAuthTracker diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 29ce18c60833..b3094838452a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -29,7 +29,6 @@ import android.view.inputmethod.InputMethodManager; 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.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.bouncer.ui.BouncerMessageView; import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder; @@ -38,7 +37,9 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.log.BouncerLogger; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -51,7 +52,6 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final SecurityMode mSecurityMode; private final KeyguardSecurityCallback mKeyguardSecurityCallback; - private final EmergencyButton mEmergencyButton; private final EmergencyButtonController mEmergencyButtonController; private boolean mPaused; protected KeyguardMessageAreaController<BouncerKeyguardMessageArea> mMessageAreaController; @@ -61,18 +61,20 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> // state for the current security method. private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {}; private final FeatureFlags mFeatureFlags; + protected final SelectedUserInteractor mSelectedUserInteractor; protected KeyguardInputViewController(T view, SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback, EmergencyButtonController emergencyButtonController, @Nullable KeyguardMessageAreaController.Factory messageAreaControllerFactory, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + SelectedUserInteractor selectedUserInteractor) { super(view); mSecurityMode = securityMode; mKeyguardSecurityCallback = keyguardSecurityCallback; - mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button); mEmergencyButtonController = emergencyButtonController; mFeatureFlags = featureFlags; + mSelectedUserInteractor = selectedUserInteractor; if (messageAreaControllerFactory != null) { try { BouncerKeyguardMessageArea kma = view.requireViewById(R.id.bouncer_message_area); @@ -207,6 +209,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final DevicePostureController mDevicePostureController; private final KeyguardViewController mKeyguardViewController; private final FeatureFlags mFeatureFlags; + private final SelectedUserInteractor mSelectedUserInteractor; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -219,7 +222,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> EmergencyButtonController.Factory emergencyButtonControllerFactory, DevicePostureController devicePostureController, KeyguardViewController keyguardViewController, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -234,6 +237,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mDevicePostureController = devicePostureController; mKeyguardViewController = keyguardViewController; mFeatureFlags = featureFlags; + mSelectedUserInteractor = selectedUserInteractor; } /** Create a new {@link KeyguardInputViewController}. */ @@ -248,32 +252,32 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mLatencyTracker, mFalsingCollector, emergencyButtonController, mMessageAreaControllerFactory, - mDevicePostureController, mFeatureFlags); + mDevicePostureController, mFeatureFlags, mSelectedUserInteractor); } else if (keyguardInputView instanceof KeyguardPasswordView) { return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mInputMethodManager, emergencyButtonController, mMainExecutor, mResources, mFalsingCollector, mKeyguardViewController, - mDevicePostureController, mFeatureFlags); + mDevicePostureController, mFeatureFlags, mSelectedUserInteractor); } else if (keyguardInputView instanceof KeyguardPINView) { return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, emergencyButtonController, mFalsingCollector, - mDevicePostureController, mFeatureFlags); + mDevicePostureController, mFeatureFlags, mSelectedUserInteractor); } else if (keyguardInputView instanceof KeyguardSimPinView) { return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, - emergencyButtonController, mFeatureFlags); + emergencyButtonController, mFeatureFlags, mSelectedUserInteractor); } else if (keyguardInputView instanceof KeyguardSimPukView) { return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, - emergencyButtonController, mFeatureFlags); + emergencyButtonController, mFeatureFlags, mSelectedUserInteractor); } throw new RuntimeException("Unable to find controller for " + keyguardInputView); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 959cf6fb8565..2e212552caaa 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -39,11 +39,12 @@ import android.widget.TextView.OnEditorActionListener; 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.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.List; @@ -51,7 +52,6 @@ import java.util.List; public class KeyguardPasswordViewController extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> { - private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms private final KeyguardSecurityCallback mKeyguardSecurityCallback; private final DevicePostureController mPostureController; private final DevicePostureController.Callback mPostureCallback = posture -> @@ -112,10 +112,11 @@ public class KeyguardPasswordViewController FalsingCollector falsingCollector, KeyguardViewController keyguardViewController, DevicePostureController postureController, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + SelectedUserInteractor selectedUserInteractor) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, falsingCollector, - emergencyButtonController, featureFlags); + emergencyButtonController, featureFlags, selectedUserInteractor); mKeyguardSecurityCallback = keyguardSecurityCallback; mInputMethodManager = inputMethodManager; mPostureController = postureController; @@ -132,7 +133,8 @@ public class KeyguardPasswordViewController @Override protected void onViewAttached() { super.onViewAttached(); - mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); + mPasswordEntry.setTextOperationUser( + UserHandle.of(mSelectedUserInteractor.getSelectedUserId())); mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); @@ -164,13 +166,6 @@ public class KeyguardPasswordViewController // If there's more than one IME, enable the IME switcher button updateSwitchImeButton(); - - // When we the current user is switching, InputMethodManagerService sometimes has not - // switched internal state yet here. As a quick workaround, we check the keyboard state - // again. - // TODO: Remove this workaround by ensuring such a race condition never happens. - mMainExecutor.executeDelayed( - this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON); } @Override @@ -187,7 +182,8 @@ public class KeyguardPasswordViewController @Override void resetState() { - mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); + mPasswordEntry.setTextOperationUser( + UserHandle.of(mSelectedUserInteractor.getSelectedUserId())); mMessageAreaController.setMessage(getInitialMessageResId()); final boolean wasDisabled = mPasswordEntry.isEnabled(); mView.setPasswordEntryEnabled(true); @@ -280,7 +276,7 @@ public class KeyguardPasswordViewController final boolean shouldIncludeAuxiliarySubtypes) { final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodListAsUser( - UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); + UserHandle.of(mSelectedUserInteractor.getSelectedUserId())); // Number of the filtered IMEs int filteredImisCount = 0; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 57151ae32db0..db7ff888356c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -36,11 +36,12 @@ import com.android.internal.widget.LockPatternView.Cell; import com.android.internal.widget.LockscreenCredential; import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.res.R; import com.android.systemui.classifier.FalsingClassifier; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import java.util.HashMap; import java.util.List; @@ -110,7 +111,7 @@ public class KeyguardPatternViewController mPendingLockCheck.cancel(false); } - final int userId = KeyguardUpdateMonitor.getCurrentUser(); + final int userId = mSelectedUserInteractor.getSelectedUserId(); if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { // Treat single-sized patterns as erroneous taps. if (pattern.size() == 1) { @@ -163,7 +164,7 @@ public class KeyguardPatternViewController private void onPatternChecked(int userId, boolean matched, int timeoutMs, boolean isValidPattern) { - boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; + boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId; if (matched) { getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0); if (dismissKeyguard) { @@ -198,9 +199,10 @@ public class KeyguardPatternViewController FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, KeyguardMessageAreaController.Factory messageAreaControllerFactory, - DevicePostureController postureController, FeatureFlags featureFlags) { + DevicePostureController postureController, FeatureFlags featureFlags, + SelectedUserInteractor selectedUserInteractor) { super(view, securityMode, keyguardSecurityCallback, emergencyButtonController, - messageAreaControllerFactory, featureFlags); + messageAreaControllerFactory, featureFlags, selectedUserInteractor); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -223,7 +225,7 @@ public class KeyguardPatternViewController mLockPatternView.setOnPatternListener(new UnlockPatternListener()); mLockPatternView.setSaveEnabled(false); mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( - KeyguardUpdateMonitor.getCurrentUser())); + mSelectedUserInteractor.getSelectedUserId())); mLockPatternView.setOnTouchListener((v, event) -> { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mFalsingCollector.avoidGesture(); @@ -243,7 +245,7 @@ public class KeyguardPatternViewController mPostureController.addCallback(mPostureCallback); // if the user is currently locked out, enforce it. long deadline = mLockPatternUtils.getLockoutAttemptDeadline( - KeyguardUpdateMonitor.getCurrentUser()); + mSelectedUserInteractor.getSelectedUserId()); if (deadline != 0) { handleAttemptLockout(deadline); } @@ -266,7 +268,7 @@ public class KeyguardPatternViewController public void reset() { // reset lock pattern mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( - KeyguardUpdateMonitor.getCurrentUser())); + mSelectedUserInteractor.getSelectedUserId())); mLockPatternView.enableInput(); mLockPatternView.setEnabled(true); mLockPatternView.clearPattern(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index aacf8662b2b8..b7d1171431a8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -25,9 +25,10 @@ import android.view.View.OnTouchListener; 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; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView> extends KeyguardAbsKeyInputViewController<T> { @@ -60,10 +61,11 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB LiftToActivateListener liftToActivateListener, EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + SelectedUserInteractor selectedUserInteractor) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, falsingCollector, - emergencyButtonController, featureFlags); + emergencyButtonController, featureFlags, selectedUserInteractor); mLiftToActivateListener = liftToActivateListener; mFalsingCollector = falsingCollector; mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); @@ -74,7 +76,7 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB super.onViewAttached(); boolean showAnimations = !mLockPatternUtils - .isPinEnhancedPrivacyEnabled(KeyguardUpdateMonitor.getCurrentUser()); + .isPinEnhancedPrivacyEnabled(mSelectedUserInteractor.getSelectedUserId()); mPasswordEntry.setShowPassword(showAnimations); for (NumPadKey button : mView.getButtons()) { button.setOnTouchListener((v, event) -> { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 9a788688c9d1..947d90f488d1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -23,11 +23,12 @@ import android.view.View; 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.flags.Flags; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; public class KeyguardPinViewController extends KeyguardPinBasedInputViewController<KeyguardPINView> { @@ -55,17 +56,17 @@ public class KeyguardPinViewController EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector, DevicePostureController postureController, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - emergencyButtonController, falsingCollector, featureFlags); + emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mPostureController = postureController; mLockPatternUtils = lockPatternUtils; mFeatureFlags = featureFlags; view.setIsLockScreenLandscapeEnabled(mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)); mBackspaceKey = view.findViewById(R.id.delete_button); - mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser()); + mPinLength = mLockPatternUtils.getPinLength(selectedUserInteractor.getSelectedUserId()); } @Override @@ -124,7 +125,7 @@ public class KeyguardPinViewController private void updateAutoConfirmationState() { mDisabledAutoConfirmation = mLockPatternUtils.getCurrentFailedPasswordAttempts( - KeyguardUpdateMonitor.getCurrentUser()) >= MIN_FAILED_PIN_ATTEMPTS; + mSelectedUserInteractor.getSelectedUserId()) >= MIN_FAILED_PIN_ATTEMPTS; updateOKButtonVisibility(); updateBackSpaceVisibility(); updatePinHinting(); @@ -179,7 +180,8 @@ public class KeyguardPinViewController */ private boolean isAutoPinConfirmEnabledInSettings() { //Checks if user has enabled the auto confirm in Settings - return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser()) + return mLockPatternUtils.isAutoPinConfirmEnabled( + mSelectedUserInteractor.getSelectedUserId()) && mPinLength != LockPatternUtils.PIN_LENGTH_UNAVAILABLE; } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 51dafac7b421..7101ed599b86 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -91,7 +91,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.user.domain.interactor.UserInteractor; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.ViewController; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; @@ -157,23 +157,25 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private int mCurrentUser = UserHandle.USER_NULL; private UserSwitcherController.UserSwitchCallback mUserSwitchCallback = new UserSwitcherController.UserSwitchCallback() { - @Override - public void onUserSwitched() { - if (mCurrentUser == KeyguardUpdateMonitor.getCurrentUser()) { - return; - } - mCurrentUser = KeyguardUpdateMonitor.getCurrentUser(); - showPrimarySecurityScreen(false); - if (mCurrentSecurityMode != SecurityMode.SimPin - && mCurrentSecurityMode != SecurityMode.SimPuk) { - reinflateViewFlipper((l) -> {}); - } - } - }; + @Override + public void onUserSwitched() { + if (mCurrentUser == mSelectedUserInteractor.getSelectedUserId()) { + return; + } + mCurrentUser = mSelectedUserInteractor.getSelectedUserId(); + showPrimarySecurityScreen(false); + if (mCurrentSecurityMode != SecurityMode.SimPin + && mCurrentSecurityMode != SecurityMode.SimPuk) { + reinflateViewFlipper((l) -> { + }); + } + } + }; @VisibleForTesting final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() { private MotionEvent mTouchDown; + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return false; @@ -267,7 +269,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard ThreadUtils.postOnBackgroundThread(() -> { try { Thread.sleep(5000); - } catch (InterruptedException ignored) { } + } catch (InterruptedException ignored) { + } System.gc(); System.runFinalization(); System.gc(); @@ -281,7 +284,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE)); mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS - : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId()); + : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId()); } @Override @@ -404,7 +407,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } mKeyguardSecurityCallback.dismiss( false /* authenticated */, - KeyguardUpdateMonitor.getCurrentUser(), + mSelectedUserInteractor.getSelectedUserId(), /* bypassSecondaryLockScreen */ false, SecurityMode.Invalid ); @@ -420,12 +423,13 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard showPrimarySecurityScreen(false); } }; - private final UserInteractor mUserInteractor; + private final SelectedUserInteractor mSelectedUserInteractor; private final Provider<DeviceEntryInteractor> mDeviceEntryInteractor; private final Provider<JavaAdapter> mJavaAdapter; private final DeviceProvisionedController mDeviceProvisionedController; private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor; - @Nullable private Job mSceneTransitionCollectionJob; + @Nullable + private Job mSceneTransitionCollectionJob; @Inject public KeyguardSecurityContainerController(KeyguardSecurityContainer view, @@ -453,7 +457,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, BouncerMessageInteractor bouncerMessageInteractor, Provider<JavaAdapter> javaAdapter, - UserInteractor userInteractor, + SelectedUserInteractor selectedUserInteractor, DeviceProvisionedController deviceProvisionedController, FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate, KeyguardTransitionInteractor keyguardTransitionInteractor, @@ -487,7 +491,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mAudioManager = audioManager; mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; mBouncerMessageInteractor = bouncerMessageInteractor; - mUserInteractor = userInteractor; + mSelectedUserInteractor = selectedUserInteractor; mDeviceEntryInteractor = deviceEntryInteractor; mJavaAdapter = javaAdapter; mKeyguardTransitionInteractor = keyguardTransitionInteractor; @@ -520,10 +524,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard // When the scene framework says that the lockscreen has been dismissed, dismiss the // keyguard here, revealing the underlying app or launcher: mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow( - mDeviceEntryInteractor.get().isDeviceEntered(), + mDeviceEntryInteractor.get().isDeviceEntered(), isDeviceEntered -> { if (isDeviceEntered) { - final int selectedUserId = mUserInteractor.getSelectedUserId(); + final int selectedUserId = mSelectedUserInteractor.getSelectedUserId(); showNextSecurityScreenOrFinish( /* authenticated= */ true, selectedUserId, @@ -548,7 +552,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } } - /** */ + /** */ public void onPause() { if (DEBUG) { Log.d(TAG, String.format("screen off, instance %s at %s", @@ -586,12 +590,13 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard /** * Shows the primary security screen for the user. This will be either the multi-selector * or the user's security method. + * * @param turningOff true if the device is being turned off */ public void showPrimarySecurityScreen(boolean turningOff) { if (DEBUG) Log.d(TAG, "show()"); SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode( - KeyguardUpdateMonitor.getCurrentUser())); + mSelectedUserInteractor.getSelectedUserId())); if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); showSecurityScreen(securityMode); } @@ -671,6 +676,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard /** * Dismisses the keyguard by going to the next screen or making it gone. + * * @param targetUserId a user that needs to be the foreground user at the dismissal completion. * @return True if the keyguard is done. */ @@ -716,7 +722,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } /** - * Resets the state of the views. + * Resets the state of the views. */ public void reset() { mView.reset(); @@ -748,7 +754,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard getCurrentSecurityController(controller -> controller.onResume(reason)); } mView.onResume( - mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()), + mSecurityModel.getSecurityMode(mSelectedUserInteractor.getSelectedUserId()), mKeyguardStateController.isFaceAuthEnabled()); } @@ -764,7 +770,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard /** * Show the bouncer and start appear animations. - * */ public void appear() { // We might still be collapsed and the view didn't have time to layout yet or still @@ -823,13 +828,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard /** * Shows the next security screen if there is one. - * @param authenticated true if the user entered the correct authentication - * @param targetUserId a user that needs to be the foreground user at the finish (if called) - * completion. + * + * @param authenticated true if the user entered the correct authentication + * @param targetUserId a user that needs to be the foreground user at the finish + * (if called) + * completion. * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary - * secondary lock screen requirement, if any. - * @param expectedSecurityMode SecurityMode that is invoking this request. SecurityMode.Invalid - * indicates that no check should be done + * secondary lock screen requirement, if any. + * @param expectedSecurityMode SecurityMode that is invoking this request. + * SecurityMode.Invalid + * indicates that no check should be done * @return true if keyguard is done */ public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId, @@ -879,7 +887,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled( - KeyguardUpdateMonitor.getCurrentUser()) + mSelectedUserInteractor.getSelectedUserId()) || !mDeviceProvisionedController.isUserSetup(targetUserId); if (securityMode == SecurityMode.None && isLockscreenDisabled) { @@ -955,6 +963,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard * Allows the media keys to work when the keyguard is showing. * The media keys should be of no interest to the actual keyguard view(s), * so intercepting them here should not be of any harm. + * * @param event The key event * @return whether the event was consumed as a media key. */ @@ -1050,8 +1059,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard /** * Switches to the given security view unless it's already being shown, in which case * this is a no-op. - * - * @param securityMode */ @VisibleForTesting void showSecurityScreen(SecurityMode securityMode) { @@ -1230,6 +1237,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard * Fades and translates in/out the security screen. * Fades in as expansion approaches 0. * Animation duration is between 0.33f and 0.67f of panel expansion fraction. + * * @param fraction amount of the screen that should show. */ public void setExpansion(float fraction) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index d2d051735643..6e242084d68c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -43,6 +43,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; public class KeyguardSimPinViewController extends KeyguardPinBasedInputViewController<KeyguardSimPinView> { @@ -83,10 +84,11 @@ public class KeyguardSimPinViewController KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, - EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) { + EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags, + SelectedUserInteractor selectedUserInteractor) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - emergencyButtonController, falsingCollector, featureFlags); + emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); @@ -168,7 +170,7 @@ public class KeyguardSimPinViewController mRemainingAttempts = -1; mShowDefaultMessage = true; getKeyguardSecurityCallback().dismiss( - true, KeyguardUpdateMonitor.getCurrentUser(), + true, mSelectedUserInteractor.getSelectedUserId(), SecurityMode.SimPin); } else { mShowDefaultMessage = false; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index b52a36b8199e..13f9d3e1038e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -40,6 +40,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; public class KeyguardSimPukViewController extends KeyguardPinBasedInputViewController<KeyguardSimPukView> { @@ -70,7 +71,8 @@ public class KeyguardSimPukViewController if (simState == TelephonyManager.SIM_STATE_READY) { mRemainingAttempts = -1; mShowDefaultMessage = true; - getKeyguardSecurityCallback().dismiss(true, KeyguardUpdateMonitor.getCurrentUser(), + getKeyguardSecurityCallback().dismiss( + true, mSelectedUserInteractor.getSelectedUserId(), SecurityMode.SimPuk); } else { resetState(); @@ -87,10 +89,11 @@ public class KeyguardSimPukViewController KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, - EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) { + EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags, + SelectedUserInteractor selectedUserInteractor) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - emergencyButtonController, falsingCollector, featureFlags); + emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); @@ -284,7 +287,7 @@ public class KeyguardSimPukViewController mShowDefaultMessage = true; getKeyguardSecurityCallback().dismiss( - true, KeyguardUpdateMonitor.getCurrentUser(), + true, mSelectedUserInteractor.getSelectedUserId(), SecurityMode.SimPuk); } else { mShowDefaultMessage = false; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 205c297cebc4..7d6240b0dc2c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -186,8 +186,8 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt; import com.android.systemui.telephony.TelephonyListenerManager; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.Assert; -import com.android.systemui.util.settings.SecureSettings; import dalvik.annotation.optimization.NeverCompile; @@ -410,7 +410,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final DevicePolicyManager mDevicePolicyManager; private final DevicePostureController mPostureController; private final BroadcastDispatcher mBroadcastDispatcher; - private final SecureSettings mSecureSettings; private final InteractionJankMonitor mInteractionJankMonitor; private final LatencyTracker mLatencyTracker; private final StatusBarStateController mStatusBarStateController; @@ -429,6 +428,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final IActivityTaskManager mActivityTaskManager; private final WakefulnessLifecycle mWakefulness; private final DisplayTracker mDisplayTracker; + private final SelectedUserInteractor mSelectedUserInteractor; private final LockPatternUtils mLockPatternUtils; @VisibleForTesting @DevicePostureInt @@ -537,13 +537,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static int sCurrentUser; + @Deprecated public synchronized static void setCurrentUser(int currentUser) { sCurrentUser = currentUser; } /** * @deprecated This can potentially return unexpected values in a multi user scenario - * as this state is managed by another component. Consider using {@link UserTracker}. + * as this state is managed by another component. Consider using {@link SelectedUserInteractor}. */ @Deprecated public synchronized static int getCurrentUser() { @@ -577,7 +578,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (enabled) { String message = null; - if (KeyguardUpdateMonitor.getCurrentUser() == userId + if (mSelectedUserInteractor.getSelectedUserId() == userId && trustGrantedMessages != null) { // Show the first non-empty string provided by a trust agent OR intentionally pass // an empty string through (to prevent the default trust agent string from showing) @@ -590,7 +591,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } mLogger.logTrustGrantedWithFlags(flags, newlyUnlocked, userId, message); - if (userId == getCurrentUser()) { + if (userId == mSelectedUserInteractor.getSelectedUserId()) { if (newlyUnlocked) { // if this callback is ever removed, this should then be logged in // TrustRepository @@ -1038,7 +1039,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.removeCallbacks(mFpCancelNotReceived); } try { - final int userId = mUserTracker.getUserId(); + final int userId = mSelectedUserInteractor.getSelectedUserId(true); if (userId != authUserId) { mLogger.logFingerprintAuthForWrongUser(authUserId); return; @@ -1127,8 +1128,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab lockedOutStateChanged = !mFingerprintLockedOutPermanent; mFingerprintLockedOutPermanent = true; mLogger.d("Fingerprint permanently locked out - requiring stronger auth"); - mLockPatternUtils.requireStrongAuth( - STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser()); + mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, + mSelectedUserInteractor.getSelectedUserId()); } if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT @@ -1309,7 +1310,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.d("Aborted successful auth because device is going to sleep."); return; } - final int userId = mUserTracker.getUserId(); + final int userId = mSelectedUserInteractor.getSelectedUserId(true); if (userId != authUserId) { mLogger.logFaceAuthForWrongUser(authUserId); return; @@ -1565,7 +1566,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Deprecated public boolean getIsFaceAuthenticated() { boolean faceAuthenticated = false; - BiometricAuthenticated bioFaceAuthenticated = mUserFaceAuthenticated.get(getCurrentUser()); + BiometricAuthenticated bioFaceAuthenticated = + mUserFaceAuthenticated.get(mSelectedUserInteractor.getSelectedUserId()); if (bioFaceAuthenticated != null) { faceAuthenticated = bioFaceAuthenticated.mAuthenticated; } @@ -1754,9 +1756,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onStrongAuthStateChanged(userId); } } - if (userId == getCurrentUser()) { + if (userId == mSelectedUserInteractor.getSelectedUserId()) { FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED.setExtraInfo( - mStrongAuthTracker.getStrongAuthForUser(getCurrentUser())); + mStrongAuthTracker.getStrongAuthForUser( + mSelectedUserInteractor.getSelectedUserId())); // Strong auth is only reset when primary auth is used to enter the device, // so we only check whether to stop biometric listening states here @@ -1783,10 +1786,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onNonStrongBiometricAllowedChanged(userId); } } - if (userId == getCurrentUser()) { + if (userId == mSelectedUserInteractor.getSelectedUserId()) { FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED.setExtraInfo( mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout( - getCurrentUser()) ? -1 : 1); + mSelectedUserInteractor.getSelectedUserId()) ? -1 : 1); // This is only reset when primary auth is used to enter the device, so we only check // whether to stop biometric listening states here @@ -2191,12 +2194,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) { - int userId = getCurrentUser(); + int userId = mSelectedUserInteractor.getSelectedUserId(); return isBiometricAllowedForUser(isStrongBiometric, userId); } public boolean hasUserAuthenticatedSinceBoot() { - int userId = getCurrentUser(); + int userId = mSelectedUserInteractor.getSelectedUserId(); return (getStrongAuthForUser(userId) & STRONG_AUTH_REQUIRED_AFTER_BOOT) == 0; } @@ -2344,7 +2347,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab UserTracker userTracker, @Main Looper mainLooper, BroadcastDispatcher broadcastDispatcher, - SecureSettings secureSettings, DumpManager dumpManager, @Background Executor backgroundExecutor, @Main Executor mainExecutor, @@ -2376,7 +2378,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab TaskStackChangeListeners taskStackChangeListeners, IActivityTaskManager activityTaskManagerService, DisplayTracker displayTracker, - WakefulnessLifecycle wakefulness) { + WakefulnessLifecycle wakefulness, + SelectedUserInteractor selectedUserInteractor) { mContext = context; mSubscriptionManager = subscriptionManager; mUserTracker = userTracker; @@ -2392,7 +2395,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mStatusBarState = mStatusBarStateController.getState(); mLockPatternUtils = lockPatternUtils; mAuthController = authController; - mSecureSettings = secureSettings; dumpManager.registerDumpable(this); mSensorPrivacyManager = sensorPrivacyManager; mActiveUnlockConfig = activeUnlockConfiguration; @@ -2426,6 +2428,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mWakefulness = wakefulness; mDisplayTracker = displayTracker; mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor); + mSelectedUserInteractor = selectedUserInteractor; mHandler = new Handler(mainLooper) { @Override @@ -2648,7 +2651,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener); mIsSystemUser = mUserManager.isSystemUser(); - int user = mUserTracker.getUserId(); + int user = mSelectedUserInteractor.getSelectedUserId(true); mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user)); mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled(); updateSecondaryLockscreenRequirement(user); @@ -2720,7 +2723,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @return true if there's at least one udfps enrolled for the current user. */ public boolean isUdfpsEnrolled() { - return mAuthController.isUdfpsEnrolled(getCurrentUser()); + return mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId()); } /** @@ -2735,7 +2738,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @return true if there's at least one sfps enrollment for the current user. */ public boolean isSfpsEnrolled() { - return mAuthController.isSfpsEnrolled(getCurrentUser()); + return mAuthController.isSfpsEnrolled(mSelectedUserInteractor.getSelectedUserId()); } /** @@ -2906,7 +2909,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (shouldTriggerActiveUnlock()) { mLogger.logActiveUnlockTriggered(reason); - mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser()); + mTrustManager.reportUserMayRequestUnlock(mSelectedUserInteractor.getSelectedUserId()); } } @@ -2960,7 +2963,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (allowRequest && shouldTriggerActiveUnlock()) { mLogger.logUserRequestedUnlock(requestOrigin, reason, dismissKeyguard); - mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser(), + mTrustManager.reportUserRequestedUnlock(mSelectedUserInteractor.getSelectedUserId(), dismissKeyguard); } } @@ -3031,7 +3034,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && mStatusBarState != StatusBarState.SHADE_LOCKED); // Gates: - final int user = getCurrentUser(); + final int user = mSelectedUserInteractor.getSelectedUserId(); // No need to trigger active unlock if we're already unlocked or don't have // pin/pattern/password setup @@ -3073,30 +3076,33 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private boolean shouldListenForFingerprintAssistant() { - BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(getCurrentUser()); + BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get( + mSelectedUserInteractor.getSelectedUserId()); return mAssistantVisible && mKeyguardOccluded && !(fingerprint != null && fingerprint.mAuthenticated) - && !mUserHasTrust.get(getCurrentUser(), false); + && !mUserHasTrust.get( + mSelectedUserInteractor.getSelectedUserId(), false); } private boolean shouldListenForFaceAssistant() { - BiometricAuthenticated face = mUserFaceAuthenticated.get(getCurrentUser()); + BiometricAuthenticated face = mUserFaceAuthenticated.get( + mSelectedUserInteractor.getSelectedUserId()); return mAssistantVisible // There can be intermediate states where mKeyguardShowing is false but // mKeyguardOccluded is true, we don't want to run face auth in such a scenario. && (mKeyguardShowing && mKeyguardOccluded) && !(face != null && face.mAuthenticated) - && !mUserHasTrust.get(getCurrentUser(), false); + && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false); } private boolean shouldTriggerActiveUnlockForAssistant() { return mAssistantVisible && mKeyguardOccluded - && !mUserHasTrust.get(getCurrentUser(), false); + && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false); } @VisibleForTesting protected boolean shouldListenForFingerprint(boolean isUdfps) { - final int user = getCurrentUser(); + final int user = mSelectedUserInteractor.getSelectedUserId(); final boolean userDoesNotHaveTrust = !getUserHasTrust(user); final boolean shouldListenForFingerprintAssistant = shouldListenForFingerprintAssistant(); final boolean shouldListenKeyguardState = @@ -3185,7 +3191,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED; final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive && !statusBarShadeLocked; - final int user = getCurrentUser(); + final int user = mSelectedUserInteractor.getSelectedUserId(); final boolean faceAuthAllowed = isUnlockingWithBiometricAllowed(FACE); final boolean canBypass = mKeyguardBypassController != null && mKeyguardBypassController.canBypass(); @@ -3285,7 +3291,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private void startListeningForFingerprint() { - final int userId = getCurrentUser(); + final int userId = mSelectedUserInteractor.getSelectedUserId(); final boolean unlockPossible = isUnlockWithFingerprintPossible(userId); if (mFingerprintCancelSignal != null) { mLogger.logUnexpectedFpCancellationSignalState( @@ -3332,7 +3338,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private void startListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) { - final int userId = getCurrentUser(); + final int userId = mSelectedUserInteractor.getSelectedUserId(); final boolean unlockPossible = isUnlockWithFacePossible(userId); if (mFaceCancelSignal != null) { mLogger.logUnexpectedFaceCancellationSignalState(mFaceRunningState, unlockPossible); @@ -3875,12 +3881,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private boolean resolveNeedsSlowUnlockTransition() { - if (isUserUnlocked(getCurrentUser())) { + if (isUserUnlocked(mSelectedUserInteractor.getSelectedUserId())) { return false; } Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME); ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(homeIntent, - 0 /* flags */, getCurrentUser()); + 0 /* flags */, mSelectedUserInteractor.getSelectedUserId()); if (resolveInfo == null) { mLogger.w("resolveNeedsSlowUnlockTransition: returning false since activity could " @@ -4066,9 +4072,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @AnyThread public void setSwitchingUser(boolean switching) { if (switching) { - mLogger.logUserSwitching(getCurrentUser(), "from setSwitchingUser"); + mLogger.logUserSwitching( + mSelectedUserInteractor.getSelectedUserId(), "from setSwitchingUser"); } else { - mLogger.logUserSwitchComplete(getCurrentUser(), "from setSwitchingUser"); + mLogger.logUserSwitchComplete( + mSelectedUserInteractor.getSelectedUserId(), "from setSwitchingUser"); } mSwitchingUser = switching; // Since this comes in on a binder thread, we need to post it first @@ -4443,9 +4451,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("KeyguardUpdateMonitor state:"); - pw.println(" getUserHasTrust()=" + getUserHasTrust(getCurrentUser())); + pw.println(" getUserHasTrust()=" + getUserHasTrust( + mSelectedUserInteractor.getSelectedUserId())); pw.println(" getUserUnlockedWithBiometric()=" - + getUserUnlockedWithBiometric(getCurrentUser())); + + getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId())); pw.println(" isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled()); pw.println(" SIM States:"); for (SimData data : mSimDatas.values()) { @@ -4463,7 +4472,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" " + subId + "=" + mServiceStates.get(subId)); } if (isFingerprintSupported()) { - final int userId = mUserTracker.getUserId(); + final int userId = mSelectedUserInteractor.getSelectedUserId(true); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId); pw.println(" Fingerprint state (user=" + userId + ")"); @@ -4506,7 +4515,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintListenBuffer.toList() ).printTableData(pw); } else if (mFpm != null && mFingerprintSensorProperties.isEmpty()) { - final int userId = mUserTracker.getUserId(); + final int userId = mSelectedUserInteractor.getSelectedUserId(true); pw.println(" Fingerprint state (user=" + userId + ")"); pw.println(" mFingerprintSensorProperties.isEmpty=" + mFingerprintSensorProperties.isEmpty()); @@ -4520,7 +4529,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab ).printTableData(pw); } if (isFaceSupported()) { - final int userId = mUserTracker.getUserId(); + final int userId = mSelectedUserInteractor.getSelectedUserId(true); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); BiometricAuthenticated face = mUserFaceAuthenticated.get(userId); pw.println(" Face authentication state (user=" + userId + ")"); @@ -4550,7 +4559,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFaceListenBuffer.toList() ).printTableData(pw); } else if (mFaceManager != null && mFaceSensorProperties.isEmpty()) { - final int userId = mUserTracker.getUserId(); + final int userId = mSelectedUserInteractor.getSelectedUserId(true); pw.println(" Face state (user=" + userId + ")"); pw.println(" mFaceSensorProperties.isEmpty=" + mFaceSensorProperties.isEmpty()); @@ -4564,7 +4573,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab ).printTableData(pw); } pw.println("ActiveUnlockRunning=" - + mTrustManager.isActiveUnlockRunning(KeyguardUpdateMonitor.getCurrentUser())); + + mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId())); new DumpsysTableLogger( "KeyguardActiveUnlockTriggers", KeyguardActiveUnlockModel.TABLE_HEADERS, diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index 40d0be1173fa..ff6a3d0cc6f0 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -25,7 +25,6 @@ import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; @@ -105,18 +104,6 @@ public class LockIconView extends FrameLayout implements Dumpable { mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor)); } - void setImageDrawable(Drawable drawable) { - mLockIcon.setImageDrawable(drawable); - - if (!mUseBackground) return; - - if (drawable == null) { - mBgView.setVisibility(View.INVISIBLE); - } else { - mBgView.setVisibility(View.VISIBLE); - } - } - /** * Whether or not to render the lock icon background. Mainly used for UDPFS. */ @@ -197,6 +184,7 @@ public class LockIconView extends FrameLayout implements Dumpable { mLockIcon = new ImageView(context, attrs); mLockIcon.setId(R.id.lock_icon); mLockIcon.setScaleType(ImageView.ScaleType.CENTER_CROP); + mLockIcon.setImageDrawable(context.getDrawable(R.drawable.super_lock_icon)); addView(mLockIcon); LayoutParams lp = (LayoutParams) mLockIcon.getLayoutParams(); lp.height = MATCH_PARENT; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 83da80f4123a..611283f12984 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -35,7 +35,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.drawable.AnimatedStateListDrawable; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricSourceType; import android.os.Process; @@ -120,9 +119,6 @@ public class LockIconViewController implements Dumpable { private boolean mUdfpsEnrolled; private Resources mResources; private Context mContext; - - @NonNull private final AnimatedStateListDrawable mIcon; - @NonNull private CharSequence mUnlockedLabel; @NonNull private CharSequence mLockedLabel; @NonNull private final VibratorHelper mVibrator; @@ -147,7 +143,6 @@ public class LockIconViewController implements Dumpable { private boolean mCanDismissLockScreen; private int mStatusBarState; private boolean mIsKeyguardShowing; - private Runnable mOnGestureDetectedRunnable; private Runnable mLongPressCancelRunnable; private boolean mUdfpsSupported; @@ -232,9 +227,6 @@ public class LockIconViewController implements Dumpable { mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); - - mIcon = (AnimatedStateListDrawable) - resources.getDrawable(R.drawable.super_lock_icon, context.getTheme()); mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button); mLockedLabel = resources.getString(R.string.accessibility_lock_icon); mLongPressTimeout = resources.getInteger(R.integer.config_lockIconLongPress); @@ -270,7 +262,6 @@ public class LockIconViewController implements Dumpable { @SuppressLint("ClickableViewAccessibility") public void setLockIconView(LockIconView lockIconView) { mView = lockIconView; - mView.setImageDrawable(mIcon); mView.setAccessibilityDelegate(mAccessibilityDelegate); if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) { @@ -492,10 +483,6 @@ public class LockIconViewController implements Dumpable { pw.println("mUdfpsSupported: " + mUdfpsSupported); pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled); pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing); - pw.println(" mIcon: "); - for (int state : mIcon.getState()) { - pw.print(" " + state); - } pw.println(); pw.println(" mShowUnlockIcon: " + mShowUnlockIcon); pw.println(" mShowLockIcon: " + mShowLockIcon); diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java index b33d50137155..c860979afb10 100644 --- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java +++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java @@ -31,7 +31,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.statusbar.phone.BiometricUnlockController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -53,25 +53,25 @@ public class LatencyTester implements CoreStartable { private static final String ACTION_FACE_WAKE = "com.android.systemui.latency.ACTION_FACE_WAKE"; - private final BiometricUnlockController mBiometricUnlockController; private final BroadcastDispatcher mBroadcastDispatcher; private final DeviceConfigProxy mDeviceConfigProxy; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final SelectedUserInteractor mSelectedUserInteractor; private boolean mEnabled; @Inject public LatencyTester( - BiometricUnlockController biometricUnlockController, BroadcastDispatcher broadcastDispatcher, DeviceConfigProxy deviceConfigProxy, @Main DelayableExecutor mainExecutor, - KeyguardUpdateMonitor keyguardUpdateMonitor + KeyguardUpdateMonitor keyguardUpdateMonitor, + SelectedUserInteractor selectedUserInteractor ) { - mBiometricUnlockController = biometricUnlockController; mBroadcastDispatcher = broadcastDispatcher; mDeviceConfigProxy = deviceConfigProxy; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mSelectedUserInteractor = selectedUserInteractor; updateEnabled(); mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER, @@ -87,11 +87,11 @@ public class LatencyTester implements CoreStartable { return; } if (type == BiometricSourceType.FACE) { - mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(), + mKeyguardUpdateMonitor.onFaceAuthenticated(mSelectedUserInteractor.getSelectedUserId(), true); } else if (type == BiometricSourceType.FINGERPRINT) { mKeyguardUpdateMonitor.onFingerprintAuthenticated( - KeyguardUpdateMonitor.getCurrentUser(), true); + mSelectedUserInteractor.getSelectedUserId(), true); } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 0e339dd68b4f..9305ab6f6968 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -28,17 +28,17 @@ import com.android.internal.app.IVisualQueryRecognitionStatusListener; import com.android.internal.app.IVoiceInteractionSessionListener; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.res.R; import com.android.systemui.assist.ui.DefaultUiController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.settings.SecureSettings; import dagger.Lazy; @@ -144,6 +144,7 @@ public class AssistManager { private final UserTracker mUserTracker; private final DisplayTracker mDisplayTracker; private final SecureSettings mSecureSettings; + private final SelectedUserInteractor mSelectedUserInteractor; private final DeviceProvisionedController mDeviceProvisionedController; @@ -152,16 +153,16 @@ public class AssistManager { private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener = new IVisualQueryDetectionAttentionListener.Stub() { - @Override - public void onAttentionGained() { - handleVisualAttentionChanged(true); - } + @Override + public void onAttentionGained() { + handleVisualAttentionChanged(true); + } - @Override - public void onAttentionLost() { - handleVisualAttentionChanged(false); - } - }; + @Override + public void onAttentionLost() { + handleVisualAttentionChanged(false); + } + }; private final CommandQueue mCommandQueue; protected final AssistUtils mAssistUtils; @@ -183,7 +184,8 @@ public class AssistManager { @Main Handler uiHandler, UserTracker userTracker, DisplayTracker displayTracker, - SecureSettings secureSettings) { + SecureSettings secureSettings, + SelectedUserInteractor selectedUserInteractor) { mContext = context; mDeviceProvisionedController = controller; mCommandQueue = commandQueue; @@ -195,6 +197,7 @@ public class AssistManager { mUserTracker = userTracker; mDisplayTracker = displayTracker; mSecureSettings = secureSettings; + mSelectedUserInteractor = selectedUserInteractor; registerVoiceInteractionSessionListener(); registerVisualQueryRecognitionStatusListener(); @@ -316,12 +319,13 @@ public class AssistManager { public boolean shouldOverrideAssist(int invocationType) { return mAssistOverrideInvocationTypes != null && Arrays.stream(mAssistOverrideInvocationTypes).anyMatch( - override -> override == invocationType); + override -> override == invocationType); } /** * @param invocationTypes The invocation types that will henceforth be handled via - * OverviewProxy (Launcher); other invocation types should be handled by this class. + * OverviewProxy (Launcher); other invocation types should be handled by + * this class. */ public void setAssistantOverridesRequested(int[] invocationTypes) { mAssistOverrideInvocationTypes = invocationTypes; @@ -478,7 +482,7 @@ public class AssistManager { @Nullable private ComponentName getAssistInfo() { - return getAssistInfoForUser(KeyguardUpdateMonitor.getCurrentUser()); + return getAssistInfoForUser(mSelectedUserInteractor.getSelectedUserId()); } public void showDisclosure() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index c9e4cbe30a2c..92eacf10e424 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -97,6 +97,7 @@ 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.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; import com.android.systemui.util.time.SystemClock; @@ -167,6 +168,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor; @NonNull private final InputManager mInputManager; @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; + @NonNull private final SelectedUserInteractor mSelectedUserInteractor; private final boolean mIgnoreRefreshRate; // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple @@ -281,7 +283,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsKeyguardAccessibilityDelegate, - mUdfpsKeyguardViewModels + mUdfpsKeyguardViewModels, + mSelectedUserInteractor ))); } @@ -644,7 +647,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull InputManager inputManager, @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate, - @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) { + @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider, + @NonNull SelectedUserInteractor selectedUserInteractor) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -687,6 +691,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { mAlternateBouncerInteractor = alternateBouncerInteractor; mInputManager = inputManager; mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate; + mSelectedUserInteractor = selectedUserInteractor; mTouchProcessor = singlePointerTouchProcessor; mSessionTracker = sessionTracker; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 7130bfb462e3..272e0f21e74a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -44,7 +44,6 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan import androidx.annotation.LayoutRes import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.res.R import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController @@ -56,12 +55,14 @@ 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 import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager 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.user.domain.interactor.SelectedUserInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi import javax.inject.Provider @@ -78,31 +79,32 @@ const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" @ExperimentalCoroutinesApi @UiThread class UdfpsControllerOverlay @JvmOverloads constructor( - private val context: Context, - private val inflater: LayoutInflater, - private val windowManager: WindowManager, - private val accessibilityManager: AccessibilityManager, - private val statusBarStateController: StatusBarStateController, - private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val dialogManager: SystemUIDialogManager, - private val dumpManager: DumpManager, - private val transitionController: LockscreenShadeTransitionController, - private val configurationController: ConfigurationController, - private val keyguardStateController: KeyguardStateController, - private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, - private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, - val requestId: Long, - @ShowReason val requestReason: Int, - private val controllerCallback: IUdfpsOverlayControllerCallback, - private val onTouch: (View, MotionEvent, Boolean) -> Boolean, - private val activityLaunchAnimator: ActivityLaunchAnimator, - private val featureFlags: FeatureFlags, - private val primaryBouncerInteractor: PrimaryBouncerInteractor, - private val alternateBouncerInteractor: AlternateBouncerInteractor, - private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, - private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, - private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>, + private val context: Context, + private val inflater: LayoutInflater, + private val windowManager: WindowManager, + private val accessibilityManager: AccessibilityManager, + private val statusBarStateController: StatusBarStateController, + private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val dialogManager: SystemUIDialogManager, + private val dumpManager: DumpManager, + private val transitionController: LockscreenShadeTransitionController, + private val configurationController: ConfigurationController, + private val keyguardStateController: KeyguardStateController, + private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, + private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, + val requestId: Long, + @ShowReason val requestReason: Int, + private val controllerCallback: IUdfpsOverlayControllerCallback, + private val onTouch: (View, MotionEvent, Boolean) -> Boolean, + private val activityLaunchAnimator: ActivityLaunchAnimator, + private val featureFlags: FeatureFlags, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, + private val alternateBouncerInteractor: AlternateBouncerInteractor, + private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, + private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, + private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>, + private val selectedUserInteractor: SelectedUserInteractor, ) { /** The view, when [isShowing], or null. */ var overlayView: UdfpsView? = null @@ -268,6 +270,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( primaryBouncerInteractor, alternateBouncerInteractor, udfpsKeyguardAccessibilityDelegate, + selectedUserInteractor, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 3d5be6fb9f8c..d7df0e585dcf 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -45,6 +45,7 @@ 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.user.domain.interactor.SelectedUserInteractor import java.io.PrintWriter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -68,6 +69,7 @@ open class UdfpsKeyguardViewControllerLegacy( primaryBouncerInteractor: PrimaryBouncerInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, + private val selectedUserInteractor: SelectedUserInteractor, ) : UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>( view, @@ -384,7 +386,7 @@ open class UdfpsKeyguardViewControllerLegacy( } if ( keyguardUpdateMonitor.getUserUnlockedWithBiometric( - KeyguardUpdateMonitor.getCurrentUser() + selectedUserInteractor.getSelectedUserId() ) ) { // If the device was unlocked by a biometric, immediately hide the UDFPS icon to avoid diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt index 2a1047ac6f76..38043b4020e5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt @@ -17,13 +17,13 @@ package com.android.systemui.biometrics.domain.interactor import android.view.MotionEvent -import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose @@ -35,11 +35,16 @@ import kotlinx.coroutines.flow.stateIn @SysUISingleton class UdfpsOverlayInteractor @Inject -constructor(private val authController: AuthController, @Application scope: CoroutineScope) { +constructor( + private val authController: AuthController, + private val selectedUserInteractor: SelectedUserInteractor, + @Application scope: CoroutineScope +) { /** Whether a touch is within the under-display fingerprint sensor area */ fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean { - val isUdfpsEnrolled = authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()) + val isUdfpsEnrolled = + authController.isUdfpsEnrolled(selectedUserInteractor.getSelectedUserId()) val isWithinOverlayBounds = udfpsOverlayParams.value.overlayBounds.contains(ev.rawX.toInt(), ev.rawY.toInt()) return isUdfpsEnrolled && isWithinOverlayBounds diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index 6e26fe952749..2bd62587834d 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -28,7 +28,6 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.DejankUtils -import com.android.systemui.res.R import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN @@ -41,8 +40,10 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -73,6 +74,7 @@ constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val trustRepository: TrustRepository, @Application private val applicationScope: CoroutineScope, + private val selectedUserInteractor: SelectedUserInteractor, ) { private val passiveAuthBouncerDelay = context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong() @@ -384,7 +386,7 @@ constructor( /** Returns whether the bouncer should be full screen. */ private fun needsFullscreenBouncer(): Boolean { val mode: KeyguardSecurityModel.SecurityMode = - keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()) + keyguardSecurityModel.getSecurityMode(selectedUserInteractor.getSelectedUserId()) return mode == KeyguardSecurityModel.SecurityMode.SimPin || mode == KeyguardSecurityModel.SecurityMode.SimPuk } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt index 36e5db4c62e0..ac3d4b6c853b 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt @@ -26,7 +26,6 @@ import com.android.keyguard.KeyguardMessageAreaController import com.android.keyguard.KeyguardSecurityContainerController import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityView -import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE @@ -37,6 +36,7 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.BouncerLogger import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @@ -53,6 +53,7 @@ object KeyguardBouncerViewBinder { bouncerMessageInteractor: BouncerMessageInteractor, bouncerLogger: BouncerLogger, featureFlags: FeatureFlags, + selectedUserInteractor: SelectedUserInteractor, ) { // Builds the KeyguardSecurityContainerController from bouncer view group. val securityContainerController: KeyguardSecurityContainerController = @@ -84,7 +85,7 @@ object KeyguardBouncerViewBinder { override fun showNextSecurityScreenOrFinish(): Boolean { return securityContainerController.dismiss( - KeyguardUpdateMonitor.getCurrentUser() + selectedUserInteractor.getSelectedUserId() ) } @@ -220,7 +221,7 @@ object KeyguardBouncerViewBinder { launch { viewModel.keyguardAuthenticated.collect { securityContainerController.finish( - KeyguardUpdateMonitor.getCurrentUser() + selectedUserInteractor.getSelectedUserId() ) viewModel.notifyKeyguardAuthenticated() } diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt index b2680950d9c5..11c7a3196913 100644 --- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt @@ -27,17 +27,16 @@ import android.content.pm.ResolveInfo import android.os.RemoteException import android.util.Log import android.view.WindowManager -import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.ActivityIntentHelper import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.settings.UserTracker import com.android.systemui.shared.system.ActivityManagerKt.isInForeground import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import java.util.concurrent.Executor import javax.inject.Inject @@ -59,7 +58,7 @@ class CameraGestureHelper @Inject constructor( private val cameraIntents: CameraIntentsWrapper, private val contentResolver: ContentResolver, @Main private val uiExecutor: Executor, - private val userTracker: UserTracker + private val selectedUserInteractor: SelectedUserInteractor, ) { /** * Whether the camera application can be launched for the camera launch gesture. @@ -72,12 +71,12 @@ class CameraGestureHelper @Inject constructor( val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser( getStartCameraIntent(), PackageManager.MATCH_DEFAULT_ONLY, - KeyguardUpdateMonitor.getCurrentUser() + selectedUserInteractor.getSelectedUserId() ) val resolvedPackage = resolveInfo?.activityInfo?.packageName return (resolvedPackage != null && (statusBarState != StatusBarState.SHADE || - !activityManager.isInForeground(resolvedPackage))) + !activityManager.isInForeground(resolvedPackage))) } /** @@ -89,7 +88,7 @@ class CameraGestureHelper @Inject constructor( val intent: Intent = getStartCameraIntent() intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source) val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity( - intent, KeyguardUpdateMonitor.getCurrentUser() + intent, selectedUserInteractor.getSelectedUserId() ) if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) { uiExecutor.execute { @@ -102,7 +101,7 @@ class CameraGestureHelper @Inject constructor( val activityOptions = ActivityOptions.makeBasic() activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true) activityOptions.rotationAnimationHint = - WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS + WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS try { activityTaskManager.startActivityAsUser( null, @@ -116,7 +115,7 @@ class CameraGestureHelper @Inject constructor( Intent.FLAG_ACTIVITY_NEW_TASK, null, activityOptions.toBundle(), - userTracker.userId, + selectedUserInteractor.getSelectedUserId(true), ) } catch (e: RemoteException) { Log.w( diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index 39c01f759654..a6b073da2530 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -37,12 +37,15 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.sensors.ThresholdSensor; import com.android.systemui.util.sensors.ThresholdSensorEvent; import com.android.systemui.util.time.SystemClock; +import dagger.Lazy; + import java.util.Collections; import javax.inject.Inject; @@ -66,6 +69,7 @@ class FalsingCollectorImpl implements FalsingCollector { private final DockManager mDockManager; private final DelayableExecutor mMainExecutor; private final SystemClock mSystemClock; + private final Lazy<SelectedUserInteractor> mUserInteractor; private int mState; private boolean mShowingAod; @@ -93,7 +97,7 @@ class FalsingCollectorImpl implements FalsingCollector { public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType, boolean isStrongBiometric) { - if (userId == KeyguardUpdateMonitor.getCurrentUser() + if (userId == mUserInteractor.get().getSelectedUserId() && biometricSourceType == BiometricSourceType.FACE) { mFalsingDataProvider.setJustUnlockedWithFace(true); } @@ -136,7 +140,8 @@ class FalsingCollectorImpl implements FalsingCollector { BatteryController batteryController, DockManager dockManager, @Main DelayableExecutor mainExecutor, - SystemClock systemClock) { + SystemClock systemClock, + Lazy<SelectedUserInteractor> userInteractor) { mFalsingDataProvider = falsingDataProvider; mFalsingManager = falsingManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -148,6 +153,7 @@ class FalsingCollectorImpl implements FalsingCollector { mDockManager = dockManager; mMainExecutor = mainExecutor; mSystemClock = systemClock; + mUserInteractor = userInteractor; mProximitySensor.setTag(PROXIMITY_SENSOR_TAG); mProximitySensor.setDelay(SensorManager.SENSOR_DELAY_GAME); diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java index bf4fba89ca55..0bf50693344a 100644 --- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java +++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java @@ -26,11 +26,13 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.types.ExtractionType; import com.android.internal.colorextraction.types.Tonal; -import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; + +import dagger.Lazy; import java.io.PrintWriter; import java.util.Arrays; @@ -48,19 +50,22 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable, private boolean mHasMediaArtwork; private final GradientColors mNeutralColorsLock; private final GradientColors mBackdropColors; + private Lazy<SelectedUserInteractor> mUserInteractor; @Inject public SysuiColorExtractor( Context context, ConfigurationController configurationController, - DumpManager dumpManager) { + DumpManager dumpManager, + Lazy<SelectedUserInteractor> userInteractor) { this( context, new Tonal(context), configurationController, context.getSystemService(WallpaperManager.class), dumpManager, - false /* immediately */); + false /* immediately */, + userInteractor); } @VisibleForTesting @@ -70,7 +75,8 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable, ConfigurationController configurationController, WallpaperManager wallpaperManager, DumpManager dumpManager, - boolean immediately) { + boolean immediately, + Lazy<SelectedUserInteractor> userInteractor) { super(context, type, immediately, wallpaperManager); mTonal = type instanceof Tonal ? (Tonal) type : new Tonal(context); mNeutralColorsLock = new GradientColors(); @@ -79,6 +85,7 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable, mBackdropColors = new GradientColors(); mBackdropColors.setMainColor(Color.BLACK); + mUserInteractor = userInteractor; // Listen to all users instead of only the current one. if (wallpaperManager.isWallpaperSupported()) { @@ -100,7 +107,7 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable, @Override public void onColorsChanged(WallpaperColors colors, int which, int userId) { - if (userId != KeyguardUpdateMonitor.getCurrentUser()) { + if (userId != mUserInteractor.get().getSelectedUserId()) { // Colors do not belong to current user, ignoring. return; } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt new file mode 100644 index 000000000000..f9c4f29afee9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.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.data.model + +import com.android.systemui.communal.shared.CommunalContentSize + +/** Metadata for the default widgets */ +data class CommunalWidgetMetadata( + /* Widget provider component name */ + val componentName: String, + + /* Defines the order in which the widget will be rendered in the grid. */ + val priority: Int, + + /* Supported sizes */ + val sizes: List<CommunalContentSize> +) diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index e2a7d077a32c..f13b62fbfeb9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -27,13 +27,17 @@ import android.content.pm.PackageManager import android.os.UserManager import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.communal.data.model.CommunalWidgetMetadata import com.android.systemui.communal.shared.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.CommunalContentSize import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose @@ -45,15 +49,20 @@ import kotlinx.coroutines.flow.map interface CommunalWidgetRepository { /** A flow of provider info for the stopwatch widget, or null if widget is unavailable. */ val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> + + /** Widgets that are allowed to render in the glanceable hub */ + val communalWidgetAllowlist: List<CommunalWidgetMetadata> } @SysUISingleton class CommunalWidgetRepositoryImpl @Inject constructor( + @Application private val applicationContext: Context, private val appWidgetManager: AppWidgetManager, private val appWidgetHost: AppWidgetHost, broadcastDispatcher: BroadcastDispatcher, + communalRepository: CommunalRepository, private val packageManager: PackageManager, private val userManager: UserManager, private val userTracker: UserTracker, @@ -64,12 +73,18 @@ constructor( const val TAG = "CommunalWidgetRepository" const val WIDGET_LABEL = "Stopwatch" } + override val communalWidgetAllowlist: List<CommunalWidgetMetadata> private val logger = Logger(logBuffer, TAG) // Whether the [AppWidgetHost] is listening for updates. private var isHostListening = false + init { + communalWidgetAllowlist = + if (communalRepository.isCommunalEnabled) getWidgetAllowlist() else emptyList() + } + // Widgets that should be rendered in communal mode. private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf() @@ -129,6 +144,18 @@ constructor( return@map addWidget(providerInfo) } + private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> { + val componentNames = + applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist) + return componentNames.mapIndexed { index, name -> + CommunalWidgetMetadata( + componentName = name, + priority = componentNames.size - index, + sizes = listOf(CommunalContentSize.HALF) + ) + } + } + private fun startListening() { if (isHostListening) { return diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt new file mode 100644 index 000000000000..0bd7d86c972d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt @@ -0,0 +1,8 @@ +package com.android.systemui.communal.shared + +/** Supported sizes for communal content in the layout grid. */ +enum class CommunalContentSize { + FULL, + HALF, + THIRD, +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt index e7f835f7b858..c3aaef76cb2f 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt @@ -1,5 +1,6 @@ package com.android.systemui.deviceentry +import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule import dagger.Module @@ -7,6 +8,7 @@ import dagger.Module includes = [ DeviceEntryRepositoryModule::class, + DeviceEntryHapticsRepositoryModule::class, ], ) object DeviceEntryModule diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt new file mode 100644 index 000000000000..1458404446e6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt @@ -0,0 +1,72 @@ +/* + * 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.deviceentry.data.repository + +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Interface for classes that can access device-entry haptics application state. */ +interface DeviceEntryHapticsRepository { + /** + * Whether a successful biometric haptic has been requested. Has not yet been handled if true. + */ + val successHapticRequest: Flow<Boolean> + + /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */ + val errorHapticRequest: Flow<Boolean> + + fun requestSuccessHaptic() + fun handleSuccessHaptic() + fun requestErrorHaptic() + fun handleErrorHaptic() +} + +/** Encapsulates application state for device entry haptics. */ +@SysUISingleton +class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository { + private val _successHapticRequest = MutableStateFlow(false) + override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow() + + private val _errorHapticRequest = MutableStateFlow(false) + override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow() + + override fun requestSuccessHaptic() { + _successHapticRequest.value = true + } + + override fun handleSuccessHaptic() { + _successHapticRequest.value = false + } + + override fun requestErrorHaptic() { + _errorHapticRequest.value = true + } + + override fun handleErrorHaptic() { + _errorHapticRequest.value = false + } +} + +@Module +interface DeviceEntryHapticsRepositoryModule { + @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt new file mode 100644 index 000000000000..53d6f737af8d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt @@ -0,0 +1,133 @@ +/* + * 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.deviceentry.domain.interactor + +import com.android.keyguard.logging.BiometricUnlockLogger +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.util.kotlin.sample +import com.android.systemui.util.time.SystemClock +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.combineTransform +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +/** + * Business logic for device entry haptic events. Determines whether the haptic should play. In + * particular, there are extra guards for whether device entry error and successes hatpics should + * play when the physical fingerprint sensor is located on the power button. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DeviceEntryHapticsInteractor +@Inject +constructor( + private val repository: DeviceEntryHapticsRepository, + fingerprintPropertyRepository: FingerprintPropertyRepository, + biometricSettingsRepository: BiometricSettingsRepository, + keyEventInteractor: KeyEventInteractor, + powerInteractor: PowerInteractor, + private val systemClock: SystemClock, + private val logger: BiometricUnlockLogger, +) { + private val powerButtonSideFpsEnrolled = + combineTransform( + fingerprintPropertyRepository.sensorType, + biometricSettingsRepository.isFingerprintEnrolledAndEnabled, + ) { sensorType, enrolledAndEnabled -> + if (sensorType == FingerprintSensorType.POWER_BUTTON) { + emit(enrolledAndEnabled) + } else { + emit(false) + } + } + .distinctUntilChanged() + private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown + private val lastPowerButtonWakeup: Flow<Long> = + powerInteractor.detailedWakefulness + .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) } + .map { systemClock.uptimeMillis() } + .onStart { + // If the power button hasn't been pressed, we still want this to evaluate to true: + // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs` + emit(recentPowerButtonPressThresholdMs * -1L - 1L) + } + + val playSuccessHaptic: Flow<Boolean> = + repository.successHapticRequest + .filter { it } + .sample( + combine( + powerButtonSideFpsEnrolled, + powerButtonDown, + lastPowerButtonWakeup, + ::Triple + ) + ) + .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> + val sideFpsAllowsHaptic = + !powerButtonDown && + systemClock.uptimeMillis() - lastPowerButtonWakeup > + recentPowerButtonPressThresholdMs + val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic + if (!allowHaptic) { + logger.d("Skip success haptic. Recent power button press or button is down.") + handleSuccessHaptic() // immediately handle, don't vibrate + } + allowHaptic + } + val playErrorHaptic: Flow<Boolean> = + repository.errorHapticRequest + .filter { it } + .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair)) + .map { (sideFpsEnrolled, powerButtonDown) -> + val allowHaptic = !sideFpsEnrolled || !powerButtonDown + if (!allowHaptic) { + logger.d("Skip error haptic. Power button is down.") + handleErrorHaptic() // immediately handle, don't vibrate + } + allowHaptic + } + + fun vibrateSuccess() { + repository.requestSuccessHaptic() + } + + fun vibrateError() { + repository.requestErrorHaptic() + } + + fun handleSuccessHaptic() { + repository.handleSuccessHaptic() + } + + fun handleErrorHaptic() { + repository.handleErrorHaptic() + } + + private val recentPowerButtonPressThresholdMs = 400L +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java index 5eb9808ebd7c..9c13a8c82b11 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java @@ -18,6 +18,7 @@ package com.android.systemui.doze; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.doze.dagger.DozeScope; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import javax.inject.Inject; @@ -28,16 +29,19 @@ import javax.inject.Inject; public class DozeAuthRemover implements DozeMachine.Part { private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final SelectedUserInteractor mSelectedUserInteractor; @Inject - public DozeAuthRemover(KeyguardUpdateMonitor keyguardUpdateMonitor) { + public DozeAuthRemover(KeyguardUpdateMonitor keyguardUpdateMonitor, + SelectedUserInteractor selectedUserInteractor) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mSelectedUserInteractor = selectedUserInteractor; } @Override public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { if (newState == DozeMachine.State.DOZE || newState == DozeMachine.State.DOZE_AOD) { - int currentUser = KeyguardUpdateMonitor.getCurrentUser(); + int currentUser = mSelectedUserInteractor.getSelectedUserId(); if (mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(currentUser)) { mKeyguardUpdateMonitor.clearBiometricRecognized(); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index 7da2cf150147..ba579188a8c9 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -31,13 +31,13 @@ import android.view.Display; import androidx.annotation.Nullable; -import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.doze.dagger.WrappedService; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.wakelock.SettableWakeLock; import com.android.systemui.util.wakelock.WakeLock; @@ -81,6 +81,7 @@ public class DozeScreenState implements DozeMachine.Part { @Nullable private UdfpsController mUdfpsController; private final DozeLog mDozeLog; private final DozeScreenBrightness mDozeScreenBrightness; + private final SelectedUserInteractor mSelectedUserInteractor; private int mPendingScreenState = Display.STATE_UNKNOWN; private SettableWakeLock mWakeLock; @@ -95,7 +96,8 @@ public class DozeScreenState implements DozeMachine.Part { AuthController authController, Provider<UdfpsController> udfpsControllerProvider, DozeLog dozeLog, - DozeScreenBrightness dozeScreenBrightness) { + DozeScreenBrightness dozeScreenBrightness, + SelectedUserInteractor selectedUserInteractor) { mDozeService = service; mHandler = handler; mParameters = parameters; @@ -105,6 +107,7 @@ public class DozeScreenState implements DozeMachine.Part { mUdfpsControllerProvider = udfpsControllerProvider; mDozeLog = dozeLog; mDozeScreenBrightness = dozeScreenBrightness; + mSelectedUserInteractor = selectedUserInteractor; updateUdfpsController(); if (mUdfpsController == null) { @@ -113,7 +116,7 @@ public class DozeScreenState implements DozeMachine.Part { } private void updateUdfpsController() { - if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) { + if (mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId())) { mUdfpsController = mUdfpsControllerProvider.get(); } else { mUdfpsController = null; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 07efbfef732b..319494252786 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -48,12 +48,11 @@ import com.android.internal.R; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; -import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.biometrics.AuthController; import com.android.systemui.plugins.SensorManagerPlugin; -import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.policy.DevicePostureController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.settings.SecureSettings; @@ -101,7 +100,7 @@ public class DozeSensors { private final SecureSettings mSecureSettings; private final DevicePostureController mDevicePostureController; private final AuthController mAuthController; - private final UserTracker mUserTracker; + private final SelectedUserInteractor mSelectedUserInteractor; private final boolean mScreenOffUdfpsEnabled; // Sensors @@ -158,7 +157,7 @@ public class DozeSensors { SecureSettings secureSettings, AuthController authController, DevicePostureController devicePostureController, - UserTracker userTracker + SelectedUserInteractor selectedUserInteractor ) { mSensorManager = sensorManager; mConfig = config; @@ -171,15 +170,15 @@ public class DozeSensors { mProximitySensor.setTag(TAG); mSelectivelyRegisterProxSensors = dozeParameters.getSelectivelyRegisterSensorsUsingProx(); mListeningProxSensors = !mSelectivelyRegisterProxSensors; + mSelectedUserInteractor = selectedUserInteractor; mScreenOffUdfpsEnabled = - config.screenOffUdfpsEnabled(KeyguardUpdateMonitor.getCurrentUser()); + config.screenOffUdfpsEnabled(mSelectedUserInteractor.getSelectedUserId()); mDevicePostureController = devicePostureController; mDevicePosture = mDevicePostureController.getDevicePosture(); mAuthController = authController; - mUserTracker = userTracker; mUdfpsEnrolled = - mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()); + mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId()); mAuthController.addCallback(mAuthControllerCallback); mTriggerSensors = new TriggerSensor[] { new TriggerSensor( @@ -255,7 +254,8 @@ public class DozeSensors { new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY), Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE, mConfig.wakeScreenGestureAvailable() - && mConfig.alwaysOnEnabled(mUserTracker.getUserId()), + && mConfig.alwaysOnEnabled( + mSelectedUserInteractor.getSelectedUserId(true)), DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE, false /* reports touch coordinates */, false /* touchscreen */ @@ -296,12 +296,13 @@ public class DozeSensors { private boolean udfpsLongPressConfigured() { return mUdfpsEnrolled - && (mConfig.alwaysOnEnabled(mUserTracker.getUserId()) || mScreenOffUdfpsEnabled); + && (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId(true)) + || mScreenOffUdfpsEnabled); } private boolean quickPickUpConfigured() { return mUdfpsEnrolled - && mConfig.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser()); + && mConfig.quickPickupSensorEnabled(mSelectedUserInteractor.getSelectedUserId()); } /** @@ -471,7 +472,7 @@ public class DozeSensors { private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) { - if (userId != mUserTracker.getUserId()) { + if (userId != mSelectedUserInteractor.getSelectedUserId(true)) { return; } for (TriggerSensor s : mTriggerSensors) { @@ -697,13 +698,13 @@ public class DozeSensors { } protected boolean enabledBySetting() { - if (!mConfig.enabled(mUserTracker.getUserId())) { + if (!mConfig.enabled(mSelectedUserInteractor.getSelectedUserId(true))) { return false; } else if (TextUtils.isEmpty(mSetting)) { return true; } return mSecureSettings.getIntForUser(mSetting, mSettingDefault ? 1 : 0, - mUserTracker.getUserId()) != 0; + mSelectedUserInteractor.getSelectedUserId(true)) != 0; } @Override @@ -873,7 +874,7 @@ public class DozeSensors { private void updateUdfpsEnrolled() { mUdfpsEnrolled = mAuthController.isUdfpsEnrolled( - KeyguardUpdateMonitor.getCurrentUser()); + mSelectedUserInteractor.getSelectedUserId()); for (TriggerSensor sensor : mTriggerSensors) { if (REASON_SENSOR_QUICK_PICKUP == sensor.mPulseReason) { sensor.setConfigured(quickPickUpConfigured()); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 85272a609588..795c3d4528c5 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -50,6 +50,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.Assert; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.ProximityCheck; @@ -102,6 +103,7 @@ public class DozeTriggers implements DozeMachine.Part { private final AuthController mAuthController; private final KeyguardStateController mKeyguardStateController; private final UserTracker mUserTracker; + private final SelectedUserInteractor mSelectedUserInteractor; private final UiEventLogger mUiEventLogger; private long mNotificationPulseTime; @@ -201,7 +203,8 @@ public class DozeTriggers implements DozeMachine.Part { SessionTracker sessionTracker, KeyguardStateController keyguardStateController, DevicePostureController devicePostureController, - UserTracker userTracker) { + UserTracker userTracker, + SelectedUserInteractor selectedUserInteractor) { mContext = context; mDozeHost = dozeHost; mConfig = config; @@ -213,7 +216,7 @@ public class DozeTriggers implements DozeMachine.Part { mDozeSensors = new DozeSensors(mContext.getResources(), mSensorManager, dozeParameters, config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor, - secureSettings, authController, devicePostureController, userTracker); + secureSettings, authController, devicePostureController, selectedUserInteractor); mDockManager = dockManager; mProxCheck = proxCheck; mDozeLog = dozeLog; @@ -222,6 +225,7 @@ public class DozeTriggers implements DozeMachine.Part { mUiEventLogger = uiEventLogger; mKeyguardStateController = keyguardStateController; mUserTracker = userTracker; + mSelectedUserInteractor = selectedUserInteractor; } @Override @@ -246,7 +250,7 @@ public class DozeTriggers implements DozeMachine.Part { return; } mNotificationPulseTime = SystemClock.elapsedRealtime(); - if (!mConfig.pulseOnNotificationEnabled(mUserTracker.getUserId())) { + if (!mConfig.pulseOnNotificationEnabled(mSelectedUserInteractor.getSelectedUserId(true))) { runIfNotNull(onPulseSuppressedListener); mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled"); return; diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 4764f223d121..ae0c8da73c40 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -180,6 +180,10 @@ object Flags { @JvmField val NEW_AOD_TRANSITION = unreleasedFlag("new_aod_transition", teamfood = true) + // TODO(b/305984787): + @JvmField + val REFACTOR_GETCURRENTUSER = unreleasedFlag("refactor_getcurrentuser", teamfood = true) + /** Flag to control the migration of face auth to modern architecture. */ // TODO(b/262838215): Tracking bug @JvmField val FACE_AUTH_REFACTOR = releasedFlag("face_auth_refactor") @@ -259,7 +263,7 @@ object Flags { // TODO(b/290652751): Tracking bug. @JvmField val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA = - unreleasedFlag("migrate_split_keyguard_bottom_area", teamfood = true) + releasedFlag("migrate_split_keyguard_bottom_area") // TODO(b/297037052): Tracking bug. @JvmField @@ -274,7 +278,7 @@ object Flags { /** Migrate the lock icon view to the new keyguard root view. */ // TODO(b/286552209): Tracking bug. - @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon", teamfood = true) + @JvmField val MIGRATE_LOCK_ICON = releasedFlag("migrate_lock_icon") // TODO(b/288276738): Tracking bug. @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard") @@ -419,7 +423,7 @@ object Flags { releasedFlag("incompatible_charging_battery_icon") // TODO(b/293585143): Tracking Bug - val INSTANT_TETHER = unreleasedFlag("instant_tether") + val INSTANT_TETHER = unreleasedFlag("instant_tether", teamfood = true) // TODO(b/294588085): Tracking Bug val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks") @@ -503,10 +507,9 @@ object Flags { @Keep @JvmField val WM_ENABLE_PARTIAL_SCREEN_SHARING = - unreleasedFlag( - name = "record_task_content", + releasedFlag( + name = "enable_record_task_content", namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER, - teamfood = true ) // TODO(b/254512674): Tracking Bug @@ -770,8 +773,7 @@ object Flags { // TODO(b/283740863): Tracking Bug @JvmField - val ENABLE_NEW_PRIVACY_DIALOG = - unreleasedFlag("enable_new_privacy_dialog", teamfood = true) + val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog") // TODO(b/289573946): Tracking Bug @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text", teamfood = true) @@ -797,8 +799,7 @@ object Flags { /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */ @JvmField - val ENABLE_CLOCK_KEYGUARD_PRESENTATION = - unreleasedFlag("enable_clock_keyguard_presentation", teamfood = true) + val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation") /** Enable the Compose implementation of the PeopleSpaceActivity. */ @JvmField @@ -818,8 +819,7 @@ object Flags { // TODO(b/287205379): Tracking bug @JvmField - val QS_CONTAINER_GRAPH_OPTIMIZER = unreleasedFlag( "qs_container_graph_optimizer", - teamfood = true) + val QS_CONTAINER_GRAPH_OPTIMIZER = releasedFlag( "qs_container_graph_optimizer") /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */ @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index c6c1f79c7113..5cc2e0aa811c 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -134,6 +134,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.telephony.TelephonyListenerManager; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.EmergencyDialerConstants; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.settings.GlobalSettings; @@ -195,6 +196,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final IDreamManager mDreamManager; private final DevicePolicyManager mDevicePolicyManager; private final LockPatternUtils mLockPatternUtils; + private final SelectedUserInteractor mSelectedUserInteractor; private final TelephonyListenerManager mTelephonyListenerManager; private final KeyguardStateController mKeyguardStateController; private final BroadcastDispatcher mBroadcastDispatcher; @@ -364,7 +366,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene PackageManager packageManager, ShadeController shadeController, KeyguardUpdateMonitor keyguardUpdateMonitor, - DialogLaunchAnimator dialogLaunchAnimator) { + DialogLaunchAnimator dialogLaunchAnimator, + SelectedUserInteractor selectedUserInteractor) { mContext = context; mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -399,6 +402,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mShadeController = shadeController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mDialogLaunchAnimator = dialogLaunchAnimator; + mSelectedUserInteractor = selectedUserInteractor; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -713,7 +717,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mUiEventLogger, mShadeController, mKeyguardUpdateMonitor, - mLockPatternUtils); + mLockPatternUtils, + mSelectedUserInteractor); dialog.setOnDismissListener(this); dialog.setOnShowListener(this); @@ -2222,6 +2227,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private GestureDetector mGestureDetector; private final ShadeController mShadeController; private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private SelectedUserInteractor mSelectedUserInteractor; private LockPatternUtils mLockPatternUtils; private float mWindowDimAmount; @@ -2300,7 +2306,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene UiEventLogger uiEventLogger, ShadeController shadeController, KeyguardUpdateMonitor keyguardUpdateMonitor, - LockPatternUtils lockPatternUtils) { + LockPatternUtils lockPatternUtils, + SelectedUserInteractor selectedUserInteractor) { // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to // dismiss this dialog when the device is locked. super(context, themeRes, false /* dismissOnDeviceLock */); @@ -2321,6 +2328,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mGestureDetector = new GestureDetector(mContext, mGestureListener); + mSelectedUserInteractor = selectedUserInteractor; } @Override @@ -2453,10 +2461,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } // If user entered from the lock screen and smart lock was enabled, disable it - int user = KeyguardUpdateMonitor.getCurrentUser(); + int user = mSelectedUserInteractor.getSelectedUserId(); boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user); if (mKeyguardShowing && userHasTrust) { - mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser()); + mLockPatternUtils.requireCredentialEntry(user); showSmartLockDisabledMessage(); } } diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt index 20d99d1e75fb..7b33e11a0c9c 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt @@ -32,6 +32,8 @@ data class SliderHapticFeedbackConfig( @FloatRange(from = 0.0, to = 1.0) val additionalVelocityMaxBump: Float = 0.15f, /** Additional time delta to wait between drag texture vibrations */ @FloatRange(from = 0.0) val deltaMillisForDragInterval: Float = 0f, + /** Progress threshold beyond which a new drag texture is delivered */ + @FloatRange(from = 0.0, to = 1.0) val deltaProgressForDragThreshold: Float = 0.015f, /** Number of low ticks in a drag texture composition. This is not expected to change */ val numberOfLowTicks: Int = 5, /** Maximum velocity allowed for vibration scaling. This is not expected to change. */ diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt index e6de156de0c4..f313fb3eef0f 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt @@ -46,6 +46,8 @@ class SliderHapticFeedbackProvider( private val positionAccelerateInterpolator = AccelerateInterpolator(config.progressInterpolatorFactor) private var dragTextureLastTime = clock.elapsedRealtime() + var dragTextureLastProgress = -1f + private set private val lowTickDurationMs = vibratorHelper.getPrimitiveDurations(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)[0] private var hasVibratedAtLowerBookend = false @@ -91,6 +93,9 @@ class SliderHapticFeedbackProvider( val elapsedSinceLastDrag = currentTime - dragTextureLastTime if (elapsedSinceLastDrag < thresholdUntilNextDragCallMillis) return + val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress) + if (deltaProgress < config.deltaProgressForDragThreshold) return + val velocityInterpolated = velocityAccelerateInterpolator.getInterpolation( min(absoluteVelocity / config.maxVelocityToScale, 1f) @@ -116,11 +121,14 @@ class SliderHapticFeedbackProvider( } vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING) dragTextureLastTime = currentTime + dragTextureLastProgress = normalizedSliderProgress } override fun onHandleAcquiredByTouch() {} - override fun onHandleReleasedFromTouch() {} + override fun onHandleReleasedFromTouch() { + dragTextureLastProgress = -1f + } override fun onLowerBookend() { if (!hasVibratedAtLowerBookend) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index b45613e0e182..47798955b43d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -35,6 +35,8 @@ import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionOldType; import static android.view.WindowManager.TransitionType; +import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER; + import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -76,13 +78,13 @@ import com.android.systemui.SystemUIApplication; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.power.shared.model.ScreenPowerState; import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier; import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder; import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder; import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel; import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.power.shared.model.ScreenPowerState; import com.android.systemui.settings.DisplayTracker; import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; @@ -599,11 +601,18 @@ public class KeyguardService extends Service { mKeyguardViewMediator.setSwitchingUser(switching); } + /** + * @deprecated This binder call is not listened to anymore. Instead the current user is + * tracked in SelectedUserInteractor.getSelectedUserId() + */ @Override // Binder interface + @Deprecated public void setCurrentUser(int userId) { - trace("setCurrentUser userId=" + userId); + trace("Deprecated/NOT USED: setCurrentUser userId=" + userId); checkPermission(); - mKeyguardViewMediator.setCurrentUser(userId); + if (!mFlags.isEnabled(REFACTOR_GETCURRENTUSER)) { + mKeyguardViewMediator.setCurrentUser(userId); + } } @Override // Binder interface diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index a511713eddd3..119ade48d4f7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -28,6 +28,7 @@ import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder @@ -44,6 +45,7 @@ import com.android.systemui.res.R import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.KeyguardIndicationController +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import javax.inject.Inject @@ -72,7 +74,9 @@ constructor( private val keyguardIndicationController: KeyguardIndicationController, private val lockIconViewController: LockIconViewController, private val shadeInteractor: ShadeInteractor, - private val interactionJankMonitor: InteractionJankMonitor + private val interactionJankMonitor: InteractionJankMonitor, + private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, + private val vibratorHelper: VibratorHelper, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null @@ -143,6 +147,8 @@ constructor( shadeInteractor, { keyguardStatusViewController!!.getClockController() }, interactionJankMonitor, + deviceEntryHapticsInteractor, + vibratorHelper, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 39742a02000b..e893c6305dee 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -38,6 +38,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE; import static com.android.systemui.DejankUtils.whitelistIpcs; +import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER; import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; @@ -163,6 +164,7 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; @@ -617,6 +619,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void onUserSwitching(int userId) { if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId)); synchronized (KeyguardViewMediator.this) { + if (mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) { + notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); + } resetKeyguardDonePendingLocked(); dismiss(null /* callback */, null /* message */); adjustStatusBarLocked(); @@ -742,7 +747,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { - final int currentUser = KeyguardUpdateMonitor.getCurrentUser(); + final int currentUser = mSelectedUserInteractor.getSelectedUserId(); if (mLockPatternUtils.isSecure(currentUser)) { mLockPatternUtils.getDevicePolicyManager().reportFailedBiometricAttempt( currentUser); @@ -760,7 +765,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onTrustChanged(int userId) { - if (userId == KeyguardUpdateMonitor.getCurrentUser()) { + if (userId == mSelectedUserInteractor.getSelectedUserId()) { synchronized (KeyguardViewMediator.this) { notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); } @@ -769,7 +774,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onStrongAuthStateChanged(int userId) { - if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { + if (mLockPatternUtils.isUserInLockdown(mSelectedUserInteractor.getSelectedUserId())) { doKeyguardLocked(null); } } @@ -784,7 +789,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void keyguardDone(int targetUserId) { - if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) { + if (targetUserId != mSelectedUserInteractor.getSelectedUserId()) { return; } if (DEBUG) Log.d(TAG, "keyguardDone"); @@ -807,7 +812,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void keyguardDonePending(int targetUserId) { Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending"); if (DEBUG) Log.d(TAG, "keyguardDonePending"); - if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) { + if (targetUserId != mSelectedUserInteractor.getSelectedUserId()) { Trace.endSection(); return; } @@ -888,7 +893,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public int getBouncerPromptReason() { - int currentUser = KeyguardUpdateMonitor.getCurrentUser(); + int currentUser = mSelectedUserInteractor.getSelectedUserId(); boolean trustAgentsEnabled = mUpdateMonitor.isTrustUsuallyManaged(currentUser); boolean biometricsEnrolled = mUpdateMonitor.isUnlockingWithBiometricsPossible(currentUser); @@ -1316,6 +1321,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private DeviceConfigProxy mDeviceConfig; private DozeParameters mDozeParameters; + private SelectedUserInteractor mSelectedUserInteractor; private final KeyguardStateController mKeyguardStateController; private final KeyguardStateController.Callback mKeyguardStateControllerCallback = @@ -1396,7 +1402,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Main CoroutineDispatcher mainDispatcher, Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel, SystemPropertiesHelper systemPropertiesHelper, - Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) { + Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager, + SelectedUserInteractor selectedUserInteractor) { mContext = context; mUserTracker = userTracker; mFalsingCollector = falsingCollector; @@ -1436,6 +1443,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode); })); mDozeParameters = dozeParameters; + mSelectedUserInteractor = selectedUserInteractor; mStatusBarStateController = statusBarStateController; statusBarStateController.addCallback(this); @@ -1493,14 +1501,17 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId()); + if (!mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) { + KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId()); + } // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard // is disabled. if (isKeyguardServiceEnabled()) { setShowingLocked(!shouldWaitForProvisioning() && !mLockPatternUtils.isLockScreenDisabled( - KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */); + mSelectedUserInteractor.getSelectedUserId()), + true /* forceCallbacks */); } else { // The system's keyguard is disabled or missing. setShowingLocked(false /* showing */, true /* forceCallbacks */); @@ -1622,11 +1633,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // Lock immediately based on setting if secure (user has a pin/pattern/password). // This also "locks" the device when not secure to provide easy access to the // camera while preventing unwanted input. - int currentUser = KeyguardUpdateMonitor.getCurrentUser(); + int currentUser = mSelectedUserInteractor.getSelectedUserId(); final boolean lockImmediately = mLockPatternUtils.getPowerButtonInstantlyLocks(currentUser) || !mLockPatternUtils.isSecure(currentUser); - long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser()); + long timeout = getLockTimeout(mSelectedUserInteractor.getSelectedUserId()); mLockLater = false; if (mShowing && !mKeyguardStateController.isKeyguardGoingAway()) { // If we are going to sleep but the keyguard is showing (and will continue to be @@ -1807,7 +1818,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } private void doKeyguardLaterLocked() { - long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser()); + long timeout = getLockTimeout(mSelectedUserInteractor.getSelectedUserId()); if (timeout == 0) { doKeyguardLocked(null); } else { @@ -1916,7 +1927,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private void maybeSendUserPresentBroadcast() { if (mSystemReady && mLockPatternUtils.isLockScreenDisabled( - KeyguardUpdateMonitor.getCurrentUser())) { + mSelectedUserInteractor.getSelectedUserId())) { // Lock screen is disabled because the user has set the preference to "None". // In this case, send out ACTION_USER_PRESENT here instead of in // handleKeyguardDone() @@ -1925,7 +1936,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // Skipping the lockscreen because we're not yet provisioned, but we still need to // notify the StrongAuthTracker that it's now safe to run trust agents, in case the // user sets a credential later. - mLockPatternUtils.userPresent(KeyguardUpdateMonitor.getCurrentUser()); + mLockPatternUtils.userPresent(mSelectedUserInteractor.getSelectedUserId()); } } @@ -1966,7 +1977,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mExternallyEnabled = enabled; if (!enabled && mShowing) { - if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { + if (mLockPatternUtils.isUserInLockdown( + mSelectedUserInteractor.getSelectedUserId())) { Log.d(TAG, "keyguardEnabled(false) overridden by user lockdown"); return; } @@ -2197,7 +2209,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private void doKeyguardLocked(Bundle options) { // if another app is disabling us, don't show if (!mExternallyEnabled - && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { + && !mLockPatternUtils.isUserInLockdown( + mSelectedUserInteractor.getSelectedUserId())) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); mNeedToReshowWhenReenabled = true; @@ -2253,7 +2266,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false); - if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser()) + if (mLockPatternUtils.isLockScreenDisabled(mSelectedUserInteractor.getSelectedUserId()) && !lockedOrMissing && !forceShow) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off"); return; @@ -2384,7 +2397,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } public boolean isSecure() { - return isSecure(KeyguardUpdateMonitor.getCurrentUser()); + return isSecure(mSelectedUserInteractor.getSelectedUserId()); } public boolean isSecure(int userId) { @@ -2605,7 +2618,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, */ private void handleKeyguardDone() { Trace.beginSection("KeyguardViewMediator#handleKeyguardDone"); - final int currentUser = KeyguardUpdateMonitor.getCurrentUser(); + final int currentUser = mSelectedUserInteractor.getSelectedUserId(); mUiBgExecutor.execute(() -> { if (mLockPatternUtils.isSecure(currentUser)) { mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser); @@ -2631,7 +2644,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private void sendUserPresentBroadcast() { synchronized (this) { if (mBootCompleted) { - int currentUserId = KeyguardUpdateMonitor.getCurrentUser(); + int currentUserId = mSelectedUserInteractor.getSelectedUserId(); final UserHandle currentUser = new UserHandle(currentUserId); final UserManager um = (UserManager) mContext.getSystemService( Context.USER_SERVICE); @@ -2679,7 +2692,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private void playSound(int soundId) { if (soundId == 0) return; int lockscreenSoundsEnabled = mSystemSettings.getIntForUser(LOCKSCREEN_SOUNDS_ENABLED, 1, - KeyguardUpdateMonitor.getCurrentUser()); + mSelectedUserInteractor.getSelectedUserId()); if (lockscreenSoundsEnabled == 1) { mLockSounds.stop(mLockSoundStreamId); @@ -2732,7 +2745,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, */ private void handleShow(Bundle options) { Trace.beginSection("KeyguardViewMediator#handleShow"); - final int currentUser = KeyguardUpdateMonitor.getCurrentUser(); + final int currentUser = mSelectedUserInteractor.getSelectedUserId(); if (mLockPatternUtils.isSecure(currentUser)) { mLockPatternUtils.getDevicePolicyManager().reportKeyguardSecured(currentUser); } @@ -2787,7 +2800,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * Schedule 4-hour idle timeout for non-strong biometrics when the device is locked */ private void scheduleNonStrongBiometricIdleTimeout() { - final int currentUser = KeyguardUpdateMonitor.getCurrentUser(); + final int currentUser = mSelectedUserInteractor.getSelectedUserId(); // If unlocking with non-strong (i.e. weak or convenience) biometrics is possible, schedule // 4hr idle timeout after which non-strong biometrics can't be used to unlock device until // unlocking with strong biometric or primary auth (i.e. PIN/pattern/password) @@ -3378,7 +3391,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, if (forceClearFlags) { try { mStatusBarService.disableForUser(flags, mStatusBarDisableToken, - mContext.getPackageName(), mUserTracker.getUserId()); + mContext.getPackageName(), + mSelectedUserInteractor.getSelectedUserId(true)); } catch (RemoteException e) { Log.d(TAG, "Failed to force clear flags", e); } @@ -3405,7 +3419,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, try { mStatusBarService.disableForUser(flags, mStatusBarDisableToken, - mContext.getPackageName(), mUserTracker.getUserId()); + mContext.getPackageName(), + mSelectedUserInteractor.getSelectedUserId(true)); } catch (RemoteException e) { Log.d(TAG, "Failed to set disable flags: " + flags, e); } @@ -3728,7 +3743,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, for (int i = size - 1; i >= 0; i--) { IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i); try { - callback.onShowingStateChanged(showing, KeyguardUpdateMonitor.getCurrentUser()); + callback.onShowingStateChanged(showing, + mSelectedUserInteractor.getSelectedUserId()); } catch (RemoteException e) { Slog.w(TAG, "Failed to call onShowingStateChanged", e); if (e instanceof DeadObjectException) { @@ -3771,10 +3787,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mKeyguardStateCallbacks.add(callback); try { callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure()); - callback.onShowingStateChanged(mShowing, KeyguardUpdateMonitor.getCurrentUser()); + callback.onShowingStateChanged(mShowing, + mSelectedUserInteractor.getSelectedUserId()); callback.onInputRestrictedStateChanged(mInputRestricted); callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust( - KeyguardUpdateMonitor.getCurrentUser())); + mSelectedUserInteractor.getSelectedUserId())); } catch (RemoteException e) { Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e); } 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 019d4283a03c..8b93b171d241 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -68,6 +68,7 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; @@ -150,7 +151,8 @@ public class KeyguardModule { @Main CoroutineDispatcher mainDispatcher, Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel, SystemPropertiesHelper systemPropertiesHelper, - Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) { + Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager, + SelectedUserInteractor selectedUserInteractor) { return new KeyguardViewMediator( context, uiEventLogger, @@ -194,7 +196,8 @@ public class KeyguardModule { mainDispatcher, dreamingToLockscreenTransitionViewModel, systemPropertiesHelper, - wmLockscreenVisibilityManager); + wmLockscreenVisibilityManager, + selectedUserInteractor); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index a4a312656039..2dc490886c3d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -31,7 +31,6 @@ import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.dreams.DreamOverlayCallbackController -import com.android.systemui.keyguard.ScreenLifecycle import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.DismissAction @@ -41,9 +40,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardDone import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.statusbar.phone.BiometricUnlockController -import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode -import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -111,6 +107,8 @@ interface KeyguardRepository { /** Is the always-on display available to be used? */ val isAodAvailable: Flow<Boolean> + fun setAodAvailable(value: Boolean) + /** * Observable for whether we are in doze state. * @@ -160,6 +158,8 @@ interface KeyguardRepository { /** Observable for biometric unlock modes */ val biometricUnlockState: Flow<BiometricUnlockModel> + fun setBiometricUnlockState(value: BiometricUnlockModel) + /** Approximate location on the screen of the fingerprint sensor. */ val fingerprintSensorLocation: Flow<Point?> @@ -240,12 +240,9 @@ class KeyguardRepositoryImpl @Inject constructor( statusBarStateController: StatusBarStateController, - screenLifecycle: ScreenLifecycle, - biometricUnlockController: BiometricUnlockController, private val keyguardStateController: KeyguardStateController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val dozeTransitionListener: DozeTransitionListener, - private val dozeParameters: DozeParameters, private val authController: AuthController, private val dreamOverlayCallbackController: DreamOverlayCallbackController, @Main private val mainDispatcher: CoroutineDispatcher, @@ -303,24 +300,12 @@ constructor( } .distinctUntilChanged() - override val isAodAvailable: Flow<Boolean> = - conflatedCallbackFlow { - val callback = - DozeParameters.Callback { - trySendWithFailureLogging( - dozeParameters.alwaysOn, - TAG, - "updated isAodAvailable" - ) - } - - dozeParameters.addCallback(callback) - // Adding the callback does not send an initial update. - trySendWithFailureLogging(dozeParameters.alwaysOn, TAG, "initial isAodAvailable") + private val _isAodAvailable = MutableStateFlow(false) + override val isAodAvailable: Flow<Boolean> = _isAodAvailable.asStateFlow() - awaitClose { dozeParameters.removeCallback(callback) } - } - .distinctUntilChanged() + override fun setAodAvailable(value: Boolean) { + _isAodAvailable.value = value + } override val isKeyguardOccluded: Flow<Boolean> = conflatedCallbackFlow { @@ -506,30 +491,11 @@ constructor( statusBarStateIntToObject(statusBarStateController.state) ) - override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow { - fun dispatchUpdate() { - trySendWithFailureLogging( - biometricModeIntToObject(biometricUnlockController.mode), - TAG, - "biometric mode" - ) - } - - val callback = - object : BiometricUnlockController.BiometricUnlockEventsListener { - override fun onModeChanged(@WakeAndUnlockMode mode: Int) { - dispatchUpdate() - } - - override fun onResetMode() { - dispatchUpdate() - } - } - - biometricUnlockController.addListener(callback) - dispatchUpdate() + private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE) + override val biometricUnlockState = _biometricUnlockState.asStateFlow() - awaitClose { biometricUnlockController.removeListener(callback) } + override fun setBiometricUnlockState(value: BiometricUnlockModel) { + _biometricUnlockState.value = value } override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow { @@ -662,20 +628,6 @@ constructor( } } - private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel { - return when (value) { - 0 -> BiometricUnlockModel.NONE - 1 -> BiometricUnlockModel.WAKE_AND_UNLOCK - 2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING - 3 -> BiometricUnlockModel.SHOW_BOUNCER - 4 -> BiometricUnlockModel.ONLY_WAKE - 5 -> BiometricUnlockModel.UNLOCK_COLLAPSING - 6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM - 7 -> BiometricUnlockModel.DISMISS_BOUNCER - else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value") - } - } - private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel { return when (state) { DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt new file mode 100644 index 000000000000..cb003a760b26 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt @@ -0,0 +1,42 @@ +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING +import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode +import javax.inject.Inject + +@SysUISingleton +class BiometricUnlockInteractor +@Inject +constructor( + private val keyguardRepository: KeyguardRepository, +) { + + fun setBiometricUnlockState(@WakeAndUnlockMode unlockStateInt: Int) { + val state = biometricModeIntToObject(unlockStateInt) + keyguardRepository.setBiometricUnlockState(state) + } + + private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel { + return when (value) { + MODE_NONE -> BiometricUnlockModel.NONE + MODE_WAKE_AND_UNLOCK -> BiometricUnlockModel.WAKE_AND_UNLOCK + MODE_WAKE_AND_UNLOCK_PULSING -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING + MODE_SHOW_BOUNCER -> BiometricUnlockModel.SHOW_BOUNCER + MODE_ONLY_WAKE -> BiometricUnlockModel.ONLY_WAKE + MODE_UNLOCK_COLLAPSING -> BiometricUnlockModel.UNLOCK_COLLAPSING + MODE_WAKE_AND_UNLOCK_FROM_DREAM -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM + MODE_DISMISS_BOUNCER -> BiometricUnlockModel.DISMISS_BOUNCER + else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value") + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt index 0c898befe6a0..af1802fac3cc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt @@ -28,6 +28,10 @@ constructor( private val keyguardRepository: KeyguardRepository, ) { + fun setAodAvailable(value: Boolean) { + keyguardRepository.setAodAvailable(value) + } + fun setIsDozing(isDozing: Boolean) { keyguardRepository.setIsDozing(isDozing) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index ad2ec69b77ce..f1649fb2851e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.keyguard.KeyguardSecurityModel -import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags @@ -27,19 +26,20 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.Utils.Companion.toQuad import com.android.systemui.util.kotlin.Utils.Companion.toQuint import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import com.android.wm.shell.animation.Interpolators +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch -import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds @SysUISingleton class FromPrimaryBouncerTransitionInteractor @@ -51,6 +51,7 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val flags: FeatureFlags, private val keyguardSecurityModel: KeyguardSecurityModel, + private val selectedUserInteractor: SelectedUserInteractor, private val powerInteractor: PowerInteractor, ) : TransitionInteractor( @@ -132,7 +133,7 @@ constructor( .collect { ( isBouncerShowing, - isAwake, + isAwake, lastStartedTransitionStep, occluded, isActiveDreamLockscreenHosted) -> @@ -162,8 +163,7 @@ constructor( ), ::toQuad ) - .collect { - (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable) + .collect { (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable) -> if ( !isBouncerShowing && @@ -181,25 +181,24 @@ constructor( private fun listenForPrimaryBouncerToDreamingLockscreenHosted() { scope.launch { keyguardInteractor.primaryBouncerShowing - .sample( - combine( - keyguardInteractor.isActiveDreamLockscreenHosted, - transitionInteractor.startedKeyguardTransitionStep, - ::Pair - ), - ::toTriple - ) - .collect { (isBouncerShowing, - isActiveDreamLockscreenHosted, - lastStartedTransitionStep) -> - if ( - !isBouncerShowing && - isActiveDreamLockscreenHosted && - lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER - ) { - startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) - } + .sample( + combine( + keyguardInteractor.isActiveDreamLockscreenHosted, + transitionInteractor.startedKeyguardTransitionStep, + ::Pair + ), + ::toTriple + ) + .collect { + (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) -> + if ( + !isBouncerShowing && + isActiveDreamLockscreenHosted && + lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER + ) { + startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) } + } } } @@ -221,7 +220,7 @@ constructor( ) { val securityMode = keyguardSecurityModel.getSecurityMode( - KeyguardUpdateMonitor.getCurrentUser() + selectedUserInteractor.getSelectedUserId() ) // IME for password requires a slightly faster animation val duration = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt index cab69285cc94..628e912253eb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt @@ -24,7 +24,7 @@ import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.keyguard.shared.model.DismissAction import com.android.systemui.keyguard.shared.model.KeyguardDone import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.Utils.Companion.toQuad import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -40,11 +40,11 @@ class KeyguardDismissInteractor @Inject constructor( trustRepository: TrustRepository, - val keyguardRepository: KeyguardRepository, - val primaryBouncerInteractor: PrimaryBouncerInteractor, - val alternateBouncerInteractor: AlternateBouncerInteractor, - val powerInteractor: PowerInteractor, - val userInteractor: UserInteractor, + private val keyguardRepository: KeyguardRepository, + primaryBouncerInteractor: PrimaryBouncerInteractor, + alternateBouncerInteractor: AlternateBouncerInteractor, + powerInteractor: PowerInteractor, + private val selectedUserInteractor: SelectedUserInteractor, ) { /* * Updates when a biometric has authenticated the device and is requesting to dismiss @@ -82,7 +82,7 @@ constructor( */ private val primaryAuthenticated: Flow<Unit> = primaryBouncerInteractor.keyguardAuthenticatedPrimaryAuth - .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() } + .filter { authedUserId -> authedUserId == selectedUserInteractor.getSelectedUserId() } .map {} // map to Unit /* @@ -92,7 +92,7 @@ constructor( */ private val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Unit> = primaryBouncerInteractor.userRequestedBouncerWhenAlreadyAuthenticated - .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() } + .filter { authedUserId -> authedUserId == selectedUserInteractor.getSelectedUserId() } .map {} // map to Unit /** Updates when keyguardDone should be requested. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt index f14552ba0685..87d8164e1cb9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt @@ -25,20 +25,18 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor import com.android.systemui.keyguard.shared.model.KeyguardDone import com.android.systemui.log.core.LogLevel -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch /** Handles keyguard dismissal requests. */ -@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class KeyguardDismissBinder @Inject constructor( private val interactor: KeyguardDismissInteractor, - private val userInteractor: UserInteractor, + private val selectedUserInteractor: SelectedUserInteractor, private val viewMediatorCallback: ViewMediatorCallback, @Application private val scope: CoroutineScope, private val keyguardLogger: KeyguardLogger, @@ -55,11 +53,15 @@ constructor( when (keyguardDoneTiming) { KeyguardDone.LATER -> { log("keyguardDonePending") - viewMediatorCallback.keyguardDonePending(userInteractor.getSelectedUserId()) + viewMediatorCallback.keyguardDonePending( + selectedUserInteractor.getSelectedUserId() + ) } else -> { log("keyguardDone") - viewMediatorCallback.keyguardDone(userInteractor.getSelectedUserId()) + viewMediatorCallback.keyguardDone( + selectedUserInteractor.getSelectedUserId() + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index c72e6ce0b7d6..4d5c503d1c4e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.DrawableRes +import android.view.HapticFeedbackConstants import android.view.View import android.view.View.OnLayoutChangeListener import android.view.ViewGroup @@ -29,6 +30,7 @@ import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.TransitionState @@ -38,6 +40,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ClockController import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.ViewPriority import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator @@ -45,6 +48,7 @@ import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo import javax.inject.Provider import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */ @@ -62,6 +66,8 @@ object KeyguardRootViewBinder { shadeInteractor: ShadeInteractor, clockControllerProvider: Provider<ClockController>?, interactionJankMonitor: InteractionJankMonitor?, + deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?, + vibratorHelper: VibratorHelper?, ): DisposableHandle { var onLayoutChangeListener: OnLayoutChange? = null val childViews = mutableMapOf<Int, View?>() @@ -177,6 +183,44 @@ object KeyguardRootViewBinder { } } } + + if (deviceEntryHapticsInteractor != null && vibratorHelper != null) { + launch { + deviceEntryHapticsInteractor.playSuccessHaptic + .filter { it } + .collect { + if ( + featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) + ) { + vibratorHelper.performHapticFeedback( + view, + HapticFeedbackConstants.CONFIRM, + ) + } else { + vibratorHelper.vibrateAuthSuccess("device-entry::success") + } + deviceEntryHapticsInteractor.handleSuccessHaptic() + } + } + + launch { + deviceEntryHapticsInteractor.playErrorHaptic + .filter { it } + .collect { + if ( + featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) + ) { + vibratorHelper.performHapticFeedback( + view, + HapticFeedbackConstants.REJECT, + ) + } else { + vibratorHelper.vibrateAuthSuccess("device-entry::error") + } + deviceEntryHapticsInteractor.handleErrorHaptic() + } + } + } } } viewModel.clockControllerProvider = clockControllerProvider @@ -189,7 +233,7 @@ object KeyguardRootViewBinder { view.setOnHierarchyChangeListener( object : OnHierarchyChangeListener { override fun onChildViewAdded(parent: View, child: View) { - childViews.put(child.id, view) + childViews.put(child.id, child) } override fun onChildViewRemoved(parent: View, child: View) { 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 5a4bbef587af..692984a90a14 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 @@ -46,6 +46,7 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder @@ -114,6 +115,7 @@ constructor( private val chipbarCoordinator: ChipbarCoordinator, private val keyguardStateController: KeyguardStateController, private val shadeInteractor: ShadeInteractor, + private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, ) { val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) @@ -339,6 +341,8 @@ constructor( shadeInteractor, null, // clock provider only needed for burn in null, // jank monitor not required for preview mode + null, // device entry haptics not required for preview mode + null, // device entry haptics not required for preview mode ) ) rootView.addView( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt index 9371d4e2d465..342a440d972b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt @@ -50,11 +50,8 @@ constructor( override fun addViews(constraintLayout: ConstraintLayout) { if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { - val view = - LayoutInflater.from(constraintLayout.context) - .inflate(R.layout.ambient_indication, constraintLayout, false) - - constraintLayout.addView(view) + LayoutInflater.from(constraintLayout.context) + .inflate(R.layout.ambient_indication, constraintLayout, true) } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt index 8634b0911391..a53f0f11c380 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt @@ -19,7 +19,11 @@ 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.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject @@ -36,21 +40,23 @@ constructor(private val service: IMediaProjectionManager) { * * @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) + fun notifyProjectionInitiated(sessionCreationSource: SessionCreationSource) { + notifyToServer( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED, + sessionCreationSource + ) } /** * Request to log that the permission request moved to the given state. * - * Should not be used for the initialization state, since that + * Should not be used for the initialization state, since that should use {@link + * MediaProjectionMetricsLogger#notifyProjectionInitiated(Int)} and pass the + * sessionCreationSource. */ fun notifyPermissionProgress(state: Int) { // TODO validate state is valid - notifyToServer( - state, - FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN) + notifyToServer(state, SessionCreationSource.UNKNOWN) } /** @@ -64,16 +70,21 @@ constructor(private val service: IMediaProjectionManager) { * 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) { + private fun notifyToServer(state: Int, sessionCreationSource: SessionCreationSource) { Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource") try { service.notifyPermissionRequestStateChange( - Process.myUid(), state, sessionCreationSource) + Process.myUid(), + state, + sessionCreationSource.toMetricsConstant() + ) } catch (e: RemoteException) { Log.e( TAG, - "Error notifying server of permission flow state $state from source $sessionCreationSource", - e) + "Error notifying server of permission flow state $state from source " + + "$sessionCreationSource", + e + ) } } @@ -81,3 +92,18 @@ constructor(private val service: IMediaProjectionManager) { const val TAG = "MediaProjectionMetricsLogger" } } + +enum class SessionCreationSource { + APP, + CAST, + SYSTEM_UI_SCREEN_RECORDER, + UNKNOWN; + + fun toMetricsConstant(): Int = + when (this) { + APP -> METRICS_CREATION_SOURCE_APP + CAST -> METRICS_CREATION_SOURCE_CAST + SYSTEM_UI_SCREEN_RECORDER -> METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER + UNKNOWN -> METRICS_CREATION_SOURCE_UNKNOWN + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt index b5d3e913cadb..0bbcfd9de24c 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt @@ -87,14 +87,15 @@ class MediaProjectionAppSelectorActivity( override fun getLayoutResource() = R.layout.media_projection_app_selector - public override fun onCreate(bundle: Bundle?) { + public override fun onCreate(savedInstanceState: Bundle?) { lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) component = componentFactory.create( hostUserHandle = hostUserHandle, callingPackage = callingPackage, view = this, - resultHandler = this + resultHandler = this, + isFirstStart = savedInstanceState == null ) component.lifecycleObservers.forEach { lifecycle.addObserver(it) } @@ -113,7 +114,7 @@ class MediaProjectionAppSelectorActivity( reviewGrantedConsentRequired = intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false) - super.onCreate(bundle) + super.onCreate(savedInstanceState) controller.init() // we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in // our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise 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 2217509167ef..8c6f307c84d6 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -146,6 +146,9 @@ interface MediaProjectionAppSelectorComponent { @BindsInstance @MediaProjectionAppSelector callingPackage: String?, @BindsInstance view: MediaProjectionAppSelectorView, @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler, + // Whether the app selector is starting for the first time. False when it is re-starting + // due to a config change. + @BindsInstance @MediaProjectionAppSelector isFirstStart: Boolean, ): MediaProjectionAppSelectorComponent } 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 fced117a8132..69132d3662d4 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt @@ -18,6 +18,8 @@ package com.android.systemui.mediaprojection.appselector import android.content.ComponentName import android.os.UserHandle +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader @@ -43,9 +45,17 @@ constructor( @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName, @MediaProjectionAppSelector private val callerPackageName: String?, private val thumbnailLoader: RecentTaskThumbnailLoader, + @MediaProjectionAppSelector private val isFirstStart: Boolean, + private val logger: MediaProjectionMetricsLogger, ) { fun init() { + // Only log during the first start of the app selector. + // Don't log when the app selector restarts due to a config change. + if (isFirstStart) { + logger.notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + } + scope.launch { val recentTasks = recentTaskListProvider.loadRecentTasks() 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 a9e6c53b3bcd..e9b458271ef7 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 @@ -22,6 +22,7 @@ import android.content.ComponentName data class RecentTask( val taskId: Int, + val displayId: Int, @UserIdInt val userId: Int, val topActivityComponent: ComponentName?, val baseIntentComponent: ComponentName?, 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 aa4c4e55c718..730aa620690a 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 @@ -60,6 +60,7 @@ constructor( .map { RecentTask( it.taskId, + it.displayId, it.userId, it.topActivity, it.baseIntent?.component, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt index fd1a683dc78f..ba837dba5354 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt @@ -130,10 +130,10 @@ constructor( view.width, view.height ) - activityOptions.setPendingIntentBackgroundActivityStartMode( + activityOptions.pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED - ) activityOptions.launchCookie = launchCookie + activityOptions.launchDisplayId = task.displayId activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle()) resultHandler.returnSelectedApp(launchCookie) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index d08d0400f354..fa418fc8b98b 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -53,7 +53,9 @@ import android.view.Window; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; import com.android.systemui.mediaprojection.MediaProjectionServiceHelper; +import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; @@ -74,6 +76,7 @@ public class MediaProjectionPermissionActivity extends Activity private final FeatureFlags mFeatureFlags; private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver; private final StatusBarManager mStatusBarManager; + private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private String mPackageName; private int mUid; @@ -90,15 +93,17 @@ public class MediaProjectionPermissionActivity extends Activity @Inject public MediaProjectionPermissionActivity(FeatureFlags featureFlags, Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver, - StatusBarManager statusBarManager) { + StatusBarManager statusBarManager, + MediaProjectionMetricsLogger mediaProjectionMetricsLogger) { mFeatureFlags = featureFlags; mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver; mStatusBarManager = statusBarManager; + mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; } @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); final Intent launchingIntent = getIntent(); mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra( @@ -133,6 +138,10 @@ public class MediaProjectionPermissionActivity extends Activity try { if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) { + if (savedInstanceState == null) { + mMediaProjectionMetricsLogger.notifyProjectionInitiated( + SessionCreationSource.APP); + } final IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName, mReviewGrantedConsentRequired); @@ -231,6 +240,13 @@ public class MediaProjectionPermissionActivity extends Activity mDialog = dialogBuilder.create(); } + if (savedInstanceState == null) { + mMediaProjectionMetricsLogger.notifyProjectionInitiated( + appName == null + ? SessionCreationSource.CAST + : SessionCreationSource.APP); + } + setUpDialog(mDialog); mDialog.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 733383e344b8..58c4f0d029c7 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -96,6 +96,44 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +/** Variables and functions that is related to Emoji. */ +class EmojiHelper { + static final CharSequence EMOJI_CAKE = "\ud83c\udf82"; + + // This regex can be used to match Unicode emoji characters and character sequences. It's from + // the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor + // changes to fit our needs. It should be updated once new emoji categories are added. + // + // Emoji categories that can be matched by this regex: + // - Country flags. "\p{RI}\p{RI}" matches country flags since they always consist of 2 Unicode + // scalars. + // - Single-Character Emoji. "\p{Emoji}" matches Single-Character Emojis. + // - Emoji with modifiers. E.g. Emojis with different skin tones. "\p{Emoji}\p{EMod}" matches + // them. + // - Emoji Presentation. Those are characters which can normally be drawn as either text or as + // Emoji. "\p{Emoji}\x{FE0F}" matches them. + // - Emoji Keycap. E.g. Emojis for number 0 to 9. "\p{Emoji}\x{FE0F}\x{20E3}" matches them. + // - Emoji tag sequence. "\p{Emoji}[\x{E0020}-\x{E007E}]+\x{E007F}" matches them. + // - Emoji Zero-Width Joiner (ZWJ) Sequence. A ZWJ emoji is actually multiple emojis joined by + // the jointer "0x200D". + // + // Note: since "\p{Emoji}" also matches some ASCII characters like digits 0-9, we use + // "\p{Emoji}&&\p{So}" to exclude them. This is the change we made from the official emoji + // regex. + private static final String UNICODE_EMOJI_REGEX = + "\\p{RI}\\p{RI}|" + + "(" + + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" + + "|[\\p{Emoji}&&\\p{So}]" + + ")" + + "(" + + "\\x{200D}" + + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" + + "?)*"; + + static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX); +} + /** Functions that help creating the People tile layouts. */ public class PeopleTileViewHelper { /** Turns on debugging information about People Space. */ @@ -125,8 +163,6 @@ public class PeopleTileViewHelper { private static final int MESSAGES_COUNT_OVERFLOW = 6; - private static final CharSequence EMOJI_CAKE = "\ud83c\udf82"; - private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+"); private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+"); private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+"); @@ -134,39 +170,6 @@ public class PeopleTileViewHelper { static final String BRIEF_PAUSE_ON_TALKBACK = "\n\n"; - // This regex can be used to match Unicode emoji characters and character sequences. It's from - // the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor - // changes to fit our needs. It should be updated once new emoji categories are added. - // - // Emoji categories that can be matched by this regex: - // - Country flags. "\p{RI}\p{RI}" matches country flags since they always consist of 2 Unicode - // scalars. - // - Single-Character Emoji. "\p{Emoji}" matches Single-Character Emojis. - // - Emoji with modifiers. E.g. Emojis with different skin tones. "\p{Emoji}\p{EMod}" matches - // them. - // - Emoji Presentation. Those are characters which can normally be drawn as either text or as - // Emoji. "\p{Emoji}\x{FE0F}" matches them. - // - Emoji Keycap. E.g. Emojis for number 0 to 9. "\p{Emoji}\x{FE0F}\x{20E3}" matches them. - // - Emoji tag sequence. "\p{Emoji}[\x{E0020}-\x{E007E}]+\x{E007F}" matches them. - // - Emoji Zero-Width Joiner (ZWJ) Sequence. A ZWJ emoji is actually multiple emojis joined by - // the jointer "0x200D". - // - // Note: since "\p{Emoji}" also matches some ASCII characters like digits 0-9, we use - // "\p{Emoji}&&\p{So}" to exclude them. This is the change we made from the official emoji - // regex. - private static final String UNICODE_EMOJI_REGEX = - "\\p{RI}\\p{RI}|" - + "(" - + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" - + "|[\\p{Emoji}&&\\p{So}]" - + ")" - + "(" - + "\\x{200D}" - + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" - + "?)*"; - - private static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX); - public static final String EMPTY_STRING = ""; private int mMediumVerticalPadding; @@ -831,7 +834,7 @@ public class PeopleTileViewHelper { if (status.getActivity() == ACTIVITY_BIRTHDAY || status.getActivity() == ACTIVITY_UPCOMING_BIRTHDAY) { - setEmojiBackground(views, EMOJI_CAKE); + setEmojiBackground(views, EmojiHelper.EMOJI_CAKE); } Icon statusIcon = status.getIcon(); @@ -1072,7 +1075,7 @@ public class PeopleTileViewHelper { /** Returns emoji if {@code message} has two of the same emoji in sequence. */ @VisibleForTesting CharSequence getDoubleEmoji(CharSequence message) { - Matcher unicodeEmojiMatcher = EMOJI_PATTERN.matcher(message); + Matcher unicodeEmojiMatcher = EmojiHelper.EMOJI_PATTERN.matcher(message); // Stores the start and end indices of each matched emoji. List<Pair<Integer, Integer>> emojiIndices = new ArrayList<>(); // Stores each emoji text. diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt index c91ed133a11e..8e307408ba86 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt @@ -42,7 +42,7 @@ import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig import com.android.systemui.security.data.repository.SecurityRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.user.data.repository.UserSwitcherRepository -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -102,7 +102,7 @@ constructor( private val deviceProvisionedController: DeviceProvisionedController, private val qsSecurityFooterUtils: QSSecurityFooterUtils, private val fgsManagerController: FgsManagerController, - private val userInteractor: UserInteractor, + private val userSwitcherInteractor: UserSwitcherInteractor, securityRepository: SecurityRepository, foregroundServicesRepository: ForegroundServicesRepository, userSwitcherRepository: UserSwitcherRepository, @@ -178,6 +178,6 @@ constructor( } override fun showUserSwitcher(expandable: Expandable) { - userInteractor.showUserSwitcher(expandable) + userSwitcherInteractor.showUserSwitcher(expandable) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index 7a0c087caacf..f469c6b78e87 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -38,6 +38,8 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; +import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; import com.android.systemui.plugins.ActivityStarter; @@ -45,13 +47,13 @@ import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.CallbackController; +import dagger.Lazy; + import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import javax.inject.Inject; -import dagger.Lazy; - /** * Helper class to initiate a screen recording */ @@ -71,6 +73,7 @@ public class RecordingController private final FeatureFlags mFlags; private final UserContextProvider mUserContextProvider; private final UserTracker mUserTracker; + private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; protected static final String INTENT_UPDATE_STATE = "com.android.systemui.screenrecord.UPDATE_STATE"; @@ -115,7 +118,8 @@ public class RecordingController FeatureFlags flags, UserContextProvider userContextProvider, Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver, - UserTracker userTracker) { + UserTracker userTracker, + MediaProjectionMetricsLogger mediaProjectionMetricsLogger) { mMainExecutor = mainExecutor; mContext = context; mFlags = flags; @@ -123,6 +127,7 @@ public class RecordingController mBroadcastDispatcher = broadcastDispatcher; mUserContextProvider = userContextProvider; mUserTracker = userTracker; + mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); @@ -149,6 +154,9 @@ public class RecordingController return new ScreenCaptureDisabledDialog(mContext); } + mMediaProjectionMetricsLogger.notifyProjectionInitiated( + SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); + return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) ? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this, activityStarter, mUserContextProvider, onStartRecordingClicked) diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 393a698bcdb7..99127ea928bf 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -90,6 +90,7 @@ open class UserTrackerImpl internal constructor( private val isBackgroundUserSwitchEnabled: Boolean get() = featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS) + @Deprecated("Use UserInteractor.getSelectedUserId()") override var userId: Int by SynchronizedDelegate(context.userId) protected set diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 6783afa3eb9d..1ecb127f0c92 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -21,6 +21,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY; import android.app.Activity; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.view.Gravity; @@ -35,8 +36,8 @@ import android.widget.FrameLayout; 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.Main; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -74,21 +75,26 @@ public class BrightnessDialog extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setWindowAttributes(); + setContentView(R.layout.brightness_mirror_container); + setBrightnessDialogViewAttributes(); + } + private void setWindowAttributes() { final Window window = getWindow(); - window.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); + window.setGravity(Gravity.TOP | Gravity.LEFT); window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); window.requestFeature(Window.FEATURE_NO_TITLE); // Calling this creates the decor View, so setLayout takes proper effect // (see Dialog#onWindowAttributesChanged) window.getDecorView(); - window.setLayout( - WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); + window.setLayout(WRAP_CONTENT, WRAP_CONTENT); getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false); + } - setContentView(R.layout.brightness_mirror_container); + void setBrightnessDialogViewAttributes() { FrameLayout frame = findViewById(R.id.brightness_mirror_container); // The brightness mirror container is INVISIBLE by default. frame.setVisibility(View.VISIBLE); @@ -97,6 +103,14 @@ public class BrightnessDialog extends Activity { getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); lp.leftMargin = horizontalMargin; lp.rightMargin = horizontalMargin; + + int verticalMargin = + getResources().getDimensionPixelSize( + R.dimen.notification_guts_option_vertical_padding); + + lp.topMargin = verticalMargin; + lp.bottomMargin = verticalMargin; + frame.setLayoutParams(lp); Rect bounds = new Rect(); frame.addOnLayoutChangeListener( @@ -113,6 +127,20 @@ public class BrightnessDialog extends Activity { frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT); mBrightnessController = mBrightnessControllerFactory.create(controller); + + Configuration configuration = getResources().getConfiguration(); + int orientation = configuration.orientation; + + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2 + - lp.leftMargin * 2; + } else if (orientation == Configuration.ORIENTATION_PORTRAIT) { + lp.width = getWindowManager().getDefaultDisplay().getWidth() + - lp.leftMargin * 2; + } + + frame.setLayoutParams(lp); + } @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 6f5e41f626de..04263882d3a8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -47,7 +47,6 @@ import android.view.WindowManager.LayoutParams; import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; -import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; @@ -72,6 +71,7 @@ import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import dagger.Lazy; @@ -112,6 +112,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final KeyguardBypassController mKeyguardBypassController; private final Executor mBackgroundExecutor; private final AuthController mAuthController; + private final Lazy<SelectedUserInteractor> mUserInteractor; private final Lazy<ShadeInteractor> mShadeInteractorLazy; private ViewGroup mWindowRootView; private LayoutParams mLp; @@ -157,7 +158,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW AuthController authController, ShadeExpansionStateManager shadeExpansionStateManager, Lazy<ShadeInteractor> shadeInteractorLazy, - ShadeWindowLogger logger) { + ShadeWindowLogger logger, + Lazy<SelectedUserInteractor> userInteractor) { mContext = context; mWindowRootViewComponentFactory = windowRootViewComponentFactory; mWindowManager = windowManager; @@ -174,6 +176,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mScreenOffAnimationController = screenOffAnimationController; dumpManager.registerDumpable(this); mAuthController = authController; + mUserInteractor = userInteractor; mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed(); mLockScreenDisplayTimeout = context.getResources() .getInteger(R.integer.config_lockScreenDisplayTimeout); @@ -348,7 +351,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW boolean onKeyguard = state.statusBarState == StatusBarState.KEYGUARD && !state.keyguardFadingAway && !state.keyguardGoingAway; if (onKeyguard - && mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) { + && mAuthController.isUdfpsEnrolled(mUserInteractor.get().getSelectedUserId())) { // both max and min display refresh rate must be set to take effect: mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardPreferredRefreshRate; mLpChanged.preferredMinDisplayRefreshRate = mKeyguardPreferredRefreshRate; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index d05dfe2c11c1..09514404d4db 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -36,7 +36,6 @@ import com.android.keyguard.LockIconViewController; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityLaunchAnimator; -import com.android.systemui.back.domain.interactor.BackActionInteractor; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; @@ -55,7 +54,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.log.BouncerLogger; -import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener; import com.android.systemui.statusbar.DragDownHelper; @@ -75,6 +73,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; @@ -128,8 +127,6 @@ public class NotificationShadeWindowViewController implements Dumpable { private final CentralSurfaces mService; private final DozeServiceHost mDozeServiceHost; private final DozeScrimController mDozeScrimController; - private final BackActionInteractor mBackActionInteractor; - private final PowerInteractor mPowerInteractor; private final NotificationShadeWindowController mNotificationShadeWindowController; private DragDownHelper mDragDownHelper; private boolean mExpandingBelowNotch; @@ -164,8 +161,6 @@ public class NotificationShadeWindowViewController implements Dumpable { CentralSurfaces centralSurfaces, DozeServiceHost dozeServiceHost, DozeScrimController dozeScrimController, - BackActionInteractor backActionInteractor, - PowerInteractor powerInteractor, NotificationShadeWindowController controller, Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider, KeyguardUnlockAnimationController keyguardUnlockAnimationController, @@ -187,7 +182,8 @@ public class NotificationShadeWindowViewController implements Dumpable { BouncerLogger bouncerLogger, SysUIKeyEventHandler sysUIKeyEventHandler, PrimaryBouncerInteractor primaryBouncerInteractor, - AlternateBouncerInteractor alternateBouncerInteractor) { + AlternateBouncerInteractor alternateBouncerInteractor, + SelectedUserInteractor selectedUserInteractor) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; mStatusBarStateController = statusBarStateController; @@ -200,12 +196,10 @@ public class NotificationShadeWindowViewController implements Dumpable { mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mStatusBarWindowStateController = statusBarWindowStateController; mLockIconViewController = lockIconViewController; - mBackActionInteractor = backActionInteractor; mShadeLogger = shadeLogger; mService = centralSurfaces; mDozeServiceHost = dozeServiceHost; mDozeScrimController = dozeScrimController; - mPowerInteractor = powerInteractor; mNotificationShadeWindowController = controller; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; mAmbientState = ambientState; @@ -229,7 +223,8 @@ public class NotificationShadeWindowViewController implements Dumpable { messageAreaControllerFactory, bouncerMessageInteractor, bouncerLogger, - featureFlags); + featureFlags, + selectedUserInteractor); collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), mLockscreenToDreamingTransition); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 3bbb2cf83a83..3c68438ff61b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -736,7 +736,11 @@ public class QuickSettingsController implements Dumpable { /** Returns whether touches from the notification panel should be disallowed */ public boolean disallowTouches() { - return mQs.disallowPanelTouches(); + if (mQs != null) { + return mQs.disallowPanelTouches(); + } else { + return false; + } } void setListening(boolean listening) { 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 e487a6fb9617..a8a20cc8559b 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.domain.interactor.UserSwitcherInteractor 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, + userSwitcherInteractor: UserSwitcherInteractor, sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, repository: ShadeRepository, ) { @@ -227,7 +227,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 || !userSwitcherInteractor.isSimpleUserSwitcher) && isShadeEnabled && disableFlags.isQuickSettingsEnabled() && !isDozing diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt deleted file mode 100644 index 17b4e3baef13..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Point -import android.graphics.Rect -import android.renderscript.Allocation -import android.renderscript.Element -import android.renderscript.RenderScript -import android.renderscript.ScriptIntrinsicBlur -import android.util.Log -import android.util.MathUtils -import com.android.internal.graphics.ColorUtils -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.MediaNotificationProcessor -import javax.inject.Inject - -private const val TAG = "MediaArtworkProcessor" -private const val COLOR_ALPHA = (255 * 0.7f).toInt() -private const val BLUR_RADIUS = 25f -private const val DOWNSAMPLE = 6 - -@SysUISingleton -class MediaArtworkProcessor @Inject constructor() { - - private val mTmpSize = Point() - private var mArtworkCache: Bitmap? = null - - fun processArtwork(context: Context, artwork: Bitmap): Bitmap? { - if (mArtworkCache != null) { - return mArtworkCache - } - val renderScript = RenderScript.create(context) - val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) - var input: Allocation? = null - var output: Allocation? = null - var inBitmap: Bitmap? = null - try { - @Suppress("DEPRECATION") - context.display?.getSize(mTmpSize) - val rect = Rect(0, 0, artwork.width, artwork.height) - MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE)) - inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(), - true /* filter */) - // Render script blurs only support ARGB_8888, we need a conversion if we got a - // different bitmap config. - if (inBitmap.config != Bitmap.Config.ARGB_8888) { - val oldIn = inBitmap - inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */) - oldIn.recycle() - } - val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0, - Bitmap.Config.ARGB_8888) - - input = Allocation.createFromBitmap(renderScript, inBitmap, - Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE) - output = Allocation.createFromBitmap(renderScript, outBitmap) - - blur.setRadius(BLUR_RADIUS) - blur.setInput(input) - blur.forEach(output) - output.copyTo(outBitmap) - - val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork) - - val canvas = Canvas(outBitmap) - canvas.drawColor(ColorUtils.setAlphaComponent(swatch.rgb, COLOR_ALPHA)) - return outBitmap - } catch (ex: IllegalArgumentException) { - Log.e(TAG, "error while processing artwork", ex) - return null - } finally { - input?.destroy() - output?.destroy() - blur.destroy() - inBitmap?.recycle() - } - } - - fun clearCache() { - mArtworkCache?.recycle() - mArtworkCache = null - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 5bd40b8ed714..389486f0ada3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -15,43 +15,29 @@ */ package com.android.systemui.statusbar; -import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; -import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_MEDIA_FAKE_ARTWORK; -import static com.android.systemui.statusbar.phone.CentralSurfaces.ENABLE_LOCKSCREEN_WALLPAPER; -import static com.android.systemui.statusbar.phone.CentralSurfaces.SHOW_LOCKSCREEN_MEDIA_ARTWORK; - -import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.WallpaperManager; import android.content.Context; -import android.graphics.Bitmap; import android.graphics.Point; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.display.DisplayManager; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; -import android.os.AsyncTask; import android.os.Trace; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; -import android.util.ArraySet; import android.util.Log; import android.view.Display; import android.view.View; import android.widget.ImageView; -import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.controls.models.player.MediaData; import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData; @@ -65,18 +51,11 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.phone.BiometricUnlockController; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.phone.ScrimState; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.Utils; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import dagger.Lazy; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -85,7 +64,6 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; /** @@ -99,7 +77,6 @@ public class NotificationMediaManager implements Dumpable { private final StatusBarStateController mStatusBarStateController; private final SysuiColorExtractor mColorExtractor; private final KeyguardStateController mKeyguardStateController; - private final KeyguardBypassController mKeyguardBypassController; private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>(); private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>(); static { @@ -117,9 +94,6 @@ public class NotificationMediaManager implements Dumpable { private final NotifCollection mNotifCollection; @Nullable - private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController; - - @Nullable private BiometricUnlockController mBiometricUnlockController; @Nullable private ScrimController mScrimController; @@ -128,12 +102,8 @@ public class NotificationMediaManager implements Dumpable { @VisibleForTesting boolean mIsLockscreenLiveWallpaperEnabled; - private final DelayableExecutor mMainExecutor; - private final Context mContext; private final ArrayList<MediaListener> mMediaListeners; - private final MediaArtworkProcessor mMediaArtworkProcessor; - private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>(); protected NotificationPresenter mPresenter; private MediaController mMediaController; @@ -150,8 +120,6 @@ public class NotificationMediaManager implements Dumpable { private List<String> mSmallerInternalDisplayUids; private Display mCurrentDisplay; - private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable; - private final MediaController.Callback mMediaListener = new MediaController.Callback() { @Override public void onPlaybackStateChanged(PlaybackState state) { @@ -173,7 +141,6 @@ public class NotificationMediaManager implements Dumpable { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); } - mMediaArtworkProcessor.clearCache(); mMediaMetadata = metadata; dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); } @@ -184,13 +151,9 @@ public class NotificationMediaManager implements Dumpable { */ public NotificationMediaManager( Context context, - Lazy<NotificationShadeWindowController> notificationShadeWindowController, NotificationVisibilityProvider visibilityProvider, - MediaArtworkProcessor mediaArtworkProcessor, - KeyguardBypassController keyguardBypassController, NotifPipeline notifPipeline, NotifCollection notifCollection, - @Main DelayableExecutor mainExecutor, MediaDataManager mediaDataManager, StatusBarStateController statusBarStateController, SysuiColorExtractor colorExtractor, @@ -199,12 +162,8 @@ public class NotificationMediaManager implements Dumpable { WallpaperManager wallpaperManager, DisplayManager displayManager) { mContext = context; - mMediaArtworkProcessor = mediaArtworkProcessor; - mKeyguardBypassController = keyguardBypassController; mMediaListeners = new ArrayList<>(); - mNotificationShadeWindowController = notificationShadeWindowController; mVisibilityProvider = visibilityProvider; - mMainExecutor = mainExecutor; mMediaDataManager = mediaDataManager; mNotifPipeline = notifPipeline; mNotifCollection = notifCollection; @@ -476,7 +435,6 @@ public class NotificationMediaManager implements Dumpable { } private void clearCurrentMediaNotificationSession() { - mMediaArtworkProcessor.clearCache(); mMediaMetadata = null; if (mMediaController != null) { if (DEBUG_MEDIA) { @@ -494,9 +452,6 @@ public class NotificationMediaManager implements Dumpable { public void onDisplayUpdated(Display display) { Trace.beginSection("NotificationMediaManager#onDisplayUpdated"); mCurrentDisplay = display; - if (mWallapperDrawable != null) { - mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays()); - } Trace.endSection(); } @@ -531,18 +486,13 @@ public class NotificationMediaManager implements Dumpable { } /** - * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. + * Update media state of lockscreen media views and controllers . */ - public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { + public void updateMediaMetaData(boolean metaDataChanged) { if (mIsLockscreenLiveWallpaperEnabled) return; Trace.beginSection("CentralSurfaces#updateMediaMetaData"); - if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) { - Trace.endSection(); - return; - } - if (getBackDropView() == null) { Trace.endSection(); return; // called too early @@ -566,168 +516,12 @@ public class NotificationMediaManager implements Dumpable { + " state=" + mStatusBarStateController.getState()); } - Bitmap artworkBitmap = null; - if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) { - artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART); - if (artworkBitmap == null) { - artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); - } - } - - // Process artwork on a background thread and send the resulting bitmap to - // finishUpdateMediaMetaData. - if (metaDataChanged) { - for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) { - task.cancel(true); - } - mProcessArtworkTasks.clear(); - } - if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) { - mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged, - allowEnterAnimation).execute(artworkBitmap)); - } else { - finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null); - } - - Trace.endSection(); - } - - private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation, - @Nullable Bitmap bmp) { - Drawable artworkDrawable = null; - if (bmp != null) { - artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp); - } - boolean hasMediaArtwork = artworkDrawable != null; - boolean allowWhenShade = false; - if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) { - Bitmap lockWallpaper = - mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null; - if (lockWallpaper != null) { - artworkDrawable = new LockscreenWallpaper.WallpaperDrawable( - mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays()); - // We're in the SHADE mode on the SIM screen - yet we still need to show - // the lockscreen wallpaper in that mode. - allowWhenShade = mStatusBarStateController.getState() == KEYGUARD; - } - } - - NotificationShadeWindowController windowController = - mNotificationShadeWindowController.get(); - boolean hideBecauseOccluded = mKeyguardStateController.isOccluded(); - - final boolean hasArtwork = artworkDrawable != null; - mColorExtractor.setHasMediaArtwork(hasMediaArtwork); + mColorExtractor.setHasMediaArtwork(false); if (mScrimController != null) { - mScrimController.setHasBackdrop(hasArtwork); + mScrimController.setHasBackdrop(false); } - if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) - && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade) - && mBiometricUnlockController != null && mBiometricUnlockController.getMode() - != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING - && !hideBecauseOccluded) { - // time to show some art! - if (mBackdrop.getVisibility() != View.VISIBLE) { - mBackdrop.setVisibility(View.VISIBLE); - if (allowEnterAnimation) { - mBackdrop.setAlpha(0); - mBackdrop.animate().alpha(1f); - } else { - mBackdrop.animate().cancel(); - mBackdrop.setAlpha(1f); - } - if (windowController != null) { - windowController.setBackdropShowing(true); - } - metaDataChanged = true; - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork"); - } - } - if (metaDataChanged) { - if (mBackdropBack.getDrawable() != null) { - Drawable drawable = - mBackdropBack.getDrawable().getConstantState() - .newDrawable(mBackdropFront.getResources()).mutate(); - mBackdropFront.setImageDrawable(drawable); - mBackdropFront.setAlpha(1f); - mBackdropFront.setVisibility(View.VISIBLE); - } else { - mBackdropFront.setVisibility(View.INVISIBLE); - } - - if (DEBUG_MEDIA_FAKE_ARTWORK) { - final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF); - Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c)); - mBackdropBack.setBackgroundColor(0xFFFFFFFF); - mBackdropBack.setImageDrawable(new ColorDrawable(c)); - } else { - if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) { - mWallapperDrawable = - (LockscreenWallpaper.WallpaperDrawable) artworkDrawable; - } - mBackdropBack.setImageDrawable(artworkDrawable); - } - - if (mBackdropFront.getVisibility() == View.VISIBLE) { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from " - + mBackdropFront.getDrawable() - + " to " - + mBackdropBack.getDrawable()); - } - mBackdropFront.animate() - .setDuration(250) - .alpha(0f).withEndAction(mHideBackdropFront); - } - } - } else { - // need to hide the album art, either because we are unlocked, on AOD - // or because the metadata isn't there to support it - if (mBackdrop.getVisibility() != View.GONE) { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork"); - } - boolean cannotAnimateDoze = mStatusBarStateController.isDozing() - && !ScrimState.AOD.getAnimateChange(); - if (((mBiometricUnlockController != null && mBiometricUnlockController.getMode() - == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING - || cannotAnimateDoze)) - || hideBecauseOccluded) { - // We are unlocking directly - no animation! - mBackdrop.setVisibility(View.GONE); - mBackdropBack.setImageDrawable(null); - if (windowController != null) { - windowController.setBackdropShowing(false); - } - } else { - if (windowController != null) { - windowController.setBackdropShowing(false); - } - mBackdrop.animate() - .alpha(0) - .setInterpolator(Interpolators.ACCELERATE_DECELERATE) - .setDuration(300) - .setStartDelay(0) - .withEndAction(() -> { - mBackdrop.setVisibility(View.GONE); - mBackdropFront.animate().cancel(); - mBackdropBack.setImageDrawable(null); - mMainExecutor.execute(mHideBackdropFront); - }); - if (mKeyguardStateController.isKeyguardFadingAway()) { - mBackdrop.animate() - .setDuration( - mKeyguardStateController.getShortenedFadingAwayDuration()) - .setStartDelay( - mKeyguardStateController.getKeyguardFadingAwayDelay()) - .setInterpolator(Interpolators.LINEAR) - .start(); - } - } - } - } + Trace.endSection(); } public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack, @@ -758,15 +552,6 @@ public class NotificationMediaManager implements Dumpable { } }; - private Bitmap processArtwork(Bitmap artwork) { - return mMediaArtworkProcessor.processArtwork(mContext, artwork); - } - - @MainThread - private void removeTask(AsyncTask<?, ?, ?> task) { - mProcessArtworkTasks.remove(task); - } - // TODO(b/273443374): remove public boolean isLockscreenWallpaperOnNotificationShade() { return mBackdrop != null && mLockscreenWallpaper != null @@ -780,52 +565,6 @@ public class NotificationMediaManager implements Dumpable { return mBackdrop; } - /** - * {@link AsyncTask} to prepare album art for use as backdrop on lock screen. - */ - private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> { - - private final WeakReference<NotificationMediaManager> mManagerRef; - private final boolean mMetaDataChanged; - private final boolean mAllowEnterAnimation; - - ProcessArtworkTask(NotificationMediaManager manager, boolean changed, - boolean allowAnimation) { - mManagerRef = new WeakReference<>(manager); - mMetaDataChanged = changed; - mAllowEnterAnimation = allowAnimation; - } - - @Override - protected Bitmap doInBackground(Bitmap... bitmaps) { - NotificationMediaManager manager = mManagerRef.get(); - if (manager == null || bitmaps.length == 0 || isCancelled()) { - return null; - } - return manager.processArtwork(bitmaps[0]); - } - - @Override - protected void onPostExecute(@Nullable Bitmap result) { - NotificationMediaManager manager = mManagerRef.get(); - if (manager != null && !isCancelled()) { - manager.removeTask(this); - manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result); - } - } - - @Override - protected void onCancelled(Bitmap result) { - if (result != null) { - result.recycle(); - } - NotificationMediaManager manager = mManagerRef.get(); - if (manager != null) { - manager.removeTask(this); - } - } - } - public interface MediaListener { /** * Called whenever there's new metadata or playback state. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 7f5829d81c6f..125c8efe1884 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -31,7 +31,6 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpHandler; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; @@ -45,12 +44,10 @@ import com.android.systemui.shade.ShadeSurface; import com.android.systemui.shade.carrier.ShadeCarrierGroupController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.MediaArtworkProcessor; import com.android.systemui.statusbar.NotificationClickNotifier; 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.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -61,7 +58,6 @@ import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.phone.CentralSurfacesImpl; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -71,7 +67,6 @@ import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.RemoteInputUriController; -import com.android.systemui.util.concurrency.DelayableExecutor; import dagger.Binds; import dagger.Lazy; @@ -122,13 +117,9 @@ public interface CentralSurfacesDependenciesModule { @Provides static NotificationMediaManager provideNotificationMediaManager( Context context, - Lazy<NotificationShadeWindowController> notificationShadeWindowController, NotificationVisibilityProvider visibilityProvider, - MediaArtworkProcessor mediaArtworkProcessor, - KeyguardBypassController keyguardBypassController, NotifPipeline notifPipeline, NotifCollection notifCollection, - @Main DelayableExecutor mainExecutor, MediaDataManager mediaDataManager, StatusBarStateController statusBarStateController, SysuiColorExtractor colorExtractor, @@ -138,13 +129,9 @@ public interface CentralSurfacesDependenciesModule { DisplayManager displayManager) { return new NotificationMediaManager( context, - notificationShadeWindowController, visibilityProvider, - mediaArtworkProcessor, - keyguardBypassController, notifPipeline, notifCollection, - mainExecutor, mediaDataManager, statusBarStateController, colorExtractor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java deleted file mode 100644 index 732c115571f8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java +++ /dev/null @@ -1,131 +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.systemui.statusbar.notification; - -import android.graphics.Bitmap; -import android.graphics.Color; - -import androidx.palette.graphics.Palette; - -import java.util.List; - -/** - * A gutted class that now contains only a color extraction utility used by the - * MediaArtworkProcessor, which has otherwise supplanted this. - * - * TODO(b/182926117): move this into MediaArtworkProcessor.kt - */ -public class MediaNotificationProcessor { - - /** - * The population fraction to select a white or black color as the background over a color. - */ - private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f; - private static final float BLACK_MAX_LIGHTNESS = 0.08f; - private static final float WHITE_MIN_LIGHTNESS = 0.90f; - private static final int RESIZE_BITMAP_AREA = 150 * 150; - - private MediaNotificationProcessor() { - } - - /** - * Finds an appropriate background swatch from media artwork. - * - * @param artwork Media artwork - * @return Swatch that should be used as the background of the media notification. - */ - public static Palette.Swatch findBackgroundSwatch(Bitmap artwork) { - return findBackgroundSwatch(generateArtworkPaletteBuilder(artwork).generate()); - } - - /** - * Finds an appropriate background swatch from the palette of media artwork. - * - * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder} - * @return Swatch that should be used as the background of the media notification. - */ - public static Palette.Swatch findBackgroundSwatch(Palette palette) { - // by default we use the dominant palette - Palette.Swatch dominantSwatch = palette.getDominantSwatch(); - if (dominantSwatch == null) { - return new Palette.Swatch(Color.WHITE, 100); - } - - if (!isWhiteOrBlack(dominantSwatch.getHsl())) { - return dominantSwatch; - } - // Oh well, we selected black or white. Lets look at the second color! - List<Palette.Swatch> swatches = palette.getSwatches(); - float highestNonWhitePopulation = -1; - Palette.Swatch second = null; - for (Palette.Swatch swatch : swatches) { - if (swatch != dominantSwatch - && swatch.getPopulation() > highestNonWhitePopulation - && !isWhiteOrBlack(swatch.getHsl())) { - second = swatch; - highestNonWhitePopulation = swatch.getPopulation(); - } - } - if (second == null) { - return dominantSwatch; - } - if (dominantSwatch.getPopulation() / highestNonWhitePopulation - > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) { - // The dominant swatch is very dominant, lets take it! - // We're not filtering on white or black - return dominantSwatch; - } else { - return second; - } - } - - /** - * Generate a palette builder for media artwork. - * - * For producing a smooth background transition, the palette is extracted from only the left - * side of the artwork. - * - * @param artwork Media artwork - * @return Builder that generates the {@link Palette} for the media artwork. - */ - public static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) { - // for the background we only take the left side of the image to ensure - // a smooth transition - return Palette.from(artwork) - .setRegion(0, 0, artwork.getWidth() / 2, artwork.getHeight()) - .clearFilters() // we want all colors, red / white / black ones too! - .resizeBitmapArea(RESIZE_BITMAP_AREA); - } - - private static boolean isWhiteOrBlack(float[] hsl) { - return isBlack(hsl) || isWhite(hsl); - } - - /** - * @return true if the color represents a color which is close to black. - */ - private static boolean isBlack(float[] hslColor) { - return hslColor[2] <= BLACK_MAX_LIGHTNESS; - } - - /** - * @return true if the color represents a color which is close to white. - */ - private static boolean isWhite(float[] hslColor) { - return hslColor[2] >= WHITE_MIN_LIGHTNESS; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt index 9ba1f7aa2f66..380cdadd1361 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import dagger.Binds import dagger.Module import javax.inject.Inject @@ -52,7 +53,8 @@ class SensitiveContentCoordinatorImpl @Inject constructor( private val lockscreenUserManager: NotificationLockscreenUserManager, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val statusBarStateController: StatusBarStateController, - private val keyguardStateController: KeyguardStateController + private val keyguardStateController: KeyguardStateController, + private val selectedUserInteractor: SelectedUserInteractor, ) : Invalidator("SensitiveContentInvalidator"), SensitiveContentCoordinator, DynamicPrivacyController.Listener, @@ -67,10 +69,10 @@ class SensitiveContentCoordinatorImpl @Inject constructor( override fun onDynamicPrivacyChanged(): Unit = invalidateList("onDynamicPrivacyChanged") override fun onBeforeRenderList(entries: List<ListEntry>) { - if (keyguardStateController.isKeyguardGoingAway() || - statusBarStateController.getState() == StatusBarState.KEYGUARD && + if (keyguardStateController.isKeyguardGoingAway || + statusBarStateController.state == StatusBarState.KEYGUARD && keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing( - KeyguardUpdateMonitor.getCurrentUser())) { + selectedUserInteractor.getSelectedUserId())) { // don't update yet if: // - the keyguard is currently going away // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index f750feda9091..1229cb9b49ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart /** View-model for the shared notification container, used by both the shade and keyguard spaces */ class SharedNotificationContainerViewModel @@ -45,8 +46,8 @@ constructor( ) { private val statesForConstrainedNotifications = setOf( - KeyguardState.LOCKSCREEN, KeyguardState.AOD, + KeyguardState.LOCKSCREEN, KeyguardState.DOZING, KeyguardState.ALTERNATE_BOUNCER, KeyguardState.PRIMARY_BOUNCER @@ -68,8 +69,17 @@ constructor( /** If the user is visually on one of the unoccluded lockscreen states. */ val isOnLockscreen: Flow<Boolean> = - keyguardTransitionInteractor.finishedKeyguardState - .map { statesForConstrainedNotifications.contains(it) } + combine( + keyguardTransitionInteractor.finishedKeyguardState.map { + statesForConstrainedNotifications.contains(it) + }, + keyguardTransitionInteractor + .transitionValue(KeyguardState.LOCKSCREEN) + .onStart { emit(0f) } + .map { it > 0 } + ) { constrainedNotificationState, transitioningToOrFromLockscreen -> + constrainedNotificationState || transitioningToOrFromLockscreen + } .distinctUntilChanged() /** Are we purely on the keyguard without the shade/qs? */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 2809cad34143..59f10aed4145 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -17,8 +17,6 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.SESSION_KEYGUARD; -import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; -import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME; import android.annotation.IntDef; import android.content.res.Resources; @@ -30,7 +28,6 @@ import android.metrics.LogMaker; import android.os.Handler; import android.os.PowerManager; import android.os.Trace; -import android.view.HapticFeedbackConstants; import androidx.annotation.Nullable; @@ -47,22 +44,27 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.logging.BiometricUnlockLogger; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.biometrics.AuthController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.time.SystemClock; +import dagger.Lazy; + import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -73,12 +75,14 @@ import java.util.Set; import javax.inject.Inject; +import kotlinx.coroutines.ExperimentalCoroutinesApi; + /** * Controller which coordinates all the biometric unlocking actions with the UI. */ +@ExperimentalCoroutinesApi @SysUISingleton public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable { - private static final long RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS = 400L; private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000; private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock"; private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); @@ -172,9 +176,12 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final WakefulnessLifecycle mWakefulnessLifecycle; private final LatencyTracker mLatencyTracker; private final VibratorHelper mVibratorHelper; + private final BiometricUnlockInteractor mBiometricUnlockInteractor; private final BiometricUnlockLogger mLogger; private final SystemClock mSystemClock; private final boolean mOrderUnlockAndWake; + private final Lazy<SelectedUserInteractor> mSelectedUserInteractor; + private final DeviceEntryHapticsInteractor mHapticsInteractor; private long mLastFpFailureUptimeMillis; private int mNumConsecutiveFpFailures; @@ -284,7 +291,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp ScreenOffAnimationController screenOffAnimationController, VibratorHelper vibrator, SystemClock systemClock, - FeatureFlags featureFlags + FeatureFlags featureFlags, + DeviceEntryHapticsInteractor hapticsInteractor, + Lazy<SelectedUserInteractor> selectedUserInteractor, + BiometricUnlockInteractor biometricUnlockInteractor ) { mPowerManager = powerManager; mUpdateMonitor = keyguardUpdateMonitor; @@ -293,6 +303,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mLatencyTracker = latencyTracker; mWakefulnessLifecycle = wakefulnessLifecycle; mWakefulnessLifecycle.addObserver(mWakefulnessObserver); + mBiometricUnlockInteractor = biometricUnlockInteractor; mNotificationShadeWindowController = notificationShadeWindowController; mDozeScrimController = dozeScrimController; @@ -314,6 +325,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mFeatureFlags = featureFlags; mOrderUnlockAndWake = resources.getBoolean( com.android.internal.R.bool.config_orderUnlockAndWake); + mHapticsInteractor = hapticsInteractor; + mSelectedUserInteractor = selectedUserInteractor; dumpManager.registerDumpable(this); } @@ -434,7 +447,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (mode == MODE_WAKE_AND_UNLOCK || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) { - vibrateSuccess(biometricSourceType); + mHapticsInteractor.vibrateSuccess(); onBiometricUnlockedWithKeyguardDismissal(biometricSourceType); } startWakeAndUnlock(mode); @@ -498,8 +511,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp case MODE_WAKE_AND_UNLOCK: if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) { Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING"); - mMediaManager.updateMediaMetaData(false /* metaDataChanged */, - true /* allowEnterAnimation */); + mMediaManager.updateMediaMetaData(false /* metaDataChanged */); } else if (mMode == MODE_WAKE_AND_UNLOCK){ Trace.beginSection("MODE_WAKE_AND_UNLOCK"); } else { @@ -523,6 +535,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) { listener.onModeChanged(mode); } + mBiometricUnlockInteractor.setBiometricUnlockState(mode); } private void onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType biometricSourceType) { @@ -535,7 +548,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp return mPendingAuthenticated != null && mUpdateMonitor .isUnlockingWithBiometricAllowed(mPendingAuthenticated.isStrongBiometric) - && mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser(); + && mPendingAuthenticated.userId + == mSelectedUserInteractor.get().getSelectedUserId(); } public @WakeAndUnlockMode int getMode() { @@ -599,11 +613,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp // if unlocking isn't allowed, log more information about why unlocking may not // have been allowed final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser( - KeyguardUpdateMonitor.getCurrentUser()); + mSelectedUserInteractor.get().getSelectedUserId()); final boolean nonStrongBiometricAllowed = mUpdateMonitor.getStrongAuthTracker() .isNonStrongBiometricAllowedAfterIdleTimeout( - KeyguardUpdateMonitor.getCurrentUser()); + mSelectedUserInteractor.get().getSelectedUserId()); mLogger.logCalculateModeForFingerprintUnlockingNotAllowed(strongBiometric, strongAuthFlags, nonStrongBiometricAllowed, deviceInteractive, keyguardShowing); @@ -669,11 +683,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp // if unlocking isn't allowed, log more information about why unlocking may not // have been allowed final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser( - KeyguardUpdateMonitor.getCurrentUser()); + mSelectedUserInteractor.get().getSelectedUserId()); final boolean nonStrongBiometricAllowed = mUpdateMonitor.getStrongAuthTracker() .isNonStrongBiometricAllowedAfterIdleTimeout( - KeyguardUpdateMonitor.getCurrentUser()); + mSelectedUserInteractor.get().getSelectedUserId()); mLogger.logCalculateModeForPassiveAuthUnlockingNotAllowed( strongBiometric, strongAuthFlags, nonStrongBiometricAllowed, @@ -721,9 +735,9 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp // Suppress all face auth errors if fingerprint can be used to authenticate if ((biometricSourceType == BiometricSourceType.FACE && !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - KeyguardUpdateMonitor.getCurrentUser())) + mSelectedUserInteractor.get().getSelectedUserId())) || (biometricSourceType == BiometricSourceType.FINGERPRINT)) { - vibrateError(biometricSourceType); + mHapticsInteractor.vibrateError(); } cleanup(); @@ -750,45 +764,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp cleanup(); } - // these haptics are for device-entry only - private void vibrateSuccess(BiometricSourceType type) { - if (mAuthController.isSfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()) - && lastWakeupFromPowerButtonWithinHapticThreshold()) { - mLogger.d("Skip auth success haptic. Power button was recently pressed."); - return; - } - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - mVibratorHelper.performHapticFeedback( - mKeyguardViewController.getViewRootImpl().getView(), - HapticFeedbackConstants.CONFIRM - ); - } else { - mVibratorHelper.vibrateAuthSuccess( - getClass().getSimpleName() + ", type =" + type + "device-entry::success"); - } - } - - private boolean lastWakeupFromPowerButtonWithinHapticThreshold() { - final boolean lastWakeupFromPowerButton = mWakefulnessLifecycle.getLastWakeReason() - == PowerManager.WAKE_REASON_POWER_BUTTON; - return lastWakeupFromPowerButton - && mWakefulnessLifecycle.getLastWakeTime() != UNKNOWN_LAST_WAKE_TIME - && mSystemClock.uptimeMillis() - mWakefulnessLifecycle.getLastWakeTime() - < RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS; - } - - private void vibrateError(BiometricSourceType type) { - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - mVibratorHelper.performHapticFeedback( - mKeyguardViewController.getViewRootImpl().getView(), - HapticFeedbackConstants.REJECT - ); - } else { - mVibratorHelper.vibrateAuthError( - getClass().getSimpleName() + ", type =" + type + "device-entry::error"); - } - } - private void cleanup() { releaseBiometricWakeLock(); } @@ -818,6 +793,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) { listener.onResetMode(); } + mBiometricUnlockInteractor.setBiometricUnlockState(MODE_NONE); mNumConsecutiveFpFailures = 0; mLastFpFailureUptimeMillis = 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 7730f7d99f51..a11cbc3bf231 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -37,14 +37,15 @@ import androidx.annotation.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.doze.DozeScreenState; import com.android.systemui.dump.DumpManager; +import com.android.systemui.keyguard.domain.interactor.DozeInteractor; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -55,9 +56,7 @@ import com.android.systemui.unfold.FoldAodAnimationController; import com.android.systemui.unfold.SysUIUnfoldComponent; import java.io.PrintWriter; -import java.util.HashSet; import java.util.Optional; -import java.util.Set; import javax.inject.Inject; @@ -83,12 +82,11 @@ public class DozeParameters implements private final Resources mResources; private final BatteryController mBatteryController; private final ScreenOffAnimationController mScreenOffAnimationController; + private final DozeInteractor mDozeInteractor; private final FoldAodAnimationController mFoldAodAnimationController; private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private final UserTracker mUserTracker; - private final Set<Callback> mCallbacks = new HashSet<>(); - private boolean mDozeAlwaysOn; private boolean mControlScreenOffAnimation; private boolean mIsQuickPickupEnabled; @@ -131,7 +129,8 @@ public class DozeParameters implements KeyguardUpdateMonitor keyguardUpdateMonitor, ConfigurationController configurationController, StatusBarStateController statusBarStateController, - UserTracker userTracker) { + UserTracker userTracker, + DozeInteractor dozeInteractor) { mResources = resources; mAmbientDisplayConfiguration = ambientDisplayConfiguration; mAlwaysOnPolicy = alwaysOnDisplayPolicy; @@ -144,6 +143,7 @@ public class DozeParameters implements mScreenOffAnimationController = screenOffAnimationController; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mUserTracker = userTracker; + mDozeInteractor = dozeInteractor; keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback); tunerService.addTunable( @@ -406,20 +406,6 @@ public class DozeParameters implements return mResources.getStringArray(R.array.doze_brightness_sensor_name_posture_mapping); } - /** - * Callback to listen for DozeParameter changes. - */ - public void addCallback(Callback callback) { - mCallbacks.add(callback); - } - - /** - * Remove callback that listens for DozeParameter changes. - */ - public void removeCallback(Callback callback) { - mCallbacks.remove(callback); - } - @Override public void onTuningChanged(String key, String newValue) { mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(mUserTracker.getUserId()); @@ -465,10 +451,9 @@ public class DozeParameters implements } private void dispatchAlwaysOnEvent() { - for (Callback callback : mCallbacks) { - callback.onAlwaysOnChange(); - } mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn()); + mDozeInteractor.setAodAvailable(getAlwaysOn()); + } private boolean getPostureSpecificBool( @@ -485,14 +470,6 @@ public class DozeParameters implements return bool; } - /** Callbacks for doze parameter related information */ - public interface Callback { - /** - * Invoked when the value of getAlwaysOn may have changed. - */ - void onAlwaysOnChange(); - } - private final class SettingsObserver extends ContentObserver { private final Uri mQuickPickupGesture = Settings.Secure.getUriFor(Settings.Secure.DOZE_QUICK_PICKUP_GESTURE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt index bde5c32c9f41..109e77e92b7b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt @@ -31,6 +31,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.Assert import com.android.systemui.util.sensors.AsyncSensorManager import java.io.PrintWriter @@ -48,7 +49,8 @@ class KeyguardLiftController @Inject constructor( private val asyncSensorManager: AsyncSensorManager, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor, - private val dumpManager: DumpManager + private val dumpManager: DumpManager, + private val selectedUserInteractor: SelectedUserInteractor, ) : Dumpable, CoreStartable { private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE) @@ -115,7 +117,7 @@ class KeyguardLiftController @Inject constructor( val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible && !statusBarStateController.isDozing - val userId = KeyguardUpdateMonitor.getCurrentUser() + val userId = selectedUserInteractor.getSelectedUserId() val isFaceEnabled = keyguardUpdateMonitor.isFaceAuthEnabledForUser(userId) val shouldListen = (onKeyguard || bouncerVisible) && isFaceEnabled if (shouldListen != isListening) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index 92c786fb569f..00fd9fbfffe3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -263,8 +263,7 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen if (result.success) { mCached = true; mCache = result.bitmap; - mMediaManager.updateMediaMetaData( - true /* metaDataChanged */, true /* allowEnterAnimation */); + mMediaManager.updateMediaMetaData(true /* metaDataChanged */); } mLoader = null; } 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 3adf3385e3cc..90fddd9ae22c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -92,6 +92,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.unfold.FoldAodAnimationController; import com.android.systemui.unfold.SysUIUnfoldComponent; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import dagger.Lazy; @@ -313,6 +314,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final KeyguardUpdateMonitor mKeyguardUpdateManager; private final LatencyTracker mLatencyTracker; private final KeyguardSecurityModel mKeyguardSecurityModel; + private final SelectedUserInteractor mSelectedUserInteractor; @Nullable private KeyguardBypassController mBypassController; @Nullable private OccludingAppBiometricUI mOccludingAppBiometricUI; @@ -370,7 +372,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb KeyguardTransitionInteractor keyguardTransitionInteractor, @Main CoroutineDispatcher mainDispatcher, Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor, - Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy + Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy, + SelectedUserInteractor selectedUserInteractor ) { mContext = context; mViewMediatorCallback = callback; @@ -403,6 +406,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mMainDispatcher = mainDispatcher; mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor; mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy; + mSelectedUserInteractor = selectedUserInteractor; } KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -961,7 +965,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); } if (isShowing) { - mMediaManager.updateMediaMetaData(false, animate && !isOccluded); + mMediaManager.updateMediaMetaData(false); } mNotificationShadeWindowController.setKeyguardOccluded(isOccluded); @@ -1142,7 +1146,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb */ public boolean isSecure() { return mKeyguardSecurityModel.getSecurityMode( - KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None; + mSelectedUserInteractor.getSelectedUserId()) + != KeyguardSecurityModel.SecurityMode.None; } /** @@ -1690,7 +1695,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb */ public boolean needsFullscreenBouncer() { KeyguardSecurityModel.SecurityMode mode = mKeyguardSecurityModel.getSecurityMode( - KeyguardUpdateMonitor.getCurrentUser()); + mSelectedUserInteractor.getSelectedUserId()); return mode == KeyguardSecurityModel.SecurityMode.SimPin || mode == KeyguardSecurityModel.SecurityMode.SimPuk; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 2d14f6b3c508..57a8e6fe0d91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -221,7 +221,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu @Override public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { - mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation); + mMediaManager.updateMediaMetaData(metaDataChanged); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index e9e52a2397e1..1670dd39ba24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -104,7 +104,7 @@ constructor( val callback = object : WifiPickerTracker.WifiPickerTrackerCallback { override fun onWifiEntriesChanged() { - val connectedEntry = wifiPickerTracker?.connectedWifiEntry + val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection logOnWifiEntriesChanged(connectedEntry) val secondaryNetworks = @@ -217,6 +217,21 @@ constructor( .stateIn(scope, SharingStarted.Eagerly, emptyList()) /** + * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the + * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry + * if it exists, falling back on the connected entry if null + */ + private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry? + get() { + val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry + return if (mergedEntry != null && mergedEntry.isDefaultNetwork) { + mergedEntry + } else { + this?.connectedWifiEntry + } + } + + /** * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the * primary network. Returns an inactive network if it's not primary. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 1c88289706f3..c62451813699 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -37,11 +37,12 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.logging.KeyguardUpdateMonitorLogger; 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.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.res.R; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import dagger.Lazy; @@ -64,6 +65,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum private final Context mContext; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final LockPatternUtils mLockPatternUtils; + private final SelectedUserInteractor mUserInteractor; private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = new UpdateMonitorCallback(); private final Lazy<KeyguardUnlockAnimationController> mUnlockAnimationControllerLazy; @@ -120,11 +122,13 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController, KeyguardUpdateMonitorLogger logger, DumpManager dumpManager, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + SelectedUserInteractor userInteractor) { mContext = context; mLogger = logger; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; + mUserInteractor = userInteractor; mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); mUnlockAnimationControllerLazy = keyguardUnlockAnimationController; mFeatureFlags = featureFlags; @@ -250,7 +254,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum @VisibleForTesting void update(boolean updateAlways) { Trace.beginSection("KeyguardStateController#update"); - int user = KeyguardUpdateMonitor.getCurrentUser(); + int user = mUserInteractor.getSelectedUserId(); boolean secure = mLockPatternUtils.isSecure(user); boolean canDismissLockScreen = !secure || mKeyguardUpdateMonitor.getUserCanSkipBouncer(user) || (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB && mDebugUnlocked); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt index f88339a4d077..7829d6e7760a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt @@ -27,7 +27,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower import com.android.systemui.user.data.source.UserRecord import com.android.systemui.user.domain.interactor.GuestUserInteractor -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper import dagger.Lazy import java.io.PrintWriter @@ -41,7 +41,7 @@ class UserSwitcherController @Inject constructor( @Application private val applicationContext: Context, - private val userInteractorLazy: Lazy<UserInteractor>, + private val userSwitcherInteractorLazy: Lazy<UserSwitcherInteractor>, private val guestUserInteractorLazy: Lazy<GuestUserInteractor>, private val keyguardInteractorLazy: Lazy<KeyguardInteractor>, private val activityStarter: ActivityStarter, @@ -53,26 +53,29 @@ constructor( fun onUserSwitched() } - private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() } + private val mUserSwitcherInteractor: UserSwitcherInteractor by lazy { + userSwitcherInteractorLazy.get() + } private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() } private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() } - private val callbackCompatMap = mutableMapOf<UserSwitchCallback, UserInteractor.UserCallback>() + private val callbackCompatMap = + mutableMapOf<UserSwitchCallback, UserSwitcherInteractor.UserCallback>() /** The current list of [UserRecord]. */ val users: ArrayList<UserRecord> - get() = userInteractor.userRecords.value + get() = mUserSwitcherInteractor.userRecords.value /** Whether the user switcher experience should use the simple experience. */ val isSimpleUserSwitcher: Boolean - get() = userInteractor.isSimpleUserSwitcher + get() = mUserSwitcherInteractor.isSimpleUserSwitcher val isUserSwitcherEnabled: Boolean - get() = userInteractor.isUserSwitcherEnabled + get() = mUserSwitcherInteractor.isUserSwitcherEnabled /** The [UserRecord] of the current user or `null` when none. */ val currentUserRecord: UserRecord? - get() = userInteractor.selectedUserRecord.value + get() = mUserSwitcherInteractor.selectedUserRecord.value /** The name of the current user of the device or `null`, when none is selected. */ val currentUserName: String? @@ -81,8 +84,8 @@ constructor( LegacyUserUiHelper.getUserRecordName( context = applicationContext, record = it, - isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated, - isGuestUserResetting = userInteractor.isGuestUserResetting, + isGuestUserAutoCreated = mUserSwitcherInteractor.isGuestUserAutoCreated, + isGuestUserResetting = mUserSwitcherInteractor.isGuestUserResetting, ) } @@ -98,21 +101,21 @@ constructor( * @param dialogShower An optional [DialogShower] in case we need to show dialogs. */ fun onUserSelected(userId: Int, dialogShower: DialogShower?) { - userInteractor.selectUser(userId, dialogShower) + mUserSwitcherInteractor.selectUser(userId, dialogShower) } /** Whether the guest user is configured to always be present on the device. */ val isGuestUserAutoCreated: Boolean - get() = userInteractor.isGuestUserAutoCreated + get() = mUserSwitcherInteractor.isGuestUserAutoCreated /** Whether the guest user is currently being reset. */ val isGuestUserResetting: Boolean - get() = userInteractor.isGuestUserResetting + get() = mUserSwitcherInteractor.isGuestUserResetting /** Registers an adapter to notify when the users change. */ fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) { - userInteractor.addCallback( - object : UserInteractor.UserCallback { + mUserSwitcherInteractor.addCallback( + object : UserSwitcherInteractor.UserCallback { override fun isEvictable(): Boolean { return adapter.get() == null } @@ -129,7 +132,7 @@ constructor( record: UserRecord, dialogShower: DialogShower?, ) { - userInteractor.onRecordSelected(record, dialogShower) + mUserSwitcherInteractor.onRecordSelected(record, dialogShower) } /** @@ -152,7 +155,7 @@ constructor( * `UserHandle.USER_NULL`, then switch immediately to the newly created guest user. */ fun removeGuestUser(guestUserId: Int, targetUserId: Int) { - userInteractor.removeGuestUser( + mUserSwitcherInteractor.removeGuestUser( guestUserId = guestUserId, targetUserId = targetUserId, ) @@ -168,7 +171,7 @@ constructor( * only if its ephemeral, else keep guest */ fun exitGuestUser(guestUserId: Int, targetUserId: Int, forceRemoveGuestOnExit: Boolean) { - userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit) + mUserSwitcherInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit) } /** @@ -194,31 +197,31 @@ constructor( * The pictures are only loaded if they have not been loaded yet. */ fun refreshUsers() { - userInteractor.refreshUsers() + mUserSwitcherInteractor.refreshUsers() } /** Adds a subscriber to when user switches. */ fun addUserSwitchCallback(callback: UserSwitchCallback) { val interactorCallback = - object : UserInteractor.UserCallback { + object : UserSwitcherInteractor.UserCallback { override fun onUserStateChanged() { callback.onUserSwitched() } } callbackCompatMap[callback] = interactorCallback - userInteractor.addCallback(interactorCallback) + mUserSwitcherInteractor.addCallback(interactorCallback) } /** Removes a previously-added subscriber. */ fun removeUserSwitchCallback(callback: UserSwitchCallback) { val interactorCallback = callbackCompatMap.remove(callback) if (interactorCallback != null) { - userInteractor.removeCallback(interactorCallback) + mUserSwitcherInteractor.removeCallback(interactorCallback) } } fun dump(pw: PrintWriter, args: Array<out String>) { - userInteractor.dump(pw) + mUserSwitcherInteractor.dump(pw) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt index dc7fadd5eb14..1238789389ee 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt @@ -21,7 +21,6 @@ import android.graphics.drawable.Drawable import android.os.Handler import android.os.UserManager import android.provider.Settings.Global.USER_SWITCHER_ENABLED -import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -30,7 +29,6 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.SettingObserver import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel import com.android.systemui.res.R -import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.statusbar.policy.UserSwitcherController import com.android.systemui.util.settings.GlobalSettings @@ -61,10 +59,10 @@ constructor( @Background private val bgHandler: Handler, @Background private val bgDispatcher: CoroutineDispatcher, private val userManager: UserManager, - private val userTracker: UserTracker, private val userSwitcherController: UserSwitcherController, private val userInfoController: UserInfoController, private val globalSetting: GlobalSettings, + private val userRepository: UserRepository, ) : UserSwitcherRepository { private val showUserSwitcherForSingleUser = context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user) @@ -80,7 +78,7 @@ constructor( globalSetting, bgHandler, USER_SWITCHER_ENABLED, - userTracker.userId, + userRepository.getSelectedUserInfo().id, ) { override fun handleValueChanged(value: Int, observedChange: Boolean) { if (observedChange) { @@ -147,7 +145,7 @@ constructor( private suspend fun isGuestUser(): Boolean { return withContext(bgDispatcher) { - userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) + userManager.isGuestUser(userRepository.getSelectedUserInfo().id) } } diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt new file mode 100644 index 000000000000..0e693d0c8622 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt @@ -0,0 +1,38 @@ +package com.android.systemui.user.domain.interactor + +import android.annotation.UserIdInt +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER +import com.android.systemui.user.data.repository.UserRepository +import javax.inject.Inject + +/** Encapsulates business logic to interact the selected user */ +@SysUISingleton +class SelectedUserInteractor +@Inject +constructor( + private val repository: UserRepository, + private val flags: FeatureFlagsClassic, +) { + + /** + * Returns the ID of the currently-selected user. + * + * @param bypassFlag this will ignore the feature flag and get the data from the repository + * instead. This is used for refactored methods that were previously pointing to `userTracker` + * and therefore should not be routed back to KeyguardUpdateMonitor when flag is disabled. + * KeyguardUpdateMonitor.getCurrentUser() is deprecated and will be removed soon (together + * with this flag). + */ + @UserIdInt + @JvmOverloads + fun getSelectedUserId(bypassFlag: Boolean = false): Int { + if (bypassFlag || flags.isEnabled(REFACTOR_GETCURRENTUSER)) { + return repository.getSelectedUserInfo().id + } else { + return KeyguardUpdateMonitor.getCurrentUser() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt index dbc3bf3a75a2..e0d205fc4b6a 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 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. @@ -83,9 +83,9 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -/** Encapsulates business logic to interact with user data and systems. */ +/** Encapsulates business logic to for the user switcher. */ @SysUISingleton -class UserInteractor +class UserSwitcherInteractor @Inject constructor( @Application private val applicationContext: Context, @@ -383,10 +383,6 @@ constructor( pw.println("isGuestUserAutoCreated=$isGuestUserAutoCreated") } - fun onDeviceBootCompleted() { - guestUserInteractor.onDeviceBootCompleted() - } - /** Switches to the user or executes the action represented by the given record. */ fun onRecordSelected( record: UserRecord, @@ -535,12 +531,6 @@ constructor( } } - /** Returns the ID of the currently-selected user. */ - @UserIdInt - fun getSelectedUserId(): Int { - return repository.getSelectedUserInfo().id - } - private fun showDialog(request: ShowDialogRequestModel) { _dialogShowRequests.value = request } @@ -664,7 +654,6 @@ constructor( // Connect to the new secondary user's service (purely to ensure that a persistent // SystemUI application is created for that user) - if (userId != Process.myUserHandle().identifier) { applicationContext.startServiceAsUser( intent, @@ -826,6 +815,6 @@ constructor( } companion object { - private const val TAG = "UserInteractor" + private const val TAG = "UserSwitcherInteractor" } } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt index 0930cb8a3d7a..922dc0521a49 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt @@ -33,7 +33,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.tiles.UserDetailView import com.android.systemui.user.UserSwitchFullscreenDialog -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor import com.android.systemui.user.domain.model.ShowDialogRequestModel import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel import dagger.Lazy @@ -53,7 +53,7 @@ constructor( private val falsingManager: Lazy<FalsingManager>, private val broadcastSender: Lazy<BroadcastSender>, private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>, - private val interactor: Lazy<UserInteractor>, + private val interactor: Lazy<UserSwitcherInteractor>, private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>, private val eventLogger: Lazy<UiEventLogger>, private val activityStarter: Lazy<ActivityStarter>, diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt index 78edad7c3af2..2c425b199b4e 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt @@ -17,12 +17,10 @@ package com.android.systemui.user.ui.viewmodel -import android.content.Context import android.graphics.drawable.Drawable import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Text -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -33,8 +31,7 @@ import kotlinx.coroutines.flow.mapLatest class StatusBarUserChipViewModel @Inject constructor( - @Application private val context: Context, - interactor: UserInteractor, + interactor: UserSwitcherInteractor, ) { /** Whether the status bar chip ui should be available */ val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt index 20f0fa8cf46b..4089889f4b1e 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt @@ -20,9 +20,8 @@ package com.android.systemui.user.ui.viewmodel import com.android.systemui.common.shared.model.Text import com.android.systemui.common.ui.drawable.CircularDrawable import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.res.R import com.android.systemui.user.domain.interactor.GuestUserInteractor -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper import com.android.systemui.user.shared.model.UserActionModel import com.android.systemui.user.shared.model.UserModel @@ -38,17 +37,17 @@ import kotlinx.coroutines.flow.map class UserSwitcherViewModel @Inject constructor( - private val userInteractor: UserInteractor, + private val userSwitcherInteractor: UserSwitcherInteractor, private val guestUserInteractor: GuestUserInteractor, ) { /** The currently selected user. */ val selectedUser: Flow<UserViewModel> = - userInteractor.selectedUser.map { user -> toViewModel(user) } + userSwitcherInteractor.selectedUser.map { user -> toViewModel(user) } /** On-device users. */ val users: Flow<List<UserViewModel>> = - userInteractor.users.map { models -> models.map { user -> toViewModel(user) } } + userSwitcherInteractor.users.map { models -> models.map { user -> toViewModel(user) } } /** The maximum number of columns that the user selection grid should use. */ val maximumUserColumns: Flow<Int> = users.map { getMaxUserSwitcherItemColumns(it.size) } @@ -61,7 +60,9 @@ constructor( val isMenuVisible: Flow<Boolean> = _isMenuVisible /** The user action menu. */ val menu: Flow<List<UserActionViewModel>> = - userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } } + userSwitcherInteractor.actions.map { actions -> + actions.map { action -> toViewModel(action) } + } /** Whether the button to open the user action menu is visible. */ val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() } @@ -175,7 +176,7 @@ constructor( isTablet = true, ), onClicked = { - userInteractor.executeAction(action = model) + userSwitcherInteractor.executeAction(action = model) // We don't finish because we want to show a dialog over the full-screen UI and // that dialog can be dismissed in case the user changes their mind and decides not // to add a user. @@ -195,7 +196,7 @@ constructor( null } else { { - userInteractor.selectUser(model.id) + userSwitcherInteractor.selectUser(model.id) userSwitched.value = true } } diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java index 9b06a37e681f..d56672564f6f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java @@ -191,7 +191,7 @@ public class AsyncSensorManager extends SensorManager } @Override - protected boolean initDataInjectionImpl(boolean enable) { + protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) { throw new UnsupportedOperationException("not implemented"); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java index 2d1e622fbdce..50d15475434b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/Events.java +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -234,6 +234,10 @@ public class Events { VOLUME_DIALOG_SLIDER(150), @UiEvent(doc = "The audio stream was set to silent via slider") VOLUME_DIALOG_SLIDER_TO_ZERO(151), + @UiEvent(doc = "ODI captions was clicked") + VOLUME_DIALOG_ODI_CAPTIONS_CLICKED(1503), + @UiEvent(doc = "ODI captions tooltip dismiss was clicked") + VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED(1504), @UiEvent(doc = "The audio volume was adjusted to silent via key") VOLUME_KEY_TO_ZERO(152), @UiEvent(doc = "The audio volume was adjusted to non-silent via key") @@ -362,6 +366,10 @@ public class Events { if (tag == EVENT_SETTINGS_CLICK) { sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_SETTINGS); sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_SETTINGS_CLICK); + } else if (tag == EVENT_ODI_CAPTIONS_CLICK) { + sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED); + } else if (tag == EVENT_ODI_CAPTIONS_TOOLTIP_CLICK) { + sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED); } return sb.toString(); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 929b91cf6993..0ff308e19dd6 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -120,7 +120,6 @@ import com.android.internal.view.RotationPolicy; import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.Prefs; -import com.android.systemui.res.R; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.dialog.MediaOutputDialogFactory; @@ -129,6 +128,7 @@ import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; @@ -287,7 +287,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private boolean mIsAnimatingDismiss = false; private boolean mHasSeenODICaptionsTooltip; private ViewStub mODICaptionsTooltipViewStub; - private View mODICaptionsTooltipView = null; + @VisibleForTesting View mODICaptionsTooltipView = null; private final boolean mUseBackgroundBlur; private Consumer<Boolean> mCrossWindowBlurEnabledListener; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt index 81fef7adb3ea..b31f630a4317 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt @@ -35,9 +35,12 @@ import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_K import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings +import java.io.PrintWriter import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -45,26 +48,21 @@ import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import java.io.PrintWriter @SmallTest class ActiveUnlockConfigTest : SysuiTestCase() { private lateinit var secureSettings: FakeSettings - @Mock - private lateinit var contentResolver: ContentResolver - @Mock - private lateinit var handler: Handler - @Mock - private lateinit var dumpManager: DumpManager - @Mock - private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var contentResolver: ContentResolver + @Mock private lateinit var handler: Handler + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var mockPrintWriter: PrintWriter - @Captor - private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver> + @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver> private lateinit var activeUnlockConfig: ActiveUnlockConfig private var currentUser: Int = 0 @@ -73,14 +71,16 @@ class ActiveUnlockConfigTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - currentUser = KeyguardUpdateMonitor.getCurrentUser() + whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUser) secureSettings = FakeSettings() - activeUnlockConfig = ActiveUnlockConfig( + activeUnlockConfig = + ActiveUnlockConfig( handler, secureSettings, contentResolver, + selectedUserInteractor, dumpManager - ) + ) } @Test @@ -92,8 +92,9 @@ class ActiveUnlockConfigTest : SysuiTestCase() { fun onWakeupSettingChanged() { // GIVEN no active unlock settings enabled assertFalse( - activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE) + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE + ) ) // WHEN unlock on wake is allowed @@ -102,16 +103,19 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure assertTrue( - activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE) + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE + ) ) assertTrue( - activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT) + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT + ) ) assertTrue( - activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL) + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL + ) ) } @@ -119,8 +123,9 @@ class ActiveUnlockConfigTest : SysuiTestCase() { fun onUnlockIntentSettingChanged() { // GIVEN no active unlock settings enabled assertFalse( - activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT) + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT + ) ) // WHEN unlock on biometric failed is allowed @@ -128,12 +133,21 @@ class ActiveUnlockConfigTest : SysuiTestCase() { updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)) // THEN active unlock triggers allowed on: biometric failure ONLY - assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)) - assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)) - assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)) + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE + ) + ) + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT + ) + ) + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL + ) + ) } @Test @@ -141,24 +155,39 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // GIVEN no active unlock settings enabled and triggering unlock intent on biometric // enrollment setting is disabled (empty string is disabled, null would use the default) secureSettings.putStringForUser( - ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "", currentUser) - updateSetting(secureSettings.getUriFor( - ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED - )) - assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)) + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + "", + currentUser + ) + updateSetting( + secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED) + ) + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL + ) + ) // WHEN unlock on biometric failed is allowed secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // THEN active unlock triggers allowed on: biometric failure ONLY - assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)) - assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)) - assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)) + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE + ) + ) + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT + ) + ) + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL + ) + ) } @Test @@ -168,16 +197,21 @@ class ActiveUnlockConfigTest : SysuiTestCase() { updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // WHEN face error timeout (3), allow trigger active unlock - secureSettings.putStringForUser( - ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser) + secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS)) // THEN active unlock triggers allowed on error TIMEOUT - assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError( - BiometricFaceConstants.FACE_ERROR_TIMEOUT)) + assertTrue( + activeUnlockConfig.shouldRequestActiveUnlockOnFaceError( + BiometricFaceConstants.FACE_ERROR_TIMEOUT + ) + ) - assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError( - BiometricFaceConstants.FACE_ERROR_CANCELED)) + assertFalse( + activeUnlockConfig.shouldRequestActiveUnlockOnFaceError( + BiometricFaceConstants.FACE_ERROR_CANCELED + ) + ) } @Test @@ -189,21 +223,34 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger secureSettings.putStringForUser( ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, - "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" + - "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}", - currentUser) + "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" + + "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}", + currentUser + ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)) // THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING - assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( - BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED)) - assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( - BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED)) - - assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( - BiometricFaceConstants.FACE_ACQUIRED_GOOD)) - assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( - BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED)) + assertTrue( + activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED + ) + ) + assertTrue( + activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED + ) + ) + + assertFalse( + activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_GOOD + ) + ) + assertFalse( + activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED + ) + ) } @Test @@ -221,14 +268,19 @@ class ActiveUnlockConfigTest : SysuiTestCase() { secureSettings.putStringForUser( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, - "${ActiveUnlockConfig.BiometricType.NONE.intValue}", currentUser) - updateSetting(secureSettings.getUriFor( - ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED - )) + "${ActiveUnlockConfig.BiometricType.NONE.intValue}", + currentUser + ) + updateSetting( + secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED) + ) // THEN active unlock triggers allowed on unlock intent - assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)) + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT + ) + ) } @Test @@ -245,33 +297,43 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs // are enrolled secureSettings.putStringForUser( - ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, - "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" + - "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}", - currentUser) - updateSetting(secureSettings.getUriFor( - ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED - )) + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" + + "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}", + currentUser + ) + updateSetting( + secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED) + ) // THEN active unlock triggers NOT allowed on unlock intent - assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)) + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT + ) + ) // WHEN fingerprint ONLY enrolled `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false) `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true) // THEN active unlock triggers allowed on unlock intent - assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)) + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT + ) + ) // WHEN face ONLY enrolled `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true) `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false) // THEN active unlock triggers allowed on unlock intent - assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)) + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT + ) + ) } @Test @@ -280,7 +342,8 @@ class ActiveUnlockConfigTest : SysuiTestCase() { secureSettings.putIntForUser( ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, PowerManager.WAKE_REASON_LIFT, - currentUser) + currentUser + ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) // THEN only WAKE_REASON_LIFT is considered an unlock intent @@ -299,16 +362,18 @@ class ActiveUnlockConfigTest : SysuiTestCase() { secureSettings.putStringForUser( ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, PowerManager.WAKE_REASON_LIFT.toString() + - "|" + - PowerManager.WAKE_REASON_TAP.toString(), + "|" + + PowerManager.WAKE_REASON_TAP.toString(), currentUser ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) // THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { - if (wakeReason == PowerManager.WAKE_REASON_LIFT || - wakeReason == PowerManager.WAKE_REASON_TAP) { + if ( + wakeReason == PowerManager.WAKE_REASON_LIFT || + wakeReason == PowerManager.WAKE_REASON_TAP + ) { assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason)) } else { assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason)) @@ -316,26 +381,36 @@ class ActiveUnlockConfigTest : SysuiTestCase() { } assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_LIFT)) assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP)) - assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent( - PowerManager.WAKE_REASON_UNFOLD_DEVICE)) + assertFalse( + activeUnlockConfig.isWakeupConsideredUnlockIntent( + PowerManager.WAKE_REASON_UNFOLD_DEVICE + ) + ) } @Test fun isWakeupConsideredUnlockIntent_emptyValues() { // GIVEN lift and tap are considered an unlock intent - secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, " ", - currentUser) + secureSettings.putStringForUser( + ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, + " ", + currentUser + ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) // THEN no wake up gestures are considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason)) } - assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent( - PowerManager.WAKE_REASON_LIFT)) + assertFalse( + activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_LIFT) + ) assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP)) - assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent( - PowerManager.WAKE_REASON_UNFOLD_DEVICE)) + assertFalse( + activeUnlockConfig.isWakeupConsideredUnlockIntent( + PowerManager.WAKE_REASON_UNFOLD_DEVICE + ) + ) } @Test @@ -343,11 +418,12 @@ class ActiveUnlockConfigTest : SysuiTestCase() { verifyRegisterSettingObserver() // GIVEN lift is considered an unlock intent - secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, - PowerManager.WAKE_REASON_LIFT.toString(), currentUser) - updateSetting(secureSettings.getUriFor( - ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD - )) + secureSettings.putStringForUser( + ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, + PowerManager.WAKE_REASON_LIFT.toString(), + currentUser + ) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD)) // THEN only WAKE_REASON_LIFT is considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { @@ -364,11 +440,12 @@ class ActiveUnlockConfigTest : SysuiTestCase() { verifyRegisterSettingObserver() // GIVEN lift and tap are considered an unlock intent - secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, - " ", currentUser) - updateSetting(secureSettings.getUriFor( - ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD - )) + secureSettings.putStringForUser( + ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, + " ", + currentUser + ) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD)) // THEN no wake up gestures are considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { @@ -381,20 +458,21 @@ class ActiveUnlockConfigTest : SysuiTestCase() { verifyRegisterSettingObserver() // GIVEN lift and tap are considered an unlock intent - secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, + secureSettings.putStringForUser( + ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, PowerManager.WAKE_REASON_LIFT.toString() + - "|" + - PowerManager.WAKE_REASON_TAP.toString(), + "|" + + PowerManager.WAKE_REASON_TAP.toString(), currentUser ) - updateSetting(secureSettings.getUriFor( - ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD - )) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD)) // THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { - if (wakeReason == PowerManager.WAKE_REASON_LIFT || - wakeReason == PowerManager.WAKE_REASON_TAP) { + if ( + wakeReason == PowerManager.WAKE_REASON_LIFT || + wakeReason == PowerManager.WAKE_REASON_TAP + ) { assertTrue(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason)) } else { assertFalse(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason)) @@ -405,13 +483,16 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun dump_onUnlockIntentWhenBiometricEnrolled_invalidNum_noArrayOutOfBoundsException() { // GIVEN an invalid input (-1) - secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, - "-1", currentUser) + secureSettings.putStringForUser( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + "-1", + currentUser + ) // WHEN the setting updates - updateSetting(secureSettings.getUriFor( - ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED - )) + updateSetting( + secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED) + ) // THEN no exception thrown activeUnlockConfig.dump(mockPrintWriter, emptyArray()) @@ -419,12 +500,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { private fun updateSetting(uri: Uri) { verifyRegisterSettingObserver() - settingsObserverCaptor.value.onChange( - false, - listOf(uri), - 0, - 0 /* flags */ - ) + settingsObserverCaptor.value.onChange(false, listOf(uri), 0, 0 /* flags */) } private fun verifyRegisterSettingObserver() { @@ -433,19 +509,21 @@ class ActiveUnlockConfigTest : SysuiTestCase() { verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS)) verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)) - verifyRegisterSettingObserver(secureSettings.getUriFor( - ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED - )) - verifyRegisterSettingObserver(secureSettings.getUriFor( - ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS - )) + verifyRegisterSettingObserver( + secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED) + ) + verifyRegisterSettingObserver( + secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS) + ) } private fun verifyRegisterSettingObserver(uri: Uri) { - verify(contentResolver).registerContentObserver( + verify(contentResolver) + .registerContentObserver( eq(uri), eq(false), capture(settingsObserverCaptor), - eq(UserHandle.USER_ALL)) + eq(UserHandle.USER_ALL) + ) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java index d506584cdfac..cdd0eb0e265d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java @@ -50,6 +50,7 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.SysuiTestCase; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import org.junit.After; import org.junit.Before; @@ -64,7 +65,7 @@ import org.mockito.MockitoAnnotations; @SmallTest public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { - private static final int TARGET_USER_ID = KeyguardUpdateMonitor.getCurrentUser(); + private static final int TARGET_USER_ID = 0; private AdminSecondaryLockScreenController mTestController; private ComponentName mComponentName; @@ -80,12 +81,15 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { private KeyguardSecurityCallback mKeyguardCallback; @Mock private KeyguardUpdateMonitor mUpdateMonitor; + @Mock + private SelectedUserInteractor mSelectedUserInteractor; private SurfaceControlViewHost.SurfacePackage mSurfacePackage; @Before public void setUp() { MockitoAnnotations.initMocks(this); + when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID); mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext)); mKeyguardSecurityContainer.setId(View.generateViewId()); @@ -106,7 +110,8 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { new Binder())).getSurfacePackage(); mTestController = new AdminSecondaryLockScreenController.Factory( - mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler) + mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler, + mSelectedUserInteractor) .create(mKeyguardCallback); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt index 30fed0b0316f..c61b11ae7336 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt @@ -29,6 +29,7 @@ import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.shade.ShadeController import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -59,6 +60,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() { @Mock lateinit var metricsLogger: MetricsLogger @Mock lateinit var lockPatternUtils: LockPatternUtils @Mock lateinit var packageManager: PackageManager + @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor val fakeSystemClock = FakeSystemClock() val mainExecutor = FakeExecutor(fakeSystemClock) val backgroundExecutor = FakeExecutor(fakeSystemClock) @@ -79,7 +81,8 @@ class EmergencyButtonControllerTest : SysuiTestCase() { metricsLogger, lockPatternUtils, mainExecutor, - backgroundExecutor + backgroundExecutor, + mSelectedUserInteractor, ) context.setMockPackageManager(packageManager) Mockito.`when`(emergencyButton.context).thenReturn(context) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index 42f65f6f53b4..7f20d9aa9451 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -37,12 +37,13 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.res.R; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import org.junit.Before; import org.junit.Test; @@ -81,6 +82,8 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { private EmergencyButtonController mEmergencyButtonController; private FakeFeatureFlags mFeatureFlags; + @Mock + private SelectedUserInteractor mSelectedUserInteractor; private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController; @Before @@ -105,7 +108,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { return new KeyguardAbsKeyInputViewController(mAbsKeyInputView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector, - mEmergencyButtonController, mFeatureFlags) { + mEmergencyButtonController, mFeatureFlags, mSelectedUserInteractor) { @Override void resetState() { } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt index 91b544b8265c..634dac16d1ec 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt @@ -25,6 +25,7 @@ import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUT import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE import com.android.systemui.SysuiTestCase import com.android.systemui.log.SessionTracker +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -34,8 +35,8 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @SmallTest @@ -50,6 +51,8 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { lateinit var sessionTracker: SessionTracker @Mock lateinit var sessionId: InstanceId + @Mock + lateinit var mSelectedUserInteractor: SelectedUserInteractor @Captor lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback> @@ -65,7 +68,8 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger( uiEventLogger, keyguardUpdateMonitor, - sessionTracker) + sessionTracker, + mSelectedUserInteractor) } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index d8a2c5f76fce..3fbcf6d77f82 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -24,12 +24,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.mockito.whenever import org.junit.Before @@ -62,6 +63,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { @Mock lateinit var mainExecutor: DelayableExecutor @Mock lateinit var falsingCollector: FalsingCollector @Mock lateinit var keyguardViewController: KeyguardViewController + @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea @Mock private lateinit var mKeyguardMessageAreaController: @@ -106,7 +108,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { falsingCollector, keyguardViewController, postureController, - fakeFeatureFlags + fakeFeatureFlags, + mSelectedUserInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index dc1618d128b3..74c922561343 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -24,15 +24,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -75,6 +76,9 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { KeyguardMessageAreaController.Factory @Mock + private lateinit var mSelectedUserInteractor: SelectedUserInteractor + + @Mock private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController<BouncerKeyguardMessageArea> @@ -108,7 +112,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { mEmergencyButtonController, mKeyguardMessageAreaControllerFactory, mPostureController, - fakeFeatureFlags + fakeFeatureFlags, + mSelectedUserInteractor ) mKeyguardPatternView.onAttachedToWindow() } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index 4a24e4a1f40f..d41c2497b230 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -34,10 +34,10 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingCollectorFake; -import com.android.systemui.classifier.SingleTapClassifier; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import org.junit.Before; import org.junit.Test; @@ -76,11 +76,11 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { private EmergencyButtonController mEmergencyButtonController; private FalsingCollector mFalsingCollector = new FalsingCollectorFake(); @Mock - private SingleTapClassifier mSingleTapClassifier; - @Mock private View mDeleteButton; @Mock private View mOkButton; + @Mock + private SelectedUserInteractor mSelectedUserInteractor; private NumPadKey[] mButtons = new NumPadKey[]{}; private KeyguardPinBasedInputViewController mKeyguardPinViewController; @@ -108,7 +108,8 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener, - mEmergencyButtonController, mFalsingCollector, featureFlags) { + mEmergencyButtonController, mFalsingCollector, featureFlags, + mSelectedUserInteractor) { @Override public void onResume(int reason) { super.onResume(reason); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 9df4dd4df4d2..80d45bcc23dd 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -25,15 +25,16 @@ import androidx.test.filters.SmallTest 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.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -84,6 +85,7 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Mock private val mEmergencyButtonController: EmergencyButtonController? = null private val falsingCollector: FalsingCollector = FalsingCollectorFake() @Mock lateinit var postureController: DevicePostureController + @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock lateinit var featureFlags: FeatureFlags @Mock lateinit var passwordTextView: PasswordTextView @@ -133,7 +135,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { mEmergencyButtonController, falsingCollector, postureController, - featureFlags + featureFlags, + mSelectedUserInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 20d4eb907944..fda4133240c2 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -63,7 +63,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.UserSwitcherController -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat @@ -136,7 +136,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback @Mock private lateinit var audioManager: AudioManager - @Mock private lateinit var userInteractor: UserInteractor + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var postureController: DevicePostureController @@ -215,10 +215,10 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { null, keyguardViewController, postureController, - featureFlags + featureFlags, + mSelectedUserInteractor, ) - whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID) sceneTestUtils = SceneTestUtils(this) sceneInteractor = sceneTestUtils.sceneInteractor() keyguardTransitionInteractor = @@ -260,7 +260,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { mock(), mock(), { JavaAdapter(sceneTestUtils.testScope.backgroundScope) }, - userInteractor, + mSelectedUserInteractor, deviceProvisionedController, faceAuthAccessibilityDelegate, keyguardTransitionInteractor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index 4290b8b28687..94c3bde29597 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -24,11 +24,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.res.R +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import org.junit.Before import org.junit.Test @@ -58,6 +59,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var falsingCollector: FalsingCollector @Mock private lateinit var emergencyButtonController: EmergencyButtonController + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<BouncerKeyguardMessageArea> @@ -90,6 +92,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { falsingCollector, emergencyButtonController, fakeFeatureFlags, + mSelectedUserInteractor ) underTest.init() } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index 31ee6411f93b..7b1f302da6e8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt @@ -24,11 +24,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.res.R +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import org.junit.Before import org.junit.Test @@ -54,6 +55,7 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var falsingCollector: FalsingCollector @Mock private lateinit var emergencyButtonController: EmergencyButtonController + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<BouncerKeyguardMessageArea> @@ -89,6 +91,7 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { falsingCollector, emergencyButtonController, fakeFeatureFlags, + mSelectedUserInteractor, ) underTest.init() } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 47be236b1272..aabdcb7fa660 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -38,7 +38,6 @@ import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING; import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT; -import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; @@ -82,7 +81,6 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.pm.UserInfo; import android.database.ContentObserver; import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; @@ -157,8 +155,8 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.telephony.TelephonyListenerManager; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.settings.GlobalSettings; -import com.android.systemui.util.settings.SecureSettings; import org.junit.After; import org.junit.Assert; @@ -240,8 +238,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock - private SecureSettings mSecureSettings; - @Mock private TelephonyManager mTelephonyManager; @Mock private SensorPrivacyManager mSensorPrivacyManager; @@ -278,18 +274,17 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private UsbPortStatus mUsbPortStatus; @Mock - private Uri mURI; - @Mock private TaskStackChangeListeners mTaskStackChangeListeners; @Mock private IActivityTaskManager mActivityTaskManager; @Mock private WakefulnessLifecycle mWakefulness; + @Mock + private SelectedUserInteractor mSelectedUserInteractor; private List<FaceSensorPropertiesInternal> mFaceSensorProperties; private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties; private final int mCurrentUserId = 100; - private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0); @Captor private ArgumentCaptor<IBiometricEnabledOnKeyguardCallback> @@ -337,8 +332,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { .startMocking(); ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID) .when(SubscriptionManager::getDefaultSubscriptionId); - KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId); - when(mUserTracker.getUserId()).thenReturn(mCurrentUserId); + when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mCurrentUserId); + when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mCurrentUserId); mContext.getOrCreateTestableResources().addOverride( com.android.systemui.res.R.integer.config_face_auth_supported_posture, @@ -351,13 +346,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mContext.getOrCreateTestableResources().addOverride(com.android.systemui.res .R.array.config_fingerprint_listen_on_occluding_activity_packages, - new String[]{ PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY }); + new String[]{PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY}); mTestableLooper = TestableLooper.get(this); allowTestableLooperAsMainThread(); - when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI); - final ContentResolver contentResolver = mContext.getContentResolver(); ExtendedMockito.spyOn(contentResolver); doNothing().when(contentResolver) @@ -1005,11 +998,14 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void trustAgentHasTrust() { // WHEN user has trust - mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null); + mKeyguardUpdateMonitor.onTrustChanged(true, true, + mSelectedUserInteractor.getSelectedUserId(), 0, null); // THEN user is considered as "having trust" and bouncer can be skipped - Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())); - Assert.assertTrue(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser())); + Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust( + mSelectedUserInteractor.getSelectedUserId())); + Assert.assertTrue(mKeyguardUpdateMonitor.getUserCanSkipBouncer( + mSelectedUserInteractor.getSelectedUserId())); } @Test @@ -1027,15 +1023,19 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void trustAgentHasTrust_fingerprintLockout() { // GIVEN user has trust - mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null); - Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())); + mKeyguardUpdateMonitor.onTrustChanged(true, true, + mSelectedUserInteractor.getSelectedUserId(), 0, null); + Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust( + mSelectedUserInteractor.getSelectedUserId())); // WHEN fingerprint is lock out fingerprintErrorTemporaryLockOut(); // THEN user is NOT considered as "having trust" and bouncer cannot be skipped - Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())); - Assert.assertFalse(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser())); + Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust( + mSelectedUserInteractor.getSelectedUserId())); + Assert.assertFalse(mKeyguardUpdateMonitor.getUserCanSkipBouncer( + mSelectedUserInteractor.getSelectedUserId())); } @Test @@ -1217,7 +1217,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); lockscreenBypassIsAllowed(); mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */, - KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, + mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, new ArrayList<>()); keyguardIsVisible(); verifyFaceAuthenticateCall(); @@ -1249,7 +1249,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); mTestableLooper.processAllMessages(); mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */, - KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>()); + mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, new ArrayList<>()); keyguardIsVisible(); verifyFaceAuthenticateNeverCalled(); } @@ -1287,7 +1287,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() { // test whether face will be skipped if authenticated, so the value of isClass3Biometric // doesn't matter here - mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(), + mKeyguardUpdateMonitor.onFaceAuthenticated(mSelectedUserInteractor.getSelectedUserId(), true /* isClass3Biometric */); setKeyguardBouncerVisibility(true); mTestableLooper.processAllMessages(); @@ -1333,7 +1333,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testGetUserCanSkipBouncer_whenFace() { - int user = KeyguardUpdateMonitor.getCurrentUser(); + int user = mSelectedUserInteractor.getSelectedUserId(); mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue(); } @@ -1342,14 +1342,14 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() { when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */)) .thenReturn(false); - int user = KeyguardUpdateMonitor.getCurrentUser(); + int user = mSelectedUserInteractor.getSelectedUserId(); mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse(); } @Test public void testGetUserCanSkipBouncer_whenFingerprint() { - int user = KeyguardUpdateMonitor.getCurrentUser(); + int user = mSelectedUserInteractor.getSelectedUserId(); mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue(); } @@ -1358,7 +1358,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() { when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */)) .thenReturn(false); - int user = KeyguardUpdateMonitor.getCurrentUser(); + int user = mSelectedUserInteractor.getSelectedUserId(); mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse(); } @@ -1372,7 +1372,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(1); assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(1); - mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, () -> {}); + mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, () -> { + }); assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(0); assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(0); } @@ -1446,7 +1447,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testGetUserCanSkipBouncer_whenTrust() { - int user = KeyguardUpdateMonitor.getCurrentUser(); + int user = mSelectedUserInteractor.getSelectedUserId(); mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */, user, 0 /* flags */, new ArrayList<>()); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue(); @@ -1493,7 +1494,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testIsUserUnlocked() { // mUserManager will report the user as unlocked on @Before - assertThat(mKeyguardUpdateMonitor.isUserUnlocked(KeyguardUpdateMonitor.getCurrentUser())) + assertThat( + mKeyguardUpdateMonitor.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId())) .isTrue(); // Invalid user should not be unlocked. int randomUser = 99; @@ -1502,7 +1504,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testTrustUsuallyManaged_whenTrustChanges() { - int user = KeyguardUpdateMonitor.getCurrentUser(); + int user = mSelectedUserInteractor.getSelectedUserId(); when(mTrustManager.isTrustUsuallyManaged(eq(user))).thenReturn(true); mKeyguardUpdateMonitor.onTrustManagedChanged(false /* managed */, user); assertThat(mKeyguardUpdateMonitor.isTrustUsuallyManaged(user)).isTrue(); @@ -1510,7 +1512,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testTrustUsuallyManaged_resetWhenUserIsRemoved() { - int user = KeyguardUpdateMonitor.getCurrentUser(); + int user = mSelectedUserInteractor.getSelectedUserId(); when(mTrustManager.isTrustUsuallyManaged(eq(user))).thenReturn(true); mKeyguardUpdateMonitor.onTrustManagedChanged(false /* managed */, user); assertThat(mKeyguardUpdateMonitor.isTrustUsuallyManaged(user)).isTrue(); @@ -1521,9 +1523,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testSecondaryLockscreenRequirement() { - KeyguardUpdateMonitor.setCurrentUser(UserHandle.myUserId()); + when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(UserHandle.myUserId()); when(mUserTracker.getUserId()).thenReturn(UserHandle.myUserId()); - int user = KeyguardUpdateMonitor.getCurrentUser(); + int user = mSelectedUserInteractor.getSelectedUserId(); String packageName = "fake.test.package"; String cls = "FakeService"; ServiceInfo serviceInfo = new ServiceInfo(); @@ -1680,7 +1682,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD); // WHEN user loses smart unlock trust - when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) + when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId())) .thenReturn(SOME_AUTH_REQUIRED_AFTER_USER_REQUEST); // THEN we should still listen for udfps @@ -1724,7 +1726,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // WHEN trust is enabled (ie: via smartlock) mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */, - KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>()); + mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, new ArrayList<>()); // THEN we shouldn't listen for udfps assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false); @@ -1738,7 +1740,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // WHEN face authenticated mKeyguardUpdateMonitor.onFaceAuthenticated( - KeyguardUpdateMonitor.getCurrentUser(), false); + mSelectedUserInteractor.getSelectedUserId(), false); // THEN we shouldn't listen for udfps assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false); @@ -1825,7 +1827,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void testShowTrustGrantedMessage_onTrustGranted() { // WHEN trust is enabled (ie: via some trust agent) with a trustGranted string mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */, - KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, + mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, Arrays.asList("Unlocked by wearable")); // THEN the showTrustGrantedMessage should be called with the first message @@ -2256,7 +2258,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.onTrustChanged( true /* enabled */, true /* newlyUnlocked */, - getCurrentUser() /* userId */, + mSelectedUserInteractor.getSelectedUserId() /* userId */, TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */, null /* trustGrantedMessages */); @@ -2281,7 +2283,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.onTrustChanged( true /* enabled */, true /* newlyUnlocked */, - getCurrentUser() /* userId */, + mSelectedUserInteractor.getSelectedUserId() /* userId */, TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */, null /* trustGrantedMessages */); @@ -2333,7 +2335,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.onTrustChanged( true /* enabled */, true /* newlyUnlocked */, - getCurrentUser() /* userId */, + mSelectedUserInteractor.getSelectedUserId() /* userId */, TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */, null /* trustGrantedMessages */); @@ -2362,7 +2364,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.onTrustChanged( true /* enabled */, true /* newlyUnlocked */, - getCurrentUser() /* userId, not the current userId */, + mSelectedUserInteractor.getSelectedUserId() /* userId, not the current userId */, TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER /* flags */, null /* trustGrantedMessages */); @@ -2388,7 +2390,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.onTrustChanged( true /* enabled */, true /* newlyUnlocked */, - getCurrentUser() /* userId, not the current userId */, + mSelectedUserInteractor.getSelectedUserId() /* userId, not the current userId */, TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */, null /* trustGrantedMessages */); @@ -2423,7 +2425,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // WHEN strong auth changes and device is in user lockdown when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); userDeviceLockDown(); - mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged(getCurrentUser()); + mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged( + mSelectedUserInteractor.getSelectedUserId()); mTestableLooper.processAllMessages(); // THEN face and fingerprint listening are cancelled @@ -2451,7 +2454,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // WHEN non-strong biometric allowed changes when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); - mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(getCurrentUser()); + mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged( + mSelectedUserInteractor.getSelectedUserId()); mTestableLooper.processAllMessages(); // THEN face and fingerprint listening are cancelled @@ -2516,13 +2520,15 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { keyguardIsVisible(); keyguardNotGoingAway(); statusBarShadeIsNotLocked(); - when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( + true); // WHEN the assistant is visible mKeyguardUpdateMonitor.setAssistantVisible(true); // THEN request unlock with keyguard dismissal - verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + verify(mTrustManager).reportUserRequestedUnlock( + eq(mSelectedUserInteractor.getSelectedUserId()), eq(true)); } @@ -2531,7 +2537,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { throws RemoteException { // GIVEN shouldTriggerActiveUnlock bouncerFullyVisible(); - when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( + true); // GIVEN active unlock triggers on biometric failures when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -2542,7 +2549,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback.onAuthenticationFailed(); // ALWAYS request unlock with a keyguard dismissal - verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + verify(mTrustManager).reportUserRequestedUnlock( + eq(mSelectedUserInteractor.getSelectedUserId()), eq(true)); } @@ -2554,7 +2562,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { keyguardIsVisible(); keyguardNotGoingAway(); statusBarShadeIsNotLocked(); - when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( + true); // GIVEN active unlock triggers on biometric failures when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -2566,7 +2575,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed(); // THEN request unlock with NO keyguard dismissal - verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + verify(mTrustManager).reportUserRequestedUnlock( + eq(mSelectedUserInteractor.getSelectedUserId()), eq(false)); } @@ -2578,7 +2588,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { keyguardIsVisible(); keyguardNotGoingAway(); statusBarShadeIsNotLocked(); - when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( + true); // GIVEN active unlock triggers on biometric failures when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -2590,7 +2601,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed(); // THEN request unlock with a keyguard dismissal - verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + verify(mTrustManager).reportUserRequestedUnlock( + eq(mSelectedUserInteractor.getSelectedUserId()), eq(true)); } @@ -2600,7 +2612,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // GIVEN shouldTriggerActiveUnlock when(mAuthController.isUdfpsFingerDown()).thenReturn(false); lockscreenBypassIsNotAllowed(); - when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( + true); // GIVEN active unlock triggers on biometric failures when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -2612,7 +2625,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed(); // THEN request unlock with a keyguard dismissal - verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + verify(mTrustManager).reportUserRequestedUnlock( + eq(mSelectedUserInteractor.getSelectedUserId()), eq(true)); } @@ -2701,7 +2715,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { throws RemoteException { // GIVEN shouldTriggerActiveUnlock keyguardIsVisible(); - when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( + true); // GIVEN active unlock triggers on wakeup when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -2717,7 +2732,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); // THEN request unlock with a keyguard dismissal - verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + verify(mTrustManager).reportUserRequestedUnlock( + eq(mSelectedUserInteractor.getSelectedUserId()), eq(true)); } @@ -2726,7 +2742,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { throws RemoteException { // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE keyguardIsVisible(); - when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( + true); // GIVEN active unlock triggers on wakeup when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -2742,7 +2759,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); // THEN request unlock WITHOUT a keyguard dismissal - verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + verify(mTrustManager).reportUserRequestedUnlock( + eq(mSelectedUserInteractor.getSelectedUserId()), eq(false)); } @@ -2751,7 +2769,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { throws RemoteException { // GIVEN shouldTriggerActiveUnlock keyguardIsVisible(); - when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( + true); // GIVEN active unlock triggers on wakeup when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -2767,7 +2786,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); // THEN request unlock with a keyguard dismissal - verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + verify(mTrustManager).reportUserRequestedUnlock( + eq(mSelectedUserInteractor.getSelectedUserId()), eq(true)); } @@ -2777,7 +2797,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { throws RemoteException { // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE keyguardIsVisible(); - when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( + true); // GIVEN active unlock triggers on wakeup when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -2793,7 +2814,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); // THEN request unlock WITHOUT a keyguard dismissal - verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + verify(mTrustManager).reportUserRequestedUnlock( + eq(mSelectedUserInteractor.getSelectedUserId()), eq(false)); } @@ -2857,27 +2879,29 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(captor.getValue().getWakeReason()) .isEqualTo(PowerManager.WAKE_REASON_POWER_BUTTON); } + @Test public void testFingerprintSensorProperties() throws RemoteException { mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered( new ArrayList<>()); assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible( - KeyguardUpdateMonitor.getCurrentUser())).isFalse(); + mSelectedUserInteractor.getSelectedUserId())).isFalse(); mFingerprintAuthenticatorsRegisteredCallback .onAllAuthenticatorsRegistered(mFingerprintSensorProperties); verifyFingerprintAuthenticateCall(); assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible( - KeyguardUpdateMonitor.getCurrentUser())).isTrue(); + mSelectedUserInteractor.getSelectedUserId())).isTrue(); } + @Test public void testFaceSensorProperties() throws RemoteException { mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(new ArrayList<>()); assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser( - KeyguardUpdateMonitor.getCurrentUser())).isFalse(); + mSelectedUserInteractor.getSelectedUserId())).isFalse(); mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); biometricsEnabledForCurrentUser(); @@ -2885,7 +2909,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verifyFaceAuthenticateNeverCalled(); verifyFaceDetectNeverCalled(); assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser( - KeyguardUpdateMonitor.getCurrentUser())).isTrue(); + mSelectedUserInteractor.getSelectedUserId())).isTrue(); } @Test @@ -2917,13 +2941,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.onTrustChanged( true /* enabled */, true /* newlyUnlocked */, - getCurrentUser() /* userId */, + mSelectedUserInteractor.getSelectedUserId() /* userId */, TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */, null /* trustGrantedMessages */); // THEN onTrustChanged is called FIRST final InOrder inOrder = Mockito.inOrder(callback); - inOrder.verify(callback).onTrustChanged(eq(getCurrentUser())); + inOrder.verify(callback).onTrustChanged(eq(mSelectedUserInteractor.getSelectedUserId())); // AND THEN onTrustGrantedForCurrentUser callback called inOrder.verify(callback).onTrustGrantedForCurrentUser( @@ -3144,7 +3168,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private void mockCanBypassLockscreen(boolean canBypass) { // force update the isFaceEnrolled cache: - mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(getCurrentUser()); + mKeyguardUpdateMonitor.isFaceAuthEnabledForUser( + mSelectedUserInteractor.getSelectedUserId()); mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController); when(mKeyguardBypassController.canBypass()).thenReturn(canBypass); @@ -3235,22 +3260,23 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private void biometricsNotDisabledThroughDevicePolicyManager() { when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, - KeyguardUpdateMonitor.getCurrentUser())).thenReturn(0); + mSelectedUserInteractor.getSelectedUserId())).thenReturn(0); } private void biometricsEnabledForCurrentUser() throws RemoteException { - mBiometricEnabledOnKeyguardCallback.onChanged(true, KeyguardUpdateMonitor.getCurrentUser()); + mBiometricEnabledOnKeyguardCallback.onChanged(true, + mSelectedUserInteractor.getSelectedUserId()); } private void biometricsDisabledForCurrentUser() throws RemoteException { mBiometricEnabledOnKeyguardCallback.onChanged( false, - KeyguardUpdateMonitor.getCurrentUser() + mSelectedUserInteractor.getSelectedUserId() ); } private void primaryAuthRequiredEncrypted() { - when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) + when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId())) .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT); when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); } @@ -3261,7 +3287,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void primaryAuthNotRequiredByStrongAuthTracker() { - when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) + when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId())) .thenReturn(0); when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); } @@ -3270,7 +3296,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.onTrustChanged( false, false, - KeyguardUpdateMonitor.getCurrentUser(), + mSelectedUserInteractor.getSelectedUserId(), -1, new ArrayList<>() ); @@ -3435,7 +3461,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { protected TestableKeyguardUpdateMonitor(Context context) { super(context, mUserTracker, TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(), - mBroadcastDispatcher, mSecureSettings, mDumpManager, + mBroadcastDispatcher, mDumpManager, mBackgroundExecutor, mMainExecutor, mStatusBarStateController, mLockPatternUtils, mAuthController, mTelephonyListenerManager, @@ -3447,7 +3473,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mFaceWakeUpTriggersConfig, mDevicePostureController, Optional.of(mInteractiveToAuthProvider), mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker, - mWakefulness); + mWakefulness, mSelectedUserInteractor); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java index 5346db1aa067..daa6070b5403 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java @@ -19,8 +19,7 @@ package com.android.systemui.accessibility.floatingmenu; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; -import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; import static com.google.common.truth.Truth.assertThat; @@ -90,7 +89,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + setFlagDefaults(mSetFlagsRule); MockitoAnnotations.initMocks(this); mContextWrapper = new ContextWrapper(mContext) { @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java index 3b2ea0f5ce86..56664356f964 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java @@ -16,8 +16,7 @@ package com.android.systemui.accessibility.floatingmenu; -import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -59,7 +58,7 @@ public class DismissAnimationControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + setFlagDefaults(mSetFlagsRule); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, mock(SecureSettings.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java index 76a3153e648a..ec6ec63d213d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -16,8 +16,7 @@ package com.android.systemui.accessibility.floatingmenu; -import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; import static com.google.common.truth.Truth.assertThat; @@ -78,7 +77,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + setFlagDefaults(mSetFlagsRule); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java index 83bcd8d50407..e8192c43f6f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java @@ -16,8 +16,7 @@ package com.android.systemui.accessibility.floatingmenu; -import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; import static com.google.common.truth.Truth.assertThat; @@ -29,8 +28,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; @@ -46,7 +45,7 @@ public class MenuEduTooltipViewTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + setFlagDefaults(mSetFlagsRule); final WindowManager windowManager = mContext.getSystemService(WindowManager.class); mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager); mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java index e01f1b76df79..62cb9a01ac84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java @@ -19,8 +19,7 @@ package com.android.systemui.accessibility.floatingmenu; import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS; import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS; -import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; import static com.google.common.truth.Truth.assertThat; @@ -40,8 +39,8 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate; 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.util.settings.SecureSettings; import org.junit.Before; @@ -75,7 +74,7 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase { @Before public void setUp() { - setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + setFlagDefaults(mSetFlagsRule); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java index a88ee108141b..32487534423d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java @@ -18,8 +18,7 @@ package com.android.systemui.accessibility.floatingmenu; import static android.view.View.OVER_SCROLL_NEVER; -import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; import static com.google.common.truth.Truth.assertThat; @@ -83,7 +82,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + setFlagDefaults(mSetFlagsRule); final WindowManager windowManager = mContext.getSystemService(WindowManager.class); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, mock(SecureSettings.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java index 41e5c209e344..03a4ba78a208 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java @@ -19,8 +19,7 @@ package com.android.systemui.accessibility.floatingmenu; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.systemBars; -import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; @@ -76,7 +75,7 @@ public class MenuViewLayerControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + setFlagDefaults(mSetFlagsRule); final WindowManager wm = mContext.getSystemService(WindowManager.class); doAnswer(invocation -> wm.getMaximumWindowMetrics()).when( mWindowManager).getMaximumWindowMetrics(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index b0776c9a5b58..df7d1b7a4aad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -22,9 +22,8 @@ import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; -import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; import static com.google.common.truth.Truth.assertThat; @@ -113,7 +112,7 @@ public class MenuViewLayerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + setFlagDefaults(mSetFlagsRule); final Rect mDisplayBounds = new Rect(); mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH, DISPLAY_WINDOW_HEIGHT); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index ac2bfaf8eddf..b9fd5d0f5e84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -18,8 +18,7 @@ package com.android.systemui.accessibility.floatingmenu; import static android.app.UiModeManager.MODE_NIGHT_YES; -import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; import static com.google.common.truth.Truth.assertThat; @@ -70,7 +69,7 @@ public class MenuViewTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + setFlagDefaults(mSetFlagsRule); mUiModeManager = mContext.getSystemService(UiModeManager.class); mNightMode = mUiModeManager.getNightMode(); mUiModeManager.setNightMode(MODE_NIGHT_YES); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java new file mode 100644 index 000000000000..297554956ed8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.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.accessibility.utils; + +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.systemui.Flags; + +public class FlagUtils { + /** + * Populates a setFlagsRule with every SystemUI a11y feature flag. + * This function should be updated when new flags are added. + * + * @param setFlagsRule set flags rule from the test environment. + */ + public static void setFlagDefaults(SetFlagsRule setFlagsRule) { + setFlagDefault(setFlagsRule, Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + } +} 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 ebe13feb0f6f..c5f16aa97f18 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -36,7 +36,6 @@ import android.view.accessibility.AccessibilityManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams @@ -46,12 +45,14 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager 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.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -111,6 +112,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Mock private lateinit var udfpsUtils: UdfpsUtils + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels> @@ -163,6 +165,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { isDebuggable, udfpsKeyguardAccessibilityDelegate, udfpsKeyguardViewModels, + mSelectedUserInteractor, ) block() } 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 dcb53984ad87..f32e1a5bcd6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -102,6 +102,7 @@ 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.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.concurrency.FakeExecution; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -214,6 +215,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; @Mock private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; + @Mock + private SelectedUserInteractor mSelectedUserInteractor; // Capture listeners so that they can be used to send events @Captor @@ -326,7 +329,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mInputManager, mock(KeyguardFaceAuthInteractor.class), mUdfpsKeyguardAccessibilityDelegate, - mUdfpsKeyguardViewModels + mUdfpsKeyguardViewModels, + mSelectedUserInteractor ); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); 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 e512adcc0542..2c4e1362bed3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java @@ -40,6 +40,7 @@ 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.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.concurrency.DelayableExecutor; import org.junit.Before; @@ -69,6 +70,7 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor; protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor; protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; + protected @Mock SelectedUserInteractor mSelectedUserInteractor; protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @@ -142,7 +144,8 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { mFeatureFlags, mPrimaryBouncerInteractor, mAlternateBouncerInteractor, - mUdfpsKeyguardAccessibilityDelegate); + mUdfpsKeyguardAccessibilityDelegate, + mSelectedUserInteractor); return controller; } } 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 02ee53879ea0..97dada27f8c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -96,6 +96,7 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : mKeyguardUpdateMonitor, FakeTrustRepository(), testScope.backgroundScope, + mSelectedUserInteractor, ) mAlternateBouncerInteractor = AlternateBouncerInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt index 6b9c34bbd237..bbf471c34cc7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt @@ -24,6 +24,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -55,6 +56,7 @@ class UdfpsOverlayInteractorTest : SysuiTestCase() { @Mock private lateinit var udfpsOverlayParams: UdfpsOverlayParams @Mock private lateinit var overlayBounds: Rect + @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor private lateinit var underTest: UdfpsOverlayInteractor @@ -104,7 +106,12 @@ class UdfpsOverlayInteractorTest : SysuiTestCase() { } private fun createUdpfsOverlayInteractor() { - underTest = UdfpsOverlayInteractor(authController, testScope.backgroundScope) + underTest = + UdfpsOverlayInteractor( + authController, + selectedUserInteractor, + testScope.backgroundScope + ) testScope.runCurrent() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt index cc4eca546e17..b48bc1d28f86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt @@ -48,6 +48,7 @@ import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown import com.android.systemui.res.R.string.kg_trust_agent_disabled import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.KotlinArgumentCaptor import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -86,6 +87,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { @Mock private lateinit var countDownTimerUtil: CountDownTimerUtil @Mock private lateinit var systemPropertiesHelper: SystemPropertiesHelper @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor private lateinit var testScope: TestScope @@ -119,6 +121,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { keyguardUpdateMonitor, fakeTrustRepository, testScope.backgroundScope, + mSelectedUserInteractor, ) underTest = BouncerMessageInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt index 9a5b4585e0ae..9373ada75003 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -25,7 +25,6 @@ import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.DejankUtils -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN @@ -37,7 +36,9 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.android.systemui.utils.os.FakeHandler @@ -70,6 +71,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Mock private lateinit var falsingCollector: FalsingCollector @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor private lateinit var mainHandler: FakeHandler private lateinit var underTest: PrimaryBouncerInteractor private lateinit var resources: TestableResources @@ -100,6 +102,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { keyguardUpdateMonitor, trustRepository, testScope.backgroundScope, + mSelectedUserInteractor, ) whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) whenever(repository.primaryBouncerShow.value).thenReturn(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt index 2018e614ba00..d1b120e0948b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt @@ -28,8 +28,8 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.TrustRepository -import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.TestScope @@ -51,8 +51,8 @@ class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() { @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor @Mock private lateinit var falsingCollector: FalsingCollector @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry - @Mock private lateinit var keyguardBypassController: KeyguardBypassController @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor private val mainHandler = FakeHandler(Looper.getMainLooper()) private lateinit var underTest: PrimaryBouncerInteractor @@ -74,6 +74,7 @@ class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() { keyguardUpdateMonitor, Mockito.mock(TrustRepository::class.java), TestScope().backgroundScope, + mSelectedUserInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt index 802f8e6043fd..b75f3e0876ff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt @@ -32,8 +32,8 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.TrustRepository -import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn @@ -60,7 +60,7 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor @Mock private lateinit var falsingCollector: FalsingCollector @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry - @Mock private lateinit var keyguardBypassController: KeyguardBypassController + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor private val mainHandler = FakeHandler(Looper.getMainLooper()) val repository = FakeKeyguardBouncerRepository() @@ -82,6 +82,7 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { keyguardUpdateMonitor, Mockito.mock(TrustRepository::class.java), TestScope().backgroundScope, + mSelectedUserInteractor, ) underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt index 037c1badf0ac..6d3cc4c536f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt @@ -28,11 +28,11 @@ import androidx.test.filters.SmallTest import com.android.systemui.ActivityIntentHelper import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.KotlinArgumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock @@ -46,8 +46,8 @@ import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.anyInt 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(JUnit4::class) @@ -74,7 +74,7 @@ class CameraGestureHelperTest : SysuiTestCase() { @Mock lateinit var contentResolver: ContentResolver @Mock - lateinit var userTracker: UserTracker + lateinit var mSelectedUserInteractor: SelectedUserInteractor private lateinit var underTest: CameraGestureHelper @@ -103,7 +103,7 @@ class CameraGestureHelperTest : SysuiTestCase() { cameraIntents = cameraIntents, contentResolver = contentResolver, uiExecutor = MoreExecutors.directExecutor(), - userTracker = userTracker, + selectedUserInteractor = mSelectedUserInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java index 442bf918ad8c..80fe9e762832 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java @@ -40,6 +40,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.sensors.ThresholdSensor; @@ -75,6 +76,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase { private ShadeExpansionStateManager mShadeExpansionStateManager; @Mock private BatteryController mBatteryController; + @Mock + private SelectedUserInteractor mSelectedUserInteractor; private final DockManagerFake mDockManager = new DockManagerFake(); private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); @@ -90,7 +93,7 @@ public class FalsingCollectorImplTest extends SysuiTestCase { mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor, mStatusBarStateController, mKeyguardStateController, mShadeExpansionStateManager, mBatteryController, - mDockManager, mFakeExecutor, mFakeSystemClock); + mDockManager, mFakeExecutor, mFakeSystemClock, () -> mSelectedUserInteractor); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java index 15e5e1c7b8db..80b281e458d0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java +++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java @@ -37,6 +37,7 @@ import com.android.internal.colorextraction.types.Tonal; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import org.junit.Before; import org.junit.Test; @@ -63,6 +64,8 @@ public class SysuiColorExtractorTests extends SysuiTestCase { private WallpaperManager mWallpaperManager; @Mock private DumpManager mDumpManager; + @Mock + private SelectedUserInteractor mSelectedUserInteractor; private ColorExtractor.GradientColors mColors; private SysuiColorExtractor mColorExtractor; @@ -83,7 +86,8 @@ public class SysuiColorExtractorTests extends SysuiTestCase { mock(ConfigurationController.class), mWallpaperManager, mDumpManager, - true /* immediately */); + true /* immediately */, + () -> mSelectedUserInteractor); } @Test @@ -125,7 +129,8 @@ public class SysuiColorExtractorTests extends SysuiTestCase { configurationController, mWallpaperManager, mDumpManager, - true /* immediately */); + true /* immediately */, + () -> mSelectedUserInteractor); verify(configurationController).addCallback(eq(sysuiColorExtractor)); reset(tonal); diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 3df9cbb29e4a..91409a376556 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -11,11 +11,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.communal.data.model.CommunalWidgetMetadata +import com.android.systemui.communal.shared.CommunalContentSize import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.kotlinArgumentCaptor @@ -59,9 +62,12 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo + private lateinit var communalRepository: FakeCommunalRepository + private lateinit var logBuffer: LogBuffer private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) @Before @@ -71,6 +77,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { logBuffer = FakeLogBuffer.Factory.create() featureFlagEnabled(true) + communalRepository = FakeCommunalRepository() + communalRepository.setIsCommunalEnabled(true) + + overrideResource( + R.array.config_communalWidgetAllowlist, + arrayOf(componentName1, componentName2) + ) + whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch") whenever(userTracker.userHandle).thenReturn(userHandle) } @@ -219,11 +233,36 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { Mockito.verify(appWidgetHost).stopListening() } + @Test + fun getCommunalWidgetAllowList_onInit() { + testScope.runTest { + val repository = initCommunalWidgetRepository() + val communalWidgetAllowlist = repository.communalWidgetAllowlist + assertThat( + listOf( + CommunalWidgetMetadata( + componentName = componentName1, + priority = 2, + sizes = listOf(CommunalContentSize.HALF) + ), + CommunalWidgetMetadata( + componentName = componentName2, + priority = 1, + sizes = listOf(CommunalContentSize.HALF) + ) + ) + ) + .containsExactly(*communalWidgetAllowlist.toTypedArray()) + } + } + private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl { return CommunalWidgetRepositoryImpl( + context, appWidgetManager, appWidgetHost, broadcastDispatcher, + communalRepository, packageManager, userManager, userTracker, @@ -282,4 +321,9 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { private fun installedProviders(providers: List<AppWidgetProviderInfo>) { whenever(appWidgetManager.installedProviders).thenReturn(providers) } + + companion object { + const val componentName1 = "component name 1" + const val componentName2 = "component name 2" + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt new file mode 100644 index 000000000000..9b8e581d1ba4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt @@ -0,0 +1,195 @@ +/* + * 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.deviceentry.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.logging.BiometricUnlockLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor +import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.statusbar.phone.ScreenOffAnimationController +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceEntryHapticsInteractorTest : SysuiTestCase() { + + private lateinit var repository: DeviceEntryHapticsRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + private lateinit var keyEventRepository: FakeKeyEventRepository + private lateinit var powerRepository: FakePowerRepository + private lateinit var systemClock: FakeSystemClock + private lateinit var underTest: DeviceEntryHapticsInteractor + + @Before + fun setUp() { + repository = DeviceEntryHapticsRepositoryImpl() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + keyEventRepository = FakeKeyEventRepository() + powerRepository = FakePowerRepository() + systemClock = FakeSystemClock() + underTest = + DeviceEntryHapticsInteractor( + repository = repository, + fingerprintPropertyRepository = fingerprintPropertyRepository, + biometricSettingsRepository = biometricSettingsRepository, + keyEventInteractor = KeyEventInteractor(keyEventRepository), + powerInteractor = + PowerInteractor( + powerRepository, + mock(FalsingCollector::class.java), + mock(ScreenOffAnimationController::class.java), + mock(StatusBarStateController::class.java), + ), + systemClock = systemClock, + logger = mock(BiometricUnlockLogger::class.java), + ) + } + + @Test + fun nonPowerButtonFPS_vibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isTrue() + } + + @Test + fun powerButtonFPS_vibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(false) + + // It's been 10 seconds since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000) + + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isTrue() + } + + @Test + fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN + + // It's been 10 seconds since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000) + + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isFalse() + } + + @Test + fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(false) + + // It's only been 50ms since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50) + + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isFalse() + } + + @Test + fun nonPowerButtonFPS_vibrateError() = runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) + underTest.vibrateError() + assertThat(playErrorHaptic).isTrue() + } + + @Test + fun powerButtonFPS_vibrateError() = runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + underTest.vibrateError() + assertThat(playErrorHaptic).isTrue() + } + + @Test + fun powerButtonFPS_powerDown_doNotVibrateError() = runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(true) + underTest.vibrateError() + assertThat(playErrorHaptic).isFalse() + } + + private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) { + fingerprintPropertyRepository.setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = fingerprintSensorType, + sensorLocations = mapOf(), + ) + } + + private fun setPowerButtonFingerprintProperty() { + setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON) + } + + private fun setFingerprintEnrolled() { + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + } + + private fun setAwakeFromPowerButton() { + powerRepository.updateWakefulness( + WakefulnessState.AWAKE, + WakeSleepReason.POWER_BUTTON, + WakeSleepReason.POWER_BUTTON, + powerButtonLaunchGestureTriggered = false, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java index 7e1edd2ac193..ba578a39b558 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java @@ -51,6 +51,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.wakelock.WakeLockFake; import com.android.systemui.utils.os.FakeHandler; @@ -85,6 +86,8 @@ public class DozeScreenStateTest extends SysuiTestCase { private DozeLog mDozeLog; @Mock private DozeScreenBrightness mDozeScreenBrightness; + @Mock + private SelectedUserInteractor mSelectedUserInteractor; @Before public void setUp() throws Exception { @@ -100,7 +103,7 @@ public class DozeScreenStateTest extends SysuiTestCase { mWakeLock = new WakeLockFake(); mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters, mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog, - mDozeScreenBrightness); + mDozeScreenBrightness, mSelectedUserInteractor); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java index a88a8e590e76..3cc04511d57b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java @@ -52,9 +52,9 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.doze.DozeSensors.TriggerSensor; import com.android.systemui.plugins.SensorManagerPlugin; -import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.policy.DevicePostureController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.settings.FakeSettings; @@ -102,7 +102,7 @@ public class DozeSensorsTest extends SysuiTestCase { @Mock private DevicePostureController mDevicePostureController; @Mock - private UserTracker mUserTracker; + private SelectedUserInteractor mSelectedUserInteractor; @Mock private ProximitySensor mProximitySensor; @@ -122,7 +122,8 @@ public class DozeSensorsTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); - when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser()); + when(mSelectedUserInteractor.getSelectedUserId()) + .thenReturn(ActivityManager.getCurrentUser()); when(mAmbientDisplayConfiguration.tapSensorTypeMapping()) .thenReturn(new String[]{"tapSensor"}); when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L); @@ -435,7 +436,7 @@ public class DozeSensorsTest extends SysuiTestCase { DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters, mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog, mProximitySensor, mFakeSettings, mAuthController, - mDevicePostureController, mUserTracker); + mDevicePostureController, mSelectedUserInteractor); for (TriggerSensor sensor : dozeSensors.mTriggerSensors) { assertFalse(sensor.mIgnoresSetting); @@ -480,7 +481,7 @@ public class DozeSensorsTest extends SysuiTestCase { DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters, mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog, mProximitySensor, mFakeSettings, mAuthController, - mDevicePostureController, mUserTracker); + mDevicePostureController, mSelectedUserInteractor); for (TriggerSensor sensor : dozeSensors.mTriggerSensors) { // THEN lift to wake's TriggerSensor enabledBySettings is false @@ -499,7 +500,7 @@ public class DozeSensorsTest extends SysuiTestCase { DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters, mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog, mProximitySensor, mFakeSettings, mAuthController, - mDevicePostureController, mUserTracker); + mDevicePostureController, mSelectedUserInteractor); for (TriggerSensor sensor : dozeSensors.mTriggerSensors) { // THEN lift to wake's TriggerSensor enabledBySettings is true @@ -514,7 +515,7 @@ public class DozeSensorsTest extends SysuiTestCase { super(mResources, mSensorManager, mDozeParameters, mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog, mProximitySensor, mFakeSettings, mAuthController, - mDevicePostureController, mUserTracker); + mDevicePostureController, mSelectedUserInteractor); for (TriggerSensor sensor : mTriggerSensors) { if (sensor instanceof PluginSensor && ((PluginSensor) sensor).mPluginSensor.getType() diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index 494e230947b3..3a6b07539a43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -53,6 +53,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeThreadFactory; import com.android.systemui.util.sensors.AsyncSensorManager; @@ -101,6 +102,8 @@ public class DozeTriggersTest extends SysuiTestCase { @Mock private UserTracker mUserTracker; @Mock + private SelectedUserInteractor mSelectedUserInteractor; + @Mock private SessionTracker mSessionTracker; private DozeTriggers mTriggers; @@ -134,7 +137,7 @@ public class DozeTriggersTest extends SysuiTestCase { asyncSensorManager, wakeLock, mDockManager, mProximitySensor, mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(), mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController, - mDevicePostureController, mUserTracker); + mDevicePostureController, mUserTracker, mSelectedUserInteractor); mTriggers.setDozeMachine(mMachine); waitForSensorManager(); } 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 2d3f69d95204..00009f778ac7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -76,6 +76,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.telephony.TelephonyListenerManager; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.settings.FakeGlobalSettings; @@ -134,6 +135,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private ShadeController mShadeController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; + @Mock private SelectedUserInteractor mSelectedUserInteractor; @Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher; @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback; @@ -186,12 +188,14 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mPackageManager, mShadeController, mKeyguardUpdateMonitor, - mDialogLaunchAnimator); + mDialogLaunchAnimator, + mSelectedUserInteractor); mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting(); ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors(); backdropColors.setMainColor(Color.BLACK); when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors); + when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(0); } @Test @@ -568,7 +572,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Test public void testOnLockScreen_disableSmartLock() { mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); - int user = KeyguardUpdateMonitor.getCurrentUser(); + int expectedUser = 100; + doReturn(expectedUser).when(mSelectedUserInteractor).getSelectedUserId(); doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); @@ -586,7 +591,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */); // Then smart lock will be disabled - verify(mLockPatternUtils).requireCredentialEntry(eq(user)); + verify(mLockPatternUtils).requireCredentialEntry(eq(expectedUser)); // hide dialog again mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */); diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt index 0ee348e0d23d..7750d25de753 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt @@ -28,6 +28,8 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import kotlin.math.max +import kotlin.test.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -149,26 +151,52 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { } @Test + fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() { + // GIVEN max velocity and a slider progress at half progress + val firstProgress = 0.5f + val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress) + + // Given a second slider progress event smaller than the progress threshold + val secondProgress = firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f) + + // GIVEN system running for 1s + clock.advanceTime(1000) + + // WHEN two calls to play occur with the required threshold separation (time and progress) + sliderHapticFeedbackProvider.onProgress(firstProgress) + clock.advanceTime(dragTextureThresholdMillis.toLong()) + sliderHapticFeedbackProvider.onProgress(secondProgress) + + // THEN Only the first compositions plays + verify(vibratorHelper, times(1)) + .vibrate(eq(firstTicks), any(VibrationAttributes::class.java)) + verify(vibratorHelper, times(1)) + .vibrate(any(VibrationEffect::class.java), any(VibrationAttributes::class.java)) + } + + @Test fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() { - // GIVEN max velocity and slider progress - val progress = 1f - val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress) - val ticks = VibrationEffect.startComposition() - repeat(config.numberOfLowTicks) { - ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale) - } + // GIVEN max velocity and a slider progress at half progress + val firstProgress = 0.5f + val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress) + + // Given a second slider progress event beyond progress threshold + val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f + val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress) // GIVEN system running for 1s clock.advanceTime(1000) - // WHEN two calls to play occur with the required threshold separation - sliderHapticFeedbackProvider.onProgress(progress) + // WHEN two calls to play occur with the required threshold separation (time and progress) + sliderHapticFeedbackProvider.onProgress(firstProgress) clock.advanceTime(dragTextureThresholdMillis.toLong()) - sliderHapticFeedbackProvider.onProgress(progress) + sliderHapticFeedbackProvider.onProgress(secondProgress) - // THEN the correct composition plays two times - verify(vibratorHelper, times(2)) - .vibrate(eq(ticks.compose()), any(VibrationAttributes::class.java)) + // THEN the correct compositions play + verify(vibratorHelper, times(1)) + .vibrate(eq(firstTicks), any(VibrationAttributes::class.java)) + verify(vibratorHelper, times(1)) + .vibrate(eq(secondTicks), any(VibrationAttributes::class.java)) } @Test @@ -229,6 +257,38 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java)) } + fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() { + // GIVEN max velocity and a slider progress at half progress + val progress = 0.5f + + // GIVEN system running for 1s + clock.advanceTime(1000) + + // WHEN a drag texture plays + sliderHapticFeedbackProvider.onProgress(progress) + + // THEN the dragTextureLastProgress remembers the latest progress + assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress) + } + + @Test + fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() { + // GIVEN max velocity and a slider progress at half progress + val progress = 0.5f + + // GIVEN system running for 1s + clock.advanceTime(1000) + + // WHEN a drag texture plays + sliderHapticFeedbackProvider.onProgress(progress) + + // WHEN the handle is released + sliderHapticFeedbackProvider.onHandleReleasedFromTouch() + + // THEN the dragTextureLastProgress tracker is reset + assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress) + } + private fun scaleAtBookends(velocity: Float): Float { val range = config.upperBookendScale - config.lowerBookendScale val interpolatedVelocity = @@ -244,4 +304,15 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { val bump = interpolatedVelocity * config.additionalVelocityMaxBump return interpolatedProgress * range + config.progressBasedDragMinScale + bump } + + private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect { + val ticks = VibrationEffect.startComposition() + repeat(config.numberOfLowTicks) { + ticks.addPrimitive( + VibrationEffect.Composition.PRIMITIVE_LOW_TICK, + scaleAtProgressChange(velocity, progress) + ) + } + return ticks.compose() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index a6468234b9b6..2b280c05ba1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -117,6 +117,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; @@ -187,6 +188,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock ShadeExpansionStateManager mShadeExpansionStateManager; private @Mock ShadeInteractor mShadeInteractor; private @Mock ShadeWindowLogger mShadeWindowLogger; + private @Mock SelectedUserInteractor mSelectedUserInteractor; private @Captor ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback; private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback> @@ -213,7 +215,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock SystemPropertiesHelper mSystemPropertiesHelper; private FakeFeatureFlags mFeatureFlags; - private int mInitialUserId; + private final int mDefaultUserId = 100; @Before public void setUp() throws Exception { @@ -233,6 +235,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { .thenReturn(mock(Flow.class)); when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded()) .thenReturn(mock(Flow.class)); + when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId); + when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl( mContext, new FakeWindowRootViewComponent.Factory(mock(WindowRootView.class)), @@ -251,9 +255,11 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mAuthController, mShadeExpansionStateManager, () -> mShadeInteractor, - mShadeWindowLogger); + mShadeWindowLogger, + () -> mSelectedUserInteractor); mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false); + mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true); DejankUtils.setImmediate(true); @@ -266,12 +272,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { }).when(mKeyguardStateController).notifyKeyguardGoingAway(anyBoolean()); createAndStartViewMediator(); - mInitialUserId = KeyguardUpdateMonitor.getCurrentUser(); - } - - @After - public void teardown() { - KeyguardUpdateMonitor.setCurrentUser(mInitialUserId); } /** @@ -451,7 +451,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.setKeyguardEnabled(false); TestableLooper.get(this).processAllMessages(); - mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId); mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); final ArgumentCaptor<Runnable> animationRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); @@ -617,8 +617,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { public void lockAfterScreenTimeoutUsesValueFromSettings() { int currentUserId = 99; int userSpecificTimeout = 5999; - KeyguardUpdateMonitor.setCurrentUser(currentUserId); + when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(currentUserId); when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false); when(mDevicePolicyManager.getMaximumTimeToLock(null, currentUserId)).thenReturn(0L); when(mSecureSettings.getIntForUser(LOCK_SCREEN_LOCK_AFTER_TIMEOUT, @@ -718,7 +718,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { startMockKeyguardExitAnimation(); assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()); - mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId); mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); TestableLooper.get(this).processAllMessages(); verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false); @@ -782,7 +782,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { // Verify keyguard told of authentication verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); - mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId); mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); final ArgumentCaptor<Runnable> animationRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); @@ -814,7 +814,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { // Verify keyguard told of authentication verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); clearInvocations(mStatusBarKeyguardViewManager); - mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId); mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); final ArgumentCaptor<Runnable> animationRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); @@ -844,7 +844,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { // Verify keyguard told of authentication verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); - mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId); mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); final ArgumentCaptor<Runnable> animationRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); @@ -1111,7 +1111,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mDispatcher, () -> mDreamingToLockscreenTransitionViewModel, mSystemPropertiesHelper, - () -> mock(WindowManagerLockscreenVisibilityManager.class)); + () -> mock(WindowManagerLockscreenVisibilityManager.class), + mSelectedUserInteractor); mViewMediator.start(); mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index b9119e10b720..ef03fdf66fd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -30,14 +30,10 @@ import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.dreams.DreamOverlayCallbackController -import com.android.systemui.keyguard.ScreenLifecycle -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.statusbar.phone.BiometricUnlockController -import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -47,7 +43,6 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -68,13 +63,10 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var screenLifecycle: ScreenLifecycle - @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var dozeTransitionListener: DozeTransitionListener @Mock private lateinit var authController: AuthController @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController - @Mock private lateinit var dozeParameters: DozeParameters private val mainDispatcher = StandardTestDispatcher() private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -89,12 +81,9 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { underTest = KeyguardRepositoryImpl( statusBarStateController, - screenLifecycle, - biometricUnlockController, keyguardStateController, keyguardUpdateMonitor, dozeTransitionListener, - dozeParameters, authController, dreamOverlayCallbackController, mainDispatcher, @@ -201,26 +190,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test - fun isAodAvailable() = runTest { - val flow = underTest.isAodAvailable - val isAodAvailable = collectLastValue(flow) - runCurrent() - - val callback = - withArgCaptor<DozeParameters.Callback> { verify(dozeParameters).addCallback(capture()) } - - whenever(dozeParameters.getAlwaysOn()).thenReturn(false) - callback.onAlwaysOnChange() - assertThat(isAodAvailable()).isEqualTo(false) - - whenever(dozeParameters.getAlwaysOn()).thenReturn(true) - callback.onAlwaysOnChange() - assertThat(isAodAvailable()).isEqualTo(true) - - flow.onCompletion { verify(dozeParameters).removeCallback(callback) } - } - - @Test fun isKeyguardOccluded() = testScope.runTest { whenever(keyguardStateController.isOccluded).thenReturn(false) @@ -386,53 +355,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test - fun biometricUnlockState() = - testScope.runTest { - val values = mutableListOf<BiometricUnlockModel>() - val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this) - - runCurrent() - val captor = argumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>() - verify(biometricUnlockController).addListener(captor.capture()) - - listOf( - BiometricUnlockController.MODE_NONE, - BiometricUnlockController.MODE_WAKE_AND_UNLOCK, - BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, - BiometricUnlockController.MODE_SHOW_BOUNCER, - BiometricUnlockController.MODE_ONLY_WAKE, - BiometricUnlockController.MODE_UNLOCK_COLLAPSING, - BiometricUnlockController.MODE_DISMISS_BOUNCER, - BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM, - ) - .forEach { - whenever(biometricUnlockController.mode).thenReturn(it) - captor.value.onModeChanged(it) - runCurrent() - } - - assertThat(values) - .isEqualTo( - listOf( - // Initial value will be NONE, followed by onModeChanged() call - BiometricUnlockModel.NONE, - BiometricUnlockModel.NONE, - BiometricUnlockModel.WAKE_AND_UNLOCK, - BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING, - BiometricUnlockModel.SHOW_BOUNCER, - BiometricUnlockModel.ONLY_WAKE, - BiometricUnlockModel.UNLOCK_COLLAPSING, - BiometricUnlockModel.DISMISS_BOUNCER, - BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM, - ) - ) - - job.cancel() - runCurrent() - verify(biometricUnlockController).removeListener(captor.value) - } - - @Test fun dozeTransitionModel() = testScope.runTest { // For the initial state diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt index 5bd747fc86e4..8dea57c2eb34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt @@ -20,10 +20,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.power.domain.interactor.PowerInteractorFactory +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.mock import dagger.Lazy import junit.framework.Assert.assertEquals @@ -42,6 +46,12 @@ import org.junit.runner.RunWith class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() { private lateinit var underTest: FromPrimaryBouncerTransitionInteractor + private val mSelectedUserInteractor = + SelectedUserInteractor( + FakeUserRepository(), + FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) } + ) + // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our // underTest interactor is provided to any classes that need it. override var fromPrimaryBouncerTransitionInteractorLazy: @@ -63,6 +73,7 @@ class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorT flags = FakeFeatureFlags(), keyguardSecurityModel = mock(), powerInteractor = PowerInteractorFactory.create().powerInteractor, + selectedUserInteractor = mSelectedUserInteractor ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt index d6e19cbcb826..e87adf5e424b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt @@ -64,8 +64,6 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { KeyguardDismissInteractorFactory.create( context = context, testScope = testScope, - broadcastDispatcher = fakeBroadcastDispatcher, - dispatcher = dispatcher, ) keyguardRepository = dismissInteractorWithDependencies.keyguardRepository transitionRepository = FakeKeyguardTransitionRepository() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt index a5cfbbfda196..ecb46bdd06c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt @@ -58,8 +58,6 @@ class KeyguardDismissInteractorTest : SysuiTestCase() { KeyguardDismissInteractorFactory.create( context = context, testScope = testScope, - broadcastDispatcher = fakeBroadcastDispatcher, - dispatcher = dispatcher, ) underTest = underTestDependencies.interactor underTestDependencies.userRepository.setUserInfos(listOf(userInfo)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 6ad2d731f375..e45f56a44b67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -62,6 +62,7 @@ import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -99,6 +100,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Before fun setup() { @@ -145,6 +147,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { keyguardUpdateMonitor, FakeTrustRepository(), testScope.backgroundScope, + mSelectedUserInteractor, ), AlternateBouncerInteractor( mock(StatusBarStateController::class.java), 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 5d5ece0f8dcb..f3930a37c486 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 @@ -42,6 +42,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.domain.model.ShadeModel +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor @@ -86,6 +87,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // Used to verify transition requests for test output @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor @@ -158,6 +160,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { flags = featureFlags, keyguardSecurityModel = keyguardSecurityModel, powerInteractor = powerInteractor, + selectedUserInteractor = mSelectedUserInteractor, ) .apply { start() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt index 02c98cd9a787..f9362a773fc5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt @@ -46,6 +46,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -90,6 +91,7 @@ class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var mockedContext: Context @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Before fun setup() { @@ -145,6 +147,7 @@ class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() { keyguardUpdateMonitor, trustRepository, testScope.backgroundScope, + mSelectedUserInteractor ), AlternateBouncerInteractor( statusBarStateController = mock(), 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 34360d2ddd5c..6cdf4efd67da 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 @@ -4,7 +4,9 @@ import android.content.ComponentName import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED import com.android.systemui.SysuiTestCase +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader @@ -20,6 +22,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.never import org.mockito.Mockito.verify @RunWith(AndroidTestingRunner::class) @@ -37,10 +40,11 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { private val view: MediaProjectionAppSelectorView = mock() private val policyResolver: ScreenCaptureDevicePolicyResolver = mock() + private val logger = mock<MediaProjectionMetricsLogger>() private val thumbnailLoader = FakeThumbnailLoader() - private val controller = + private fun createController(isFirstStart: Boolean = true) = MediaProjectionAppSelectorController( taskListProvider, view, @@ -50,6 +54,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { appSelectorComponentName, callerPackageName, thumbnailLoader, + isFirstStart, + logger ) @Before @@ -61,7 +67,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { fun initNoRecentTasks_bindsEmptyList() { taskListProvider.tasks = emptyList() - controller.init() + createController().init() verify(view).bind(emptyList()) } @@ -70,7 +76,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { fun initOneRecentTask_bindsList() { taskListProvider.tasks = listOf(createRecentTask(taskId = 1)) - controller.init() + createController().init() verify(view).bind(listOf(createRecentTask(taskId = 1))) } @@ -86,7 +92,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3) } @@ -101,7 +107,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -124,7 +130,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -147,7 +153,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -172,7 +178,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -199,11 +205,29 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { taskListProvider.tasks = tasks givenCaptureAllowed(isAllow = false) - controller.init() + createController().init() verify(view).bind(emptyList()) } + @Test + fun init_firstStart_logsAppSelectorDisplayed() { + val controller = createController(isFirstStart = true) + + controller.init() + + verify(logger).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + } + + @Test + fun init_notFirstStart_doesNotLogAppSelectorDisplayed() { + val controller = createController(isFirstStart = false) + + controller.init() + + verify(logger, never()).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + } + private fun givenCaptureAllowed(isAllow: Boolean) { whenever(policyResolver.isScreenCaptureAllowed(any(), any())).thenReturn(isAllow) } @@ -216,6 +240,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ): RecentTask { return RecentTask( taskId = taskId, + displayId = 0, topActivityComponent = topActivityComponent, baseIntentComponent = ComponentName("com", "Test"), userId = userId, 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 2c7ee56e9408..d75553fe57ab 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 @@ -128,6 +128,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { private fun createRecentTask(taskId: Int): RecentTask = RecentTask( taskId = taskId, + displayId = 0, userId = 0, topActivityComponent = null, baseIntentComponent = null, 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 9b1f8303f1ec..0e6e4fa659da 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 @@ -36,7 +36,6 @@ 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 import com.android.systemui.statusbar.policy.FakeUserInfoController import com.android.systemui.statusbar.policy.FakeUserInfoController.FakeInfo @@ -130,7 +129,6 @@ class FooterActionsViewModelTest : SysuiTestCase() { val userInfoController = FakeUserInfoController(FakeInfo(picture = picture)) val settings = FakeGlobalSettings() val userId = 42 - val userTracker = FakeUserTracker(userId) val userSwitcherControllerWrapper = MockUserSwitcherControllerWrapper(currentUserName = "foo") @@ -148,7 +146,6 @@ class FooterActionsViewModelTest : SysuiTestCase() { utils.footerActionsInteractor( userSwitcherRepository = utils.userSwitcherRepository( - userTracker = userTracker, settings = settings, userManager = userManager, userInfoController = userInfoController, 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 7b13de657657..e1345d28dbd0 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 @@ -42,6 +42,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Assume.assumeTrue @@ -292,6 +293,7 @@ class SceneContainerStartableTest : SysuiTestCase() { initialSceneKey = SceneKey.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = false, + startsAwake = false ) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() @@ -299,6 +301,7 @@ class SceneContainerStartableTest : SysuiTestCase() { utils.deviceEntryRepository.setUnlocked(true) runCurrent() powerInteractor.setAwakeForTest() + runCurrent() assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -403,42 +406,43 @@ class SceneContainerStartableTest : SysuiTestCase() { initialSceneKey = SceneKey.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = false, + startsAwake = false, ) underTest.start() runCurrent() verify(falsingCollector, never()).onScreenTurningOn() verify(falsingCollector, never()).onScreenOnFromTouch() - verify(falsingCollector, never()).onScreenOff() + verify(falsingCollector, times(1)).onScreenOff() powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON) runCurrent() verify(falsingCollector, times(1)).onScreenTurningOn() verify(falsingCollector, never()).onScreenOnFromTouch() - verify(falsingCollector, never()).onScreenOff() + verify(falsingCollector, times(1)).onScreenOff() powerInteractor.setAsleepForTest() runCurrent() verify(falsingCollector, times(1)).onScreenTurningOn() verify(falsingCollector, never()).onScreenOnFromTouch() - verify(falsingCollector, times(1)).onScreenOff() + verify(falsingCollector, times(2)).onScreenOff() powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_TAP) runCurrent() verify(falsingCollector, times(1)).onScreenTurningOn() verify(falsingCollector, times(1)).onScreenOnFromTouch() - verify(falsingCollector, times(1)).onScreenOff() + verify(falsingCollector, times(2)).onScreenOff() powerInteractor.setAsleepForTest() runCurrent() verify(falsingCollector, times(1)).onScreenTurningOn() verify(falsingCollector, times(1)).onScreenOnFromTouch() - verify(falsingCollector, times(2)).onScreenOff() + verify(falsingCollector, times(3)).onScreenOff() powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON) runCurrent() verify(falsingCollector, times(2)).onScreenTurningOn() verify(falsingCollector, times(1)).onScreenOnFromTouch() - verify(falsingCollector, times(2)).onScreenOff() + verify(falsingCollector, times(3)).onScreenOff() } @Test @@ -509,11 +513,12 @@ class SceneContainerStartableTest : SysuiTestCase() { verify(falsingCollector, times(2)).onBouncerHidden() } - private fun prepareState( + private fun TestScope.prepareState( isDeviceUnlocked: Boolean = false, isBypassEnabled: Boolean = false, initialSceneKey: SceneKey? = null, authenticationMethod: AuthenticationMethodModel? = null, + startsAwake: Boolean = true, ): MutableStateFlow<ObservableTransitionState> { assumeTrue(Flags.SCENE_CONTAINER_ENABLED) sceneContainerFlags.enabled = true @@ -537,6 +542,13 @@ class SceneContainerStartableTest : SysuiTestCase() { authenticationMethod != AuthenticationMethodModel.None ) } + if (startsAwake) { + powerInteractor.setAwakeForTest() + } else { + powerInteractor.setAsleepForTest() + } + runCurrent() + return transitionStateFlow } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index 6e6833dc0944..90d2e78a411f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -17,8 +17,10 @@ package com.android.systemui.screenrecord; import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -37,6 +39,8 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; +import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; import com.android.systemui.plugins.ActivityStarter; @@ -76,6 +80,8 @@ public class RecordingControllerTest extends SysuiTestCase { private ActivityStarter mActivityStarter; @Mock private UserTracker mUserTracker; + @Mock + private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private FakeFeatureFlags mFeatureFlags; private RecordingController mController; @@ -86,8 +92,15 @@ public class RecordingControllerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mFeatureFlags = new FakeFeatureFlags(); - mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext, - mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker); + mController = new RecordingController( + mMainExecutor, + mBroadcastDispatcher, + mContext, + mFeatureFlags, + mUserContextProvider, + () -> mDevicePolicyResolver, + mUserTracker, + mMediaProjectionMetricsLogger); mController.addCallback(mCallback); } @@ -269,4 +282,21 @@ public class RecordingControllerTest extends SysuiTestCase { assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class); } + + @Test + public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); + when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); + + mController.createScreenRecordDialog(mContext, mFeatureFlags, + mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); + + verify(mMediaProjectionMetricsLogger) + .notifyProjectionInitiated(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); + } } 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..be82bc314277 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,8 @@ 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.domain.interactor.SelectedUserInteractor; +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor; import com.google.common.util.concurrent.MoreExecutors; @@ -131,6 +132,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private AuthController mAuthController; @Mock private ShadeExpansionStateManager mShadeExpansionStateManager; @Mock private ShadeWindowLogger mShadeWindowLogger; + @Mock private SelectedUserInteractor mSelectedUserInteractor; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener; private final Executor mBackgroundExecutor = MoreExecutors.directExecutor(); @@ -216,6 +218,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { keyguardInteractor, featureFlags, mKeyguardSecurityModel, + mSelectedUserInteractor, powerInteractor); mShadeInteractor = @@ -230,7 +233,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mock(UserInteractor.class), + mock(UserSwitcherInteractor.class), new SharedNotificationContainerInteractor( configurationRepository, mContext, @@ -255,7 +258,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mAuthController, mShadeExpansionStateManager, () -> mShadeInteractor, - mShadeWindowLogger) { + mShadeWindowLogger, + () -> mSelectedUserInteractor) { @Override protected boolean isDebuggable() { return false; 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 677d9db0b97c..4f3216da7370 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -81,6 +81,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -142,6 +143,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { Optional<UnfoldTransitionProgressProvider> @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock lateinit var dragDownHelper: DragDownHelper + @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel @Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler @@ -200,8 +202,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { centralSurfaces, dozeServiceHost, dozeScrimController, - backActionInteractor, - powerInteractor, notificationShadeWindowController, unfoldTransitionProgressProvider, keyguardUnlockAnimationController, @@ -243,6 +243,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { mock(KeyguardUpdateMonitor::class.java), FakeTrustRepository(), testScope.backgroundScope, + mSelectedUserInteractor, ), facePropertyRepository = FakeFacePropertyRepository(), deviceEntryFingerprintAuthRepository = @@ -254,6 +255,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { sysUIKeyEventHandler, primaryBouncerInteractor, alternateBouncerInteractor, + mSelectedUserInteractor, ) underTest.setupExpandedStatusBar() underTest.setDragDownHelper(dragDownHelper) 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 a4a2ca0a6a21..4d3eab45d001 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -79,6 +79,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -144,6 +145,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock private lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel @@ -202,8 +204,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { centralSurfaces, dozeServiceHost, dozeScrimController, - backActionInteractor, - powerInteractor, notificationShadeWindowController, unfoldTransitionProgressProvider, keyguardUnlockAnimationController, @@ -245,6 +245,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { Mockito.mock(KeyguardUpdateMonitor::class.java), FakeTrustRepository(), testScope.backgroundScope, + mSelectedUserInteractor, ), facePropertyRepository = FakeFacePropertyRepository(), deviceEntryFingerprintAuthRepository = @@ -256,6 +257,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { Mockito.mock(SysUIKeyEventHandler::class.java), primaryBouncerInteractor, alternateBouncerInteractor, + mSelectedUserInteractor, ) controller.setupExpandedStatusBar() 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..0fcfaf960737 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,17 @@ 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.domain.interactor.SelectedUserInteractor; +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor; 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 +164,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { @Mock protected DumpManager mDumpManager; @Mock protected UiEventLogger mUiEventLogger; @Mock protected CastController mCastController; - @Mock protected UserInteractor mUserInteractor; + @Mock protected UserSwitcherInteractor mUserSwitcherInteractor; + @Mock protected SelectedUserInteractor mSelectedUserInteractor; protected FakeDisableFlagsRepository mDisableFlagsRepository = new FakeDisableFlagsRepository(); protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); @@ -249,6 +252,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { keyguardInteractor, featureFlags, mock(KeyguardSecurityModel.class), + mSelectedUserInteractor, powerInteractor); ResourcesSplitShadeStateController splitShadeStateController = @@ -266,7 +270,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mUserInteractor, + mUserSwitcherInteractor, new SharedNotificationContainerInteractor( configurationRepository, mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java index 5ca45f3df472..7cb6d931ea8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java @@ -398,6 +398,12 @@ public class QuickSettingsControllerTest extends QuickSettingsControllerBaseTest .isEqualTo(mQsController.getScrimCornerRadius()); } + @Test + public void disallowTouches_nullQs_false() { + mQsController.setQs(null); + assertThat(mQsController.disallowTouches()).isFalse(); + } + private void lockScreen() { mQsController.setBarState(KEYGUARD); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt deleted file mode 100644 index e4da53a1a0a4..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar - -import com.google.common.truth.Truth.assertThat - -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Point -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -private const val WIDTH = 200 -private const val HEIGHT = 200 - -@RunWith(AndroidTestingRunner::class) -@SmallTest -class MediaArtworkProcessorTest : SysuiTestCase() { - - private var screenWidth = 0 - private var screenHeight = 0 - - private lateinit var processor: MediaArtworkProcessor - - @Before - fun setUp() { - processor = MediaArtworkProcessor() - - val point = Point() - checkNotNull(context.display).getSize(point) - screenWidth = point.x - screenHeight = point.y - } - - @After - fun tearDown() { - processor.clearCache() - } - - @Test - fun testProcessArtwork() { - // GIVEN some "artwork", which is just a solid blue image - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) - Canvas(artwork).drawColor(Color.BLUE) - // WHEN the background is created from the artwork - val background = processor.processArtwork(context, artwork)!! - // THEN the background has the size of the screen that has been downsamples - assertThat(background.height).isLessThan(screenHeight) - assertThat(background.width).isLessThan(screenWidth) - assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) - } - - @Test - fun testCache() { - // GIVEN a solid blue image - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) - Canvas(artwork).drawColor(Color.BLUE) - // WHEN the background is processed twice - val background1 = processor.processArtwork(context, artwork)!! - val background2 = processor.processArtwork(context, artwork)!! - // THEN the two bitmaps are the same - // Note: This is currently broken and trying to use caching causes issues - assertThat(background1).isNotSameInstanceAs(background2) - } - - @Test - fun testConfig() { - // GIVEN some which is not ARGB_8888 - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ALPHA_8) - Canvas(artwork).drawColor(Color.BLUE) - // WHEN the background is created from the artwork - val background = processor.processArtwork(context, artwork)!! - // THEN the background has Config ARGB_8888 - assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) - } - - @Test - fun testRecycledArtwork() { - // GIVEN some "artwork", which is just a solid blue image - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) - Canvas(artwork).drawColor(Color.BLUE) - // AND the artwork is recycled - artwork.recycle() - // WHEN the background is created from the artwork - val background = processor.processArtwork(context, artwork) - // THEN the processed bitmap is null - assertThat(background).isNull() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt index 9d6ea857fefc..cfcf4257ce28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt @@ -48,9 +48,7 @@ class NotificationMediaManagerTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - doCallRealMethod() - .whenever(notificationMediaManager) - .updateMediaMetaData(anyBoolean(), anyBoolean()) + doCallRealMethod().whenever(notificationMediaManager).updateMediaMetaData(anyBoolean()) doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView } @@ -62,7 +60,7 @@ class NotificationMediaManagerTest : SysuiTestCase() { notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true for (metaDataChanged in listOf(true, false)) { for (allowEnterAnimation in listOf(true, false)) { - notificationMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation) + notificationMediaManager.updateMediaMetaData(metaDataChanged) verify(notificationMediaManager, never()).mediaMetadata } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java deleted file mode 100644 index aeb5b037be0c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java +++ /dev/null @@ -1,87 +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.systemui.statusbar.notification; - -import static com.google.common.truth.Truth.assertThat; - -import android.annotation.Nullable; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.test.suitebuilder.annotation.SmallTest; - -import androidx.palette.graphics.Palette; -import androidx.test.runner.AndroidJUnit4; - -import com.android.systemui.SysuiTestCase; - -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class MediaNotificationProcessorTest extends SysuiTestCase { - - private static final int BITMAP_WIDTH = 10; - private static final int BITMAP_HEIGHT = 10; - - /** - * Color tolerance is borrowed from the AndroidX test utilities for Palette. - */ - private static final int COLOR_TOLERANCE = 8; - - @Nullable private Bitmap mArtwork; - - @After - public void tearDown() { - if (mArtwork != null) { - mArtwork.recycle(); - mArtwork = null; - } - } - - @Test - public void findBackgroundSwatch_white() { - // Given artwork that is completely white. - mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(mArtwork); - canvas.drawColor(Color.WHITE); - // WHEN the background swatch is computed - Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork); - // THEN the swatch color is white - assertCloseColors(swatch.getRgb(), Color.WHITE); - } - - @Test - public void findBackgroundSwatch_red() { - // Given artwork that is completely red. - mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(mArtwork); - canvas.drawColor(Color.RED); - // WHEN the background swatch is computed - Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork); - // THEN the swatch color is red - assertCloseColors(swatch.getRgb(), Color.RED); - } - - static void assertCloseColors(int expected, int actual) { - assertThat((float) Color.red(expected)).isWithin(COLOR_TOLERANCE).of(Color.red(actual)); - assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual)); - assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual)); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt index 27be4c802aec..df547ae5883e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock @@ -53,6 +54,7 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock() val statusBarStateController: StatusBarStateController = mock() val keyguardStateController: KeyguardStateController = mock() + val mSelectedUserInteractor: SelectedUserInteractor = mock() val coordinator: SensitiveContentCoordinator = DaggerTestSensitiveContentCoordinatorComponent @@ -62,7 +64,8 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { lockscreenUserManager, keyguardUpdateMonitor, statusBarStateController, - keyguardStateController) + keyguardStateController, + mSelectedUserInteractor) .coordinator @Test @@ -263,7 +266,8 @@ interface TestSensitiveContentCoordinatorComponent { @BindsInstance lockscreenUserManager: NotificationLockscreenUserManager, @BindsInstance keyguardUpdateMonitor: KeyguardUpdateMonitor, @BindsInstance statusBarStateController: StatusBarStateController, - @BindsInstance keyguardStateController: KeyguardStateController + @BindsInstance keyguardStateController: KeyguardStateController, + @BindsInstance selectedUserInteractor: SelectedUserInteractor, ): TestSensitiveContentCoordinatorComponent } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index ac11ff29b83a..0a7dc4e05633 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -192,10 +192,26 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { val isOnLockscreen by collectLastValue(underTest.isOnLockscreen) keyguardTransitionRepository.sendTransitionStep( - TransitionStep(to = KeyguardState.GONE, transitionState = TransitionState.FINISHED) + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + value = 1f, + transitionState = TransitionState.FINISHED + ) ) assertThat(isOnLockscreen).isFalse() + // While progressing from lockscreen, should still be true + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + value = 0.8f, + transitionState = TransitionState.RUNNING + ) + ) + assertThat(isOnLockscreen).isTrue() + keyguardTransitionRepository.sendTransitionStep( TransitionStep( to = KeyguardState.LOCKSCREEN, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 700de5305778..cfd220b45f1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -18,7 +18,9 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; + 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; @@ -38,7 +40,6 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableResources; -import android.view.HapticFeedbackConstants; import android.view.ViewRootImpl; import com.android.internal.logging.MetricsLogger; @@ -47,17 +48,19 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.logging.BiometricUnlockLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; @@ -107,8 +110,6 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock - private ScreenLifecycle mScreenLifecycle; - @Mock private StatusBarStateController mStatusBarStateController; @Mock private SessionTracker mSessionTracker; @@ -122,6 +123,12 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private BiometricUnlockLogger mLogger; @Mock private ViewRootImpl mViewRootImpl; + @Mock + private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor; + @Mock + private SelectedUserInteractor mSelectedUserInteractor; + @Mock + private BiometricUnlockInteractor mBiometricUnlockInteractor; private final FakeSystemClock mSystemClock = new FakeSystemClock(); private FakeFeatureFlags mFeatureFlags; private BiometricUnlockController mBiometricUnlockController; @@ -158,7 +165,10 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mAuthController, mStatusBarStateController, mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper, mSystemClock, - mFeatureFlags + mFeatureFlags, + mDeviceEntryHapticsInteractor, + () -> mSelectedUserInteractor, + mBiometricUnlockInteractor ); biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); biometricUnlockController.addListener(mBiometricUnlockEventsListener); @@ -462,145 +472,23 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test - public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() { - // GIVEN side fingerprint enrolled, last wake reason was power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - - // GIVEN last wake time just occurred - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN DO NOT vibrate the device - verify(mVibratorHelper, never()).vibrateAuthSuccess(anyString()); - } - - @Test - public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() { - // GIVEN side fingerprint enrolled, last wake reason was power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - - // GIVEN last wake time was 500ms ago - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - mSystemClock.advanceTime(500); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN vibrate the device - verify(mVibratorHelper).vibrateAuthSuccess(anyString()); - } - - @Test - public void onSideFingerprintSuccess_oldPowerButtonPress_playOneWayHaptic() { - // GIVEN oneway haptics is enabled - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - // GIVEN side fingerprint enrolled, last wake reason was power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - - // GIVEN last wake time was 500ms ago - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - mSystemClock.advanceTime(500); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN vibrate the device - verify(mVibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.CONFIRM) - ); - } - - @Test - public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() { - // GIVEN side fingerprint enrolled, wakeup just happened - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // GIVEN last wake reason was from a gesture - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_GESTURE); - + public void onFingerprintSuccess_requestSuccessHaptic() { // WHEN biometric fingerprint succeeds givenFingerprintModeUnlockCollapsing(); mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true); - // THEN vibrate the device - verify(mVibratorHelper).vibrateAuthSuccess(anyString()); - } - - @Test - public void onSideFingerprintSuccess_recentGestureWakeUp_playOnewayHaptic() { - //GIVEN oneway haptics is enabled - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - // GIVEN side fingerprint enrolled, wakeup just happened - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // GIVEN last wake reason was from a gesture - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_GESTURE); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN vibrate the device - verify(mVibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.CONFIRM) - ); - } - - @Test - public void onSideFingerprintFail_alwaysPlaysHaptic() { - // GIVEN side fingerprint enrolled, last wake reason was recent power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // WHEN biometric fingerprint fails - mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); - // THEN always vibrate the device - verify(mVibratorHelper).vibrateAuthError(anyString()); + verify(mDeviceEntryHapticsInteractor).vibrateSuccess(); } @Test - public void onSideFingerprintFail_alwaysPlaysOneWayHaptic() { - // GIVEN oneway haptics is enabled - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - // GIVEN side fingerprint enrolled, last wake reason was recent power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - + public void onFingerprintFail_requestErrorHaptic() { // WHEN biometric fingerprint fails mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); // THEN always vibrate the device - verify(mVibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.REJECT) - ); + verify(mDeviceEntryHapticsInteractor).vibrateError(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java index f5b7ca804fbc..6fecbb08f040 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java @@ -23,7 +23,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -44,6 +43,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.doze.DozeScreenState; import com.android.systemui.dump.DumpManager; +import com.android.systemui.keyguard.domain.interactor.DozeInteractor; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.BatteryController; @@ -85,6 +85,7 @@ public class DozeParametersTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private ConfigurationController mConfigurationController; @Mock private UserTracker mUserTracker; + @Mock private DozeInteractor mDozeInteractor; @Captor private ArgumentCaptor<BatteryStateChangeCallback> mBatteryStateChangeCallback; /** @@ -128,7 +129,8 @@ public class DozeParametersTest extends SysuiTestCase { mKeyguardUpdateMonitor, mConfigurationController, mStatusBarStateController, - mUserTracker + mUserTracker, + mDozeInteractor ); verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture()); @@ -186,9 +188,7 @@ public class DozeParametersTest extends SysuiTestCase { @Test public void testGetAlwaysOn_whenBatterySaverCallback() { - DozeParameters.Callback callback = mock(DozeParameters.Callback.class); - mDozeParameters.addCallback(callback); - + reset(mDozeInteractor); when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); when(mBatteryController.isAodPowerSave()).thenReturn(true); @@ -196,16 +196,16 @@ public class DozeParametersTest extends SysuiTestCase { mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1"); mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true); - verify(callback, times(2)).onAlwaysOnChange(); + verify(mDozeInteractor, times(2)).setAodAvailable(anyBoolean()); verify(mScreenOffAnimationController, times(2)).onAlwaysOnChanged(false); assertThat(mDozeParameters.getAlwaysOn()).isFalse(); reset(mScreenOffAnimationController); - reset(callback); + reset(mDozeInteractor); when(mBatteryController.isAodPowerSave()).thenReturn(false); mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true); - verify(callback).onAlwaysOnChange(); + verify(mDozeInteractor).setAodAvailable(anyBoolean()); verify(mScreenOffAnimationController).onAlwaysOnChanged(true); assertThat(mDozeParameters.getAlwaysOn()).isTrue(); } 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 45e9224aa253..46b3996c4337 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 @@ -93,6 +93,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.unfold.SysUIUnfoldComponent; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.google.common.truth.Truth; @@ -147,6 +148,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private WindowInsetsController mWindowInsetsController; @Mock private TaskbarDelegate mTaskbarDelegate; @Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback; + @Mock private SelectedUserInteractor mSelectedUserInteractor; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback @@ -212,7 +214,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mock(KeyguardTransitionInteractor.class), StandardTestDispatcher(null, null), () -> mock(WindowManagerLockscreenVisibilityInteractor.class), - () -> mock(KeyguardDismissActionInteractor.class)) { + () -> mock(KeyguardDismissActionInteractor.class), + mSelectedUserInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -715,7 +718,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mock(KeyguardTransitionInteractor.class), StandardTestDispatcher(null, null), () -> mock(WindowManagerLockscreenVisibilityInteractor.class), - () -> mock(KeyguardDismissActionInteractor.class)) { + () -> mock(KeyguardDismissActionInteractor.class), + mSelectedUserInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index c2f56654e00a..31263627213d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -201,11 +201,11 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.isWifiDefault) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest).isTrue() @@ -229,11 +229,11 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.isWifiDefault) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(false) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest).isFalse() @@ -526,13 +526,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) whenever(this.subscriptionId).thenReturn(567) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() @@ -546,11 +547,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) whenever(wifiManager.maxSignalLevel).thenReturn(5) getCallback().onWifiEntriesChanged() @@ -566,12 +568,13 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() @@ -628,11 +631,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) getCallback().onWifiEntriesChanged() assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) @@ -717,12 +721,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() @@ -730,6 +736,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { // WHEN we lose our current network whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null) getCallback().onWifiEntriesChanged() // THEN we update to no network @@ -767,6 +774,56 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } @Test + fun wifiNetwork_carrierMerged_default_usesCarrierMergedInfo() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val mergedEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + whenever(this.isDefaultNetwork).thenReturn(true) + } + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(1) + whenever(this.title).thenReturn(TITLE) + } + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + } + + @Test + fun wifiNetwork_carrierMerged_notDefault_usesConnectedInfo() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val mergedEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + whenever(this.isDefaultNetwork).thenReturn(false) + } + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(1) + whenever(this.title).thenReturn(TITLE) + } + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.Active).isTrue() + } + + @Test fun secondaryNetworks_activeEntriesEmpty_isEmpty() = testScope.runTest { featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java index cae892fc2213..e6b09e36cae9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java @@ -38,6 +38,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import dagger.Lazy; @@ -67,6 +68,8 @@ public class KeyguardStateControllerTest extends SysuiTestCase { @Mock private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; @Mock + private SelectedUserInteractor mSelectedUserInteractor; + @Mock private KeyguardUpdateMonitorLogger mLogger; @Mock private FeatureFlags mFeatureFlags; @@ -84,7 +87,8 @@ public class KeyguardStateControllerTest extends SysuiTestCase { mKeyguardUnlockAnimationControllerLazy, mLogger, mDumpManager, - mFeatureFlags); + mFeatureFlags, + mSelectedUserInteractor); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt new file mode 100644 index 000000000000..60fe7d2248e4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt @@ -0,0 +1,50 @@ +package com.android.systemui.user.domain.interactor + +import android.content.pm.UserInfo +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.user.data.repository.FakeUserRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class SelectedUserInteractorTest : SysuiTestCase() { + + private lateinit var underTest: SelectedUserInteractor + + private val userRepository = FakeUserRepository() + + @Before + fun setUp() { + userRepository.setUserInfos(USER_INFOS) + underTest = + SelectedUserInteractor( + userRepository, + FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) } + ) + } + + @Test + fun getSelectedUserIdReturnsId() { + runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) } + + val actualId = underTest.getSelectedUserId() + + assertThat(actualId).isEqualTo(USER_INFOS[0].id) + } + + companion object { + private val USER_INFOS = + listOf( + UserInfo(100, "First user", 0), + UserInfo(101, "Second user", 0), + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt index c56266dde752..1968d75a7964 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt @@ -88,7 +88,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) -class UserInteractorTest : SysuiTestCase() { +class UserSwitcherInteractorTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var manager: UserManager @@ -102,7 +102,7 @@ class UserInteractorTest : SysuiTestCase() { @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - private lateinit var underTest: UserInteractor + private lateinit var underTest: UserSwitcherInteractor private lateinit var spyContext: Context private lateinit var testScope: TestScope @@ -665,8 +665,8 @@ class UserInteractorTest : SysuiTestCase() { userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - val callback1: UserInteractor.UserCallback = mock() - val callback2: UserInteractor.UserCallback = mock() + val callback1: UserSwitcherInteractor.UserCallback = mock() + val callback2: UserSwitcherInteractor.UserCallback = mock() underTest.addCallback(callback1) underTest.addCallback(callback2) runCurrent() @@ -1117,7 +1117,7 @@ class UserInteractorTest : SysuiTestCase() { userRepository.setSelectedUserInfo(userInfo) } underTest = - UserInteractor( + UserSwitcherInteractor( applicationContext = spyContext, repository = userRepository, activityStarter = activityStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt index a8db368d4150..7041eab9d247 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -42,7 +42,7 @@ import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.domain.interactor.GuestUserInteractor import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode import com.android.systemui.user.domain.interactor.RefreshUsersScheduler -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -243,9 +243,8 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { userRepository.setSelectedUserInfo(USER_0) } return StatusBarUserChipViewModel( - context = context, interactor = - UserInteractor( + UserSwitcherInteractor( applicationContext = context, repository = userRepository, activityStarter = activityStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index c236b12d723f..686f492fde50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -42,7 +42,7 @@ import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.domain.interactor.GuestUserInteractor import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode import com.android.systemui.user.domain.interactor.RefreshUsersScheduler -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper import com.android.systemui.user.shared.model.UserActionModel import com.android.systemui.util.mockito.any @@ -157,8 +157,8 @@ class UserSwitcherViewModelTest : SysuiTestCase() { underTest = UserSwitcherViewModel( - userInteractor = - UserInteractor( + userSwitcherInteractor = + UserSwitcherInteractor( applicationContext = context, repository = userRepository, activityStarter = activityStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java index a853f1d84176..c69f5c8198eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java @@ -209,6 +209,11 @@ public class EventsTest extends SysuiTestCase { new int[]{MetricsEvent.POWER_OVERHEAT_ALARM, MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM}, Events.VolumeDialogEvent.USB_OVERHEAT_ALARM_DISMISSED}, + {Events.EVENT_ODI_CAPTIONS_CLICK, null, "writeEvent odi_captions_click", null, + Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED}, + {Events.EVENT_ODI_CAPTIONS_TOOLTIP_CLICK, null, + "writeEvent odi_captions_tooltip_click", null, + Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED} }); } } 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 b8f747b8e961..c4c7472ba39c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -53,11 +53,12 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; +import androidx.test.core.view.MotionEventBuilder; import androidx.test.filters.SmallTest; import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.Prefs; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.dump.DumpManager; @@ -66,6 +67,7 @@ import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; @@ -76,6 +78,8 @@ import com.android.systemui.util.settings.SecureSettings; import dagger.Lazy; +import junit.framework.Assert; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -699,6 +703,60 @@ public class VolumeDialogImplTest extends SysuiTestCase { } } + /** + * The click should be a single tap, thus we inject a down and an up event. + */ + @Test + public void clickCaptionsButton_logsUiEvent() { + UiEventLoggerFake logger = new UiEventLoggerFake(); + Events.sUiEventLogger = logger; + MotionEvent down = MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_DOWN).build(); + MotionEvent up = MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_UP).build(); + + mODICaptionsIcon.onTouchEvent(down); + mODICaptionsIcon.onTouchEvent(up); + mTestableLooper.moveTimeForward(300); // to confirm it was only a single tap + mTestableLooper.processAllMessages(); + + boolean foundCaptionLog = false; + for (UiEventLoggerFake.FakeUiEvent event : logger.getLogs()) { + if (event.eventId + == Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED.getId()) { + foundCaptionLog = true; + break; + } + } + Assert.assertTrue("Did not log the captions button click.", foundCaptionLog); + } + + /** + * Pressing the small x button at top right dismisses the captions tooltip. + */ + @Test + public void dismissCaptionsTooltip_logsUiEvent() { + UiEventLoggerFake logger = new UiEventLoggerFake(); + Events.sUiEventLogger = logger; + mDialog.showCaptionsTooltip(); + assumeNotNull(mDialog.mODICaptionsTooltipView); + View dismissButton = mDialog.mODICaptionsTooltipView.findViewById(R.id.dismiss); + + dismissButton.performClick(); + + boolean foundCaptionLog = false; + for (UiEventLoggerFake.FakeUiEvent event : logger.getLogs()) { + if (event.eventId + == Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED.getId() + ) { + foundCaptionLog = true; + break; + } + } + Assert.assertTrue("Did not log the captions tooltip dismiss button click.", + foundCaptionLog); + } + @After public void teardown() { // Detailed logs to track down timeout issues in b/299491332 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..a42fa4129a5f 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,8 @@ 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.domain.interactor.SelectedUserInteractor; +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.Bubble; @@ -324,6 +325,8 @@ public class BubblesTest extends SysuiTestCase { @Mock private ShadeWindowLogger mShadeWindowLogger; @Mock + private SelectedUserInteractor mSelectedUserInteractor; + @Mock private NotifPipelineFlags mNotifPipelineFlags; @Mock private Icon mAppBubbleIcon; @@ -433,6 +436,7 @@ public class BubblesTest extends SysuiTestCase { keyguardInteractor, featureFlags, mock(KeyguardSecurityModel.class), + mSelectedUserInteractor, powerInteractor); ResourcesSplitShadeStateController splitShadeStateController = @@ -450,7 +454,7 @@ public class BubblesTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mock(UserInteractor.class), + mock(UserSwitcherInteractor.class), new SharedNotificationContainerInteractor( configurationRepository, mContext, @@ -476,7 +480,8 @@ public class BubblesTest extends SysuiTestCase { mAuthController, mShadeExpansionStateManager, () -> mShadeInteractor, - mShadeWindowLogger + mShadeWindowLogger, + () -> mSelectedUserInteractor ); mNotificationShadeWindowController.fetchWindowRootView(); mNotificationShadeWindowController.attach(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt index 1a8c5830e453..30132f7747b7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt @@ -1,5 +1,6 @@ package com.android.systemui.communal.data.repository +import com.android.systemui.communal.data.model.CommunalWidgetMetadata import com.android.systemui.communal.shared.CommunalAppWidgetInfo import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -8,6 +9,7 @@ import kotlinx.coroutines.flow.MutableStateFlow class FakeCommunalWidgetRepository : CommunalWidgetRepository { private val _stopwatchAppWidgetInfo = MutableStateFlow<CommunalAppWidgetInfo?>(null) override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo + override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList() fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) { _stopwatchAppWidgetInfo.value = appWidgetInfo diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 9de7ad8ea780..fae49b120d7d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -29,12 +29,12 @@ import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityStat import com.android.systemui.keyguard.shared.model.StatusBarState import dagger.Binds import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import javax.inject.Inject /** Fake implementation of [KeyguardRepository] */ @SysUISingleton @@ -182,8 +182,8 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _lastDozeTapToWakePosition.value = position } - fun setAodAvailable(isAodAvailable: Boolean) { - _isAodAvailable.value = isAodAvailable + override fun setAodAvailable(value: Boolean) { + _isAodAvailable.value = value } fun setDreaming(isDreaming: Boolean) { @@ -202,8 +202,8 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _dozeAmount.value = dozeAmount } - fun setBiometricUnlockState(state: BiometricUnlockModel) { - _biometricUnlockState.tryEmit(state) + override fun setBiometricUnlockState(value: BiometricUnlockModel) { + _biometricUnlockState.tryEmit(value) } fun setBiometricUnlockSource(source: BiometricUnlockSource?) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt index 8e96b522e997..fc34903e8a1d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt @@ -16,11 +16,8 @@ package com.android.systemui.keyguard.domain.interactor -import android.app.ActivityManager import android.content.Context import android.os.Handler -import android.os.UserManager -import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository @@ -28,7 +25,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.ui.BouncerView -import com.android.systemui.broadcast.FakeBroadcastDispatcher import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags @@ -36,21 +32,13 @@ import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository -import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.telephony.data.repository.FakeTelephonyRepository -import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.user.domain.interactor.GuestUserInteractor -import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode -import com.android.systemui.user.domain.interactor.RefreshUsersScheduler -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.time.FakeSystemClock -import com.android.systemui.utils.UserRestrictionChecker -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.TestScope import org.mockito.Mockito.mock @@ -64,8 +52,6 @@ object KeyguardDismissInteractorFactory { fun create( context: Context, testScope: TestScope, - broadcastDispatcher: FakeBroadcastDispatcher, - dispatcher: CoroutineDispatcher, trustRepository: FakeTrustRepository = FakeTrustRepository(), keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(), bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(), @@ -74,6 +60,7 @@ object KeyguardDismissInteractorFactory { FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, true) set(Flags.FULL_SCREEN_USER_SWITCHER, false) + set(Flags.REFACTOR_GETCURRENTUSER, true) }, powerRepository: FakePowerRepository = FakePowerRepository(), userRepository: FakeUserRepository = FakeUserRepository(), @@ -92,6 +79,7 @@ object KeyguardDismissInteractorFactory { keyguardUpdateMonitor, trustRepository, testScope.backgroundScope, + mock(SelectedUserInteractor::class.java), ) val alternateBouncerInteractor = AlternateBouncerInteractor( @@ -103,38 +91,11 @@ object KeyguardDismissInteractorFactory { keyguardUpdateMonitor, ) val powerInteractorWithDeps = - PowerInteractorFactory.create( - repository = powerRepository, - ) - val userInteractor = - UserInteractor( - applicationContext = context, - repository = userRepository, - mock(ActivityStarter::class.java), - keyguardInteractor = - KeyguardInteractorFactory.create( - repository = keyguardRepository, - bouncerRepository = bouncerRepository, - featureFlags = featureFlags, - ) - .keyguardInteractor, - featureFlags = featureFlags, - manager = mock(UserManager::class.java), - headlessSystemUserMode = mock(HeadlessSystemUserMode::class.java), - applicationScope = testScope.backgroundScope, - telephonyInteractor = - TelephonyInteractor( - repository = FakeTelephonyRepository(), - ), - broadcastDispatcher = broadcastDispatcher, - keyguardUpdateMonitor = keyguardUpdateMonitor, - backgroundDispatcher = dispatcher, - activityManager = mock(ActivityManager::class.java), - refreshUsersScheduler = mock(RefreshUsersScheduler::class.java), - guestUserInteractor = mock(GuestUserInteractor::class.java), - uiEventLogger = mock(UiEventLogger::class.java), - userRestrictionChecker = mock(UserRestrictionChecker::class.java), + PowerInteractorFactory.create( + repository = powerRepository, ) + val selectedUserInteractor = + SelectedUserInteractor(repository = userRepository, flags = featureFlags) return WithDependencies( trustRepository = trustRepository, keyguardRepository = keyguardRepository, @@ -149,7 +110,7 @@ object KeyguardDismissInteractorFactory { primaryBouncerInteractor, alternateBouncerInteractor, powerInteractorWithDeps.powerInteractor, - userInteractor, + selectedUserInteractor, ), ) } 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 911eafae5c27..cddb007222d5 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 @@ -41,17 +41,17 @@ import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorI import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.security.data.repository.SecurityRepository import com.android.systemui.security.data.repository.SecurityRepositoryImpl -import com.android.systemui.settings.FakeUserTracker -import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.FakeSecurityController import com.android.systemui.statusbar.policy.FakeUserInfoController import com.android.systemui.statusbar.policy.SecurityController import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.statusbar.policy.UserSwitcherController +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.data.repository.UserRepository 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.user.domain.interactor.UserSwitcherInteractor import com.android.systemui.util.mockito.mock import com.android.systemui.util.settings.FakeGlobalSettings import com.android.systemui.util.settings.GlobalSettings @@ -102,7 +102,7 @@ class FooterActionsTestUtils( deviceProvisionedController: DeviceProvisionedController = mock(), qsSecurityFooterUtils: QSSecurityFooterUtils = mock(), fgsManagerController: FgsManagerController = mock(), - userInteractor: UserInteractor = mock(), + userSwitcherInteractor: UserSwitcherInteractor = mock(), securityRepository: SecurityRepository = securityRepository(), foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(), userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(), @@ -116,7 +116,7 @@ class FooterActionsTestUtils( deviceProvisionedController, qsSecurityFooterUtils, fgsManagerController, - userInteractor, + userSwitcherInteractor, securityRepository, foregroundServicesRepository, userSwitcherRepository, @@ -149,7 +149,7 @@ class FooterActionsTestUtils( bgHandler: Handler = Handler(testableLooper.looper), bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler), userManager: UserManager = mock(), - userTracker: UserTracker = FakeUserTracker(), + userRepository: UserRepository = FakeUserRepository(), userSwitcherController: UserSwitcherController = mock(), userInfoController: UserInfoController = FakeUserInfoController(), settings: GlobalSettings = FakeGlobalSettings(), @@ -159,10 +159,10 @@ class FooterActionsTestUtils( bgHandler, bgDispatcher, userManager, - userTracker, userSwitcherController, userInfoController, settings, + userRepository, ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java index 197873f15d0d..288dcfe2825d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java @@ -186,7 +186,7 @@ public class FakeSensorManager extends SensorManager { } @Override - protected boolean initDataInjectionImpl(boolean enable) { + protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) { return false; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index cd83f8f4d9d8..5af80da894cc 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -68,7 +68,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo * * @see #setUserAndEnabledFeatures(int, int) */ - static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001; + static final int FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP = 0x00000001; /** * Flag for enabling the touch exploration feature. @@ -100,7 +100,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo /** * Flag for enabling the feature to control the screen magnifier. If - * {@link #FLAG_FEATURE_SCREEN_MAGNIFIER} is set this flag is ignored + * {@link #FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP} is set this flag is ignored * as the screen magnifier feature performs a super set of the work * performed by this feature. * @@ -149,7 +149,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo FLAG_FEATURE_INJECT_MOTION_EVENTS | FLAG_FEATURE_AUTOCLICK | FLAG_FEATURE_TOUCH_EXPLORATION - | FLAG_FEATURE_SCREEN_MAGNIFIER + | FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER | FLAG_SERVICE_HANDLES_DOUBLE_TAP | FLAG_REQUEST_MULTI_FINGER_GESTURES @@ -530,7 +530,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 - || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) + || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0) || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { final MagnificationGestureHandler magnificationGestureHandler = createMagnificationGestureHandler(displayId, @@ -648,7 +648,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private MagnificationGestureHandler createMagnificationGestureHandler( int displayId, Context displayContext) { final boolean detectControlGestures = (mEnabledFeatures - & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0; + & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0; final boolean triggerable = (mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0; MagnificationGestureHandler magnificationGestureHandler; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 203d9a1e72cd..e65a185adfd2 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2794,8 +2794,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityInputFilter inputFilter = null; synchronized (mLock) { int flags = 0; - if (userState.isDisplayMagnificationEnabledLocked()) { - flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER; + if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()) { + flags |= AccessibilityInputFilter + .FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP; } if (userState.isShortcutMagnificationEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; @@ -3148,12 +3149,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } private boolean readMagnificationEnabledSettingsLocked(AccessibilityUserState userState) { - final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser( + final boolean magnificationSingleFingerTripleTapEnabled = Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userState.mUserId) == 1; - if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) { - userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled); + if ((magnificationSingleFingerTripleTapEnabled + != userState.isMagnificationSingleFingerTripleTapEnabledLocked())) { + userState.setMagnificationSingleFingerTripleTapEnabledLocked( + magnificationSingleFingerTripleTapEnabled); return true; } return false; @@ -3393,7 +3396,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // We would skip overlay display because it uses overlay window to simulate secondary // displays in one display. It's not a real display and there's no input events for it. final ArrayList<Display> displays = getValidDisplayList(); - if (userState.isDisplayMagnificationEnabledLocked() + if (userState.isMagnificationSingleFingerTripleTapEnabledLocked() || userState.isShortcutMagnificationEnabledLocked()) { for (int i = 0; i < displays.size(); i++) { final Display display = displays.get(i); @@ -3422,7 +3425,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } final boolean connect = (userState.isShortcutMagnificationEnabledLocked() - || userState.isDisplayMagnificationEnabledLocked()) + || userState.isMagnificationSingleFingerTripleTapEnabledLocked()) && (userState.getMagnificationCapabilitiesLocked() != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) || userHasMagnificationServicesLocked(userState); @@ -5107,7 +5110,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub updateWindowMagnificationConnectionIfNeeded(userState); // Remove magnification button UI when the magnification capability is not all mode or // magnification is disabled. - if (!(userState.isDisplayMagnificationEnabledLocked() + if (!(userState.isMagnificationSingleFingerTripleTapEnabledLocked() || userState.isShortcutMagnificationEnabledLocked()) || userState.getMagnificationCapabilitiesLocked() != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 693526adc8d6..b4efec1a4b38 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -109,7 +109,7 @@ class AccessibilityUserState { private boolean mBindInstantServiceAllowed; private boolean mIsAudioDescriptionByDefaultRequested; private boolean mIsAutoclickEnabled; - private boolean mIsDisplayMagnificationEnabled; + private boolean mIsMagnificationSingleFingerTripleTapEnabled; private boolean mIsFilterKeyEventsEnabled; private boolean mIsPerformGesturesEnabled; private boolean mAccessibilityFocusOnlyInActiveWindow; @@ -211,7 +211,7 @@ class AccessibilityUserState { mRequestMultiFingerGestures = false; mRequestTwoFingerPassthrough = false; mSendMotionEventsEnabled = false; - mIsDisplayMagnificationEnabled = false; + mIsMagnificationSingleFingerTripleTapEnabled = false; mIsAutoclickEnabled = false; mUserNonInteractiveUiTimeout = 0; mUserInteractiveUiTimeout = 0; @@ -520,7 +520,7 @@ class AccessibilityUserState { .append(String.valueOf(mRequestTwoFingerPassthrough)); pw.append(", sendMotionEventsEnabled").append(String.valueOf(mSendMotionEventsEnabled)); pw.append(", displayMagnificationEnabled=").append(String.valueOf( - mIsDisplayMagnificationEnabled)); + mIsMagnificationSingleFingerTripleTapEnabled)); pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled)); pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout)); pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout)); @@ -625,12 +625,12 @@ class AccessibilityUserState { mIsAutoclickEnabled = enabled; } - public boolean isDisplayMagnificationEnabledLocked() { - return mIsDisplayMagnificationEnabled; + public boolean isMagnificationSingleFingerTripleTapEnabledLocked() { + return mIsMagnificationSingleFingerTripleTapEnabled; } - public void setDisplayMagnificationEnabledLocked(boolean enabled) { - mIsDisplayMagnificationEnabled = enabled; + public void setMagnificationSingleFingerTripleTapEnabledLocked(boolean enabled) { + mIsMagnificationSingleFingerTripleTapEnabled = enabled; } public boolean isFilterKeyEventsEnabledLocked() { diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index ee41a69c6f4c..65975e44ee2a 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -1437,6 +1437,10 @@ public class ContentCaptureManagerService extends if (!mDevCfgEnableContentProtectionReceiver) { return false; } + if (mDevCfgContentProtectionRequiredGroups.isEmpty() + && mDevCfgContentProtectionOptionalGroups.isEmpty()) { + return false; + } } return mContentProtectionConsentManager.isConsentGranted(userId) && mContentProtectionBlocklistManager.isAllowed(packageName); diff --git a/services/core/Android.bp b/services/core/Android.bp index e5225f65f22a..898cdcc30e0f 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -96,11 +96,26 @@ genrule { out: ["com/android/server/location/contexthub/ContextHubStatsLog.java"], } +/* + * This module is used to refer aconfig flag libraries that are + * added to the framework via static_libs. + * These libraries are referred here via libs prevent duplication of classes in both + * the framework and the system server. +*/ +java_defaults { + name: "shared-framework-aconfig-libs", + libs: [ + "display_flags_lib", + "camera_platform_flags_core_java_lib", + ], +} + java_library_static { name: "services.core.unboosted", defaults: [ "platform_service_defaults", "android.hardware.power-java_static", + "shared-framework-aconfig-libs", ], srcs: [ ":android.hardware.biometrics.face-V3-java-source", @@ -181,7 +196,6 @@ java_library_static { "android.hardware.power.stats-V2-java", "android.hidl.manager-V1.2-java", "cbor-java", - "display_flags_lib", "icu4j_calendar_astronomer", "android.security.aaid_aidl-java", "netd-client", @@ -191,9 +205,8 @@ java_library_static { "ImmutabilityAnnotation", "securebox", "apache-commons-math", - "power_optimization_flags_lib", + "backstage_power_flags_lib", "notification_flags_lib", - "camera_platform_flags_core_java_lib", "biometrics_flags_lib", "am_flags_lib", ], diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 8df54569cccd..638abdba36ec 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1387,11 +1387,12 @@ public abstract class PackageManagerInternal { @UserIdInt int userId); /** - * Tells PackageManager when a component (except BroadcastReceivers) of the package is used + * Tells PackageManager when a component of the package is used * and the package should get out of stopped state and be enabled. */ public abstract void notifyComponentUsed(@NonNull String packageName, - @UserIdInt int userId, @NonNull String recentCallingPackage, @NonNull String debugInfo); + @UserIdInt int userId, @Nullable String recentCallingPackage, + @NonNull String debugInfo); /** @deprecated For legacy shell command only. */ @Deprecated diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 127c5b389d79..3c56752d08e3 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -1440,10 +1440,9 @@ public class BroadcastQueueImpl extends BroadcastQueue { r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED); } - // Broadcast is being executed, its package can't be stopped. try { - mService.mPackageManagerInt.setPackageStoppedState( - r.curComponent.getPackageName(), false, r.userId); + mService.mPackageManagerInt.notifyComponentUsed( + r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString()); } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + r.curComponent.getPackageName() + ": " + e); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index d19eae5b0709..b48169788180 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -1982,8 +1982,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mService.notifyPackageUse(receiverPackageName, PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER); - mService.mPackageManagerInt.setPackageStoppedState( - receiverPackageName, false, r.userId); + mService.mPackageManagerInt.notifyComponentUsed( + receiverPackageName, r.userId, r.callerPackage, r.toString()); } private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app, diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index a451f361a933..9bba08ad1bfd 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2373,8 +2373,7 @@ public class OomAdjuster { } } - if (ppr.getLastProviderTime() > 0 - && (ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) { + if ((ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) { if (adj > PREVIOUS_APP_ADJ) { adj = PREVIOUS_APP_ADJ; schedGroup = SCHED_GROUP_BACKGROUND; diff --git a/services/core/java/com/android/server/am/ProcessProviderRecord.java b/services/core/java/com/android/server/am/ProcessProviderRecord.java index 751e8a821ab1..9b72a3afcfd9 100644 --- a/services/core/java/com/android/server/am/ProcessProviderRecord.java +++ b/services/core/java/com/android/server/am/ProcessProviderRecord.java @@ -34,7 +34,7 @@ final class ProcessProviderRecord { /** * The last time someone else was using a provider in this process. */ - private long mLastProviderTime; + private long mLastProviderTime = Long.MIN_VALUE; /** * class (String) -> ContentProviderRecord. diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 3771c05a294e..f02b8c737f90 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -511,8 +511,8 @@ class ProcessRecord implements WindowProcessListener { pw.print(prefix); pw.print("pid="); pw.println(mPid); pw.print(prefix); pw.print("lastActivityTime="); TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw); - pw.print(prefix); pw.print("startUptimeTime="); - TimeUtils.formatDuration(mStartElapsedTime, nowUptime, pw); + pw.print(prefix); pw.print("startUpTime="); + TimeUtils.formatDuration(mStartUptime, nowUptime, pw); pw.print(prefix); pw.print("startElapsedTime="); TimeUtils.formatDuration(mStartElapsedTime, nowElapsedTime, pw); pw.println(); diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index a9c388c232ed..27c08763fab0 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -280,7 +280,7 @@ final class ProcessStateRecord { * The last time the process was in the TOP state or greater. */ @GuardedBy("mService") - private long mLastTopTime; + private long mLastTopTime = Long.MIN_VALUE; /** * Is this an empty background process? diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 16e3fdf2a6ab..2d231b3cf42e 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -152,6 +152,7 @@ public class SettingsToPropertiesMapper { "preload_safety", "responsible_apis", "rust", + "safety_center", "system_performance", "test_suites", "text", @@ -159,7 +160,14 @@ public class SettingsToPropertiesMapper { "tv_system_ui", "vibrator", "virtual_devices", + "wear_calling_messaging", + "wear_connectivity", + "wear_esim_carriers", "wear_frameworks", + "wear_health_services", + "wear_media", + "wear_offload", + "wear_security", "wear_system_health", "wear_systems", "window_surfaces", diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 26d99d843c7e..bb9ea285385b 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -2,7 +2,7 @@ package: "com.android.server.am" flag { name: "oomadjuster_correctness_rewrite" - namespace: "android_platform_power_optimization" + namespace: "backstage_power" description: "Utilize new OomAdjuster implementation" bug: "298055811" is_fixed_read_only: true diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 9cfac9af3991..eea3d3885b34 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -68,6 +68,7 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; @@ -496,8 +497,9 @@ public class AudioDeviceBroker { AudioDeviceInfo.TYPE_AUX_LINE }; - /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) { - return isValidCommunicationDeviceType(device.getType()); + /*package */ static boolean isValidCommunicationDevice(@NonNull AudioDeviceInfo device) { + Objects.requireNonNull(device, "device must not be null"); + return device.isSink() && isValidCommunicationDeviceType(device.getType()); } private static boolean isValidCommunicationDeviceType(int deviceType) { diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java index 6c0fef5f628d..5f4e4c3bc4e0 100644 --- a/services/core/java/com/android/server/audio/MusicFxHelper.java +++ b/services/core/java/com/android/server/audio/MusicFxHelper.java @@ -157,7 +157,8 @@ public class MusicFxHelper { 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)); + final List<Integer> sessions = + new ArrayList(mClientUidSessionMap.get(Integer.valueOf(uid))); Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions"); for (Integer session : sessions) { Intent intent = new Intent( diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 906c66d4fe8a..76dde5463534 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -1474,11 +1474,11 @@ public class ClipboardService extends SystemService { .getDrawable(R.drawable.ic_safety_protection); toastToShow = Toast.makeCustomToastWithIcon(toastContext, UiThread.get().getLooper(), message, - Toast.LENGTH_SHORT, safetyProtectionIcon); + Toast.LENGTH_LONG, safetyProtectionIcon); } else { toastToShow = Toast.makeText( toastContext, UiThread.get().getLooper(), message, - Toast.LENGTH_SHORT); + Toast.LENGTH_LONG); } toastToShow.show(); } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 35a4f475c878..cb2302a60248 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -758,7 +758,8 @@ public final class DisplayManagerService extends SystemService { mContext.registerReceiver(mIdleModeReceiver, filter); - mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext); + mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled()) + ? SmallAreaDetectionController.create(mContext) : null; } @VisibleForTesting @@ -2973,7 +2974,7 @@ public final class DisplayManagerService extends SystemService { // Check if the target app is in cached mode private boolean isUidCached(int uid) { - if (mActivityManagerInternal == null) { + if (mActivityManagerInternal == null || uid < FIRST_APPLICATION_UID) { return false; } int procState = mActivityManagerInternal.getUidProcessState(uid); diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java index adaa5390cb9b..bf384b02d95e 100644 --- a/services/core/java/com/android/server/display/SmallAreaDetectionController.java +++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManagerInternal; +import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.DeviceConfigInterface; import android.util.ArrayMap; @@ -30,15 +31,14 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; -import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.pkg.PackageStateInternal; import java.io.PrintWriter; -import java.util.Arrays; import java.util.Map; final class SmallAreaDetectionController { - private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds); - private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold); + private static native void nativeUpdateSmallAreaDetection(int[] appIds, float[] thresholds); + private static native void nativeSetSmallAreaDetectionThreshold(int appId, float threshold); // TODO(b/281720315): Move this to DeviceConfig once server side ready. private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST = @@ -47,12 +47,8 @@ final class SmallAreaDetectionController { private final Object mLock = new Object(); private final Context mContext; private final PackageManagerInternal mPackageManager; - private final UserManagerInternal mUserManager; @GuardedBy("mLock") private final Map<String, Float> mAllowPkgMap = new ArrayMap<>(); - // TODO(b/298722189): Update allowlist when user changes - @GuardedBy("mLock") - private int[] mUserIds; static SmallAreaDetectionController create(@NonNull Context context) { final SmallAreaDetectionController controller = @@ -67,7 +63,6 @@ final class SmallAreaDetectionController { SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) { mContext = context; mPackageManager = LocalServices.getService(PackageManagerInternal.class); - mUserManager = LocalServices.getService(UserManagerInternal.class); deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, BackgroundThread.getExecutor(), new SmallAreaDetectionController.OnPropertiesChangedListener()); @@ -76,6 +71,7 @@ final class SmallAreaDetectionController { @VisibleForTesting void updateAllowlist(@Nullable String property) { + final Map<String, Float> allowPkgMap = new ArrayMap<>(); synchronized (mLock) { mAllowPkgMap.clear(); if (property != null) { @@ -86,8 +82,11 @@ final class SmallAreaDetectionController { .getStringArray(R.array.config_smallAreaDetectionAllowlist); for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString); } - updateSmallAreaDetection(); + + if (mAllowPkgMap.isEmpty()) return; + allowPkgMap.putAll(mAllowPkgMap); } + updateSmallAreaDetection(allowPkgMap); } @GuardedBy("mLock") @@ -105,43 +104,32 @@ final class SmallAreaDetectionController { } } - @GuardedBy("mLock") - private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) { - for (int i = 0; i < mUserIds.length; i++) { - final int userId = mUserIds[i]; - final int uid = mPackageManager.getPackageUid(pkg, 0, userId); - if (uid > 0) list.put(uid, threshold); - } - } - - @GuardedBy("mLock") - private void updateSmallAreaDetection() { - if (mAllowPkgMap.isEmpty()) return; - - mUserIds = mUserManager.getUserIds(); - - final SparseArray<Float> uidThresholdList = new SparseArray<>(); - for (String pkg : mAllowPkgMap.keySet()) { - final float threshold = mAllowPkgMap.get(pkg); - updateUidListForAllUsers(uidThresholdList, pkg, threshold); + private void updateSmallAreaDetection(Map<String, Float> allowPkgMap) { + final SparseArray<Float> appIdThresholdList = new SparseArray(allowPkgMap.size()); + for (String pkg : allowPkgMap.keySet()) { + final float threshold = allowPkgMap.get(pkg); + final PackageStateInternal stage = mPackageManager.getPackageStateInternal(pkg); + if (stage != null) { + appIdThresholdList.put(stage.getAppId(), threshold); + } } - final int[] uids = new int[uidThresholdList.size()]; - final float[] thresholds = new float[uidThresholdList.size()]; - for (int i = 0; i < uidThresholdList.size(); i++) { - uids[i] = uidThresholdList.keyAt(i); - thresholds[i] = uidThresholdList.valueAt(i); + final int[] appIds = new int[appIdThresholdList.size()]; + final float[] thresholds = new float[appIdThresholdList.size()]; + for (int i = 0; i < appIdThresholdList.size(); i++) { + appIds[i] = appIdThresholdList.keyAt(i); + thresholds[i] = appIdThresholdList.valueAt(i); } - updateSmallAreaDetection(uids, thresholds); + updateSmallAreaDetection(appIds, thresholds); } @VisibleForTesting - void updateSmallAreaDetection(int[] uids, float[] thresholds) { - nativeUpdateSmallAreaDetection(uids, thresholds); + void updateSmallAreaDetection(int[] appIds, float[] thresholds) { + nativeUpdateSmallAreaDetection(appIds, thresholds); } - void setSmallAreaDetectionThreshold(int uid, float threshold) { - nativeSetSmallAreaDetectionThreshold(uid, threshold); + void setSmallAreaDetectionThreshold(int appId, float threshold) { + nativeSetSmallAreaDetectionThreshold(appId, threshold); } void dump(PrintWriter pw) { @@ -151,7 +139,6 @@ final class SmallAreaDetectionController { for (String pkg : mAllowPkgMap.keySet()) { pw.println(" " + pkg + " threshold = " + mAllowPkgMap.get(pkg)); } - pw.println(" mUserIds=" + Arrays.toString(mUserIds)); } } @@ -167,11 +154,15 @@ final class SmallAreaDetectionController { private final class PackageReceiver implements PackageManagerInternal.PackageListObserver { @Override public void onPackageAdded(@NonNull String packageName, int uid) { + float threshold = 0.0f; synchronized (mLock) { if (mAllowPkgMap.containsKey(packageName)) { - setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName)); + threshold = mAllowPkgMap.get(packageName); } } + if (threshold > 0.0f) { + setSmallAreaDetectionThreshold(UserHandle.getAppId(uid), threshold); + } } } } 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 fae8383bb62e..d953e8e52365 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -71,6 +71,10 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER, Flags::enablePowerThrottlingClamper); + private final FlagState mSmallAreaDetectionFlagState = new FlagState( + Flags.FLAG_ENABLE_SMALL_AREA_DETECTION, + Flags::enableSmallAreaDetection); + /** Returns whether connected display management is enabled or not. */ public boolean isConnectedDisplayManagementEnabled() { return mConnectedDisplayManagementFlagState.isEnabled(); @@ -147,6 +151,10 @@ public class DisplayManagerFlags { return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled(); } + public boolean isSmallAreaDetectionEnabled() { + return mSmallAreaDetectionFlagState.isEnabled(); + } + private static class FlagState { private final String mName; 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 9ab9c9def61b..9141814da6fc 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 @@ -104,3 +104,12 @@ flag { bug: "211737588" is_fixed_read_only: true } + +flag { + name: "enable_small_area_detection" + namespace: "display_manager" + description: "Feature flag for SmallAreaDetection" + bug: "298722189" + 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 ca23844044ca..d023913c9694 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -723,7 +723,9 @@ public class DisplayModeDirector { if (mode.getPhysicalWidth() > maxAllowedWidth || mode.getPhysicalHeight() > maxAllowedHeight || mode.getPhysicalWidth() < outSummary.minWidth - || mode.getPhysicalHeight() < outSummary.minHeight) { + || mode.getPhysicalHeight() < outSummary.minHeight + || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate + || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) { continue; } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index cc261a4b9797..44719f88351b 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -515,8 +515,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public RoutingSessionInfo getSystemSessionInfoForPackage(IMediaRouter2Manager manager, - String packageName) { + public RoutingSessionInfo getSystemSessionInfoForPackage(@Nullable String packageName) { final int uid = Binder.getCallingUid(); final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); boolean setDeviceRouteSelected = false; diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java new file mode 100644 index 000000000000..ff70cb35f9dc --- /dev/null +++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java @@ -0,0 +1,91 @@ +/* + * 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 android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; + +public class MediaProjectionSessionIdGenerator { + + private static final String PREFERENCES_FILE_NAME = "media_projection_session_id"; + private static final String SESSION_ID_PREF_KEY = "media_projection_session_id_key"; + private static final int SESSION_ID_DEFAULT_VALUE = 0; + + private static final Object sInstanceLock = new Object(); + + @GuardedBy("sInstanceLock") + private static MediaProjectionSessionIdGenerator sInstance; + + private final Object mSessionIdLock = new Object(); + + @GuardedBy("mSessionIdLock") + private final SharedPreferences mSharedPreferences; + + /** Creates or returns an existing instance of {@link MediaProjectionSessionIdGenerator}. */ + public static MediaProjectionSessionIdGenerator getInstance(Context context) { + synchronized (sInstanceLock) { + if (sInstance == null) { + File preferencesFile = + new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME); + SharedPreferences preferences = + context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE); + sInstance = new MediaProjectionSessionIdGenerator(preferences); + } + return sInstance; + } + } + + @VisibleForTesting + public MediaProjectionSessionIdGenerator(SharedPreferences sharedPreferences) { + this.mSharedPreferences = sharedPreferences; + } + + /** Returns the current session ID. This value is persisted across reboots. */ + public int getCurrentSessionId() { + synchronized (mSessionIdLock) { + return getCurrentSessionIdInternal(); + } + } + + /** + * Creates and returns a new session ID. This value will be persisted as the new current session + * ID, and will be persisted across reboots. + */ + public int createAndGetNewSessionId() { + synchronized (mSessionIdLock) { + int newSessionId = getCurrentSessionId() + 1; + setSessionIdInternal(newSessionId); + return newSessionId; + } + } + + @GuardedBy("mSessionIdLock") + private void setSessionIdInternal(int value) { + mSharedPreferences.edit().putInt(SESSION_ID_PREF_KEY, value).apply(); + } + + @GuardedBy("mSessionIdLock") + private int getCurrentSessionIdInternal() { + return mSharedPreferences.getInt(SESSION_ID_PREF_KEY, SESSION_ID_DEFAULT_VALUE); + } +} diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java new file mode 100644 index 000000000000..4026d0c43484 --- /dev/null +++ b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java @@ -0,0 +1,109 @@ +/* + * 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; +import java.time.Duration; +import java.time.Instant; +import java.time.InstantSource; + +/** Stores timestamps of media projection sessions. */ +public class MediaProjectionTimestampStore { + private static final String PREFERENCES_FILE_NAME = "media_projection_timestamp"; + private static final String TIMESTAMP_PREF_KEY = "media_projection_timestamp_key"; + private static final Object sInstanceLock = new Object(); + + @GuardedBy("sInstanceLock") + private static MediaProjectionTimestampStore sInstance; + + private final Object mTimestampLock = new Object(); + + @GuardedBy("mTimestampLock") + private final SharedPreferences mSharedPreferences; + + private final InstantSource mInstantSource; + + @VisibleForTesting + public MediaProjectionTimestampStore( + SharedPreferences sharedPreferences, InstantSource instantSource) { + this.mSharedPreferences = sharedPreferences; + this.mInstantSource = instantSource; + } + + /** Creates or returns an existing instance of {@link MediaProjectionTimestampStore}. */ + public static MediaProjectionTimestampStore getInstance(Context context) { + synchronized (sInstanceLock) { + if (sInstance == null) { + File preferencesFile = + new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME); + SharedPreferences preferences = + context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE); + sInstance = new MediaProjectionTimestampStore(preferences, InstantSource.system()); + } + return sInstance; + } + } + + /** + * Returns the time that has passed since the last active session, or {@code null} if there was + * no last active session. + */ + @Nullable + public Duration timeSinceLastActiveSession() { + synchronized (mTimestampLock) { + Instant lastActiveSessionTimestamp = getLastActiveSessionTimestamp(); + if (lastActiveSessionTimestamp == null) { + return null; + } + Instant now = mInstantSource.instant(); + return Duration.between(lastActiveSessionTimestamp, now); + } + } + + /** Registers that the current active session ended now. */ + public void registerActiveSessionEnded() { + synchronized (mTimestampLock) { + Instant now = mInstantSource.instant(); + setLastActiveSessionTimestamp(now); + } + } + + @GuardedBy("mTimestampLock") + @Nullable + private Instant getLastActiveSessionTimestamp() { + long lastActiveSessionEpochMilli = + mSharedPreferences.getLong(TIMESTAMP_PREF_KEY, /* defValue= */ -1); + if (lastActiveSessionEpochMilli == -1) { + return null; + } + return Instant.ofEpochMilli(lastActiveSessionEpochMilli); + } + + @GuardedBy("mTimestampLock") + private void setLastActiveSessionTimestamp(@NonNull Instant timestamp) { + mSharedPreferences.edit().putLong(TIMESTAMP_PREF_KEY, timestamp.toEpochMilli()).apply(); + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 837b76154363..b4d36db96c01 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -3500,8 +3500,19 @@ public class NotificationManagerService extends SystemService { null /* options */); record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token, text, callback, duration, windowToken, displayId, textCallback); - mToastQueue.add(record); - index = mToastQueue.size() - 1; + + // Insert system toasts at the front of the queue + int systemToastInsertIdx = mToastQueue.size(); + if (isSystemToast) { + systemToastInsertIdx = getInsertIndexForSystemToastLocked(); + } + if (systemToastInsertIdx < mToastQueue.size()) { + index = systemToastInsertIdx; + mToastQueue.add(index, record); + } else { + mToastQueue.add(record); + index = mToastQueue.size() - 1; + } keepProcessAliveForToastIfNeededLocked(callingPid); } // If it's at index 0, it's the current toast. It doesn't matter if it's @@ -3517,6 +3528,23 @@ public class NotificationManagerService extends SystemService { } } + @GuardedBy("mToastQueue") + private int getInsertIndexForSystemToastLocked() { + // If there are other system toasts: insert after the last one + int idx = 0; + for (ToastRecord r : mToastQueue) { + if (idx == 0 && mIsCurrentToastShown) { + idx++; + continue; + } + if (!r.isSystemToast) { + return idx; + } + idx++; + } + return idx; + } + private boolean checkCanEnqueueToast(String pkg, int callingUid, int displayId, boolean isAppRenderedToast, boolean isSystemToast) { final boolean isPackageSuspended = isPackagePaused(pkg); diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java index 148e0df7cf64..9ad8318c2b5f 100644 --- a/services/core/java/com/android/server/pm/MovePackageHelper.java +++ b/services/core/java/com/android/server/pm/MovePackageHelper.java @@ -89,6 +89,21 @@ public final class MovePackageHelper { if (packageState == null || packageState.getPkg() == null) { throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package"); } + final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState, + mPm.mUserManager.getUserIds(), true); + final UserHandle userForMove; + if (installedUserIds.length > 0) { + userForMove = UserHandle.of(installedUserIds[0]); + } else { + throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, + "Package is not installed for any user"); + } + for (int userId : installedUserIds) { + if (snapshot.shouldFilterApplicationIncludingUninstalled(packageState, callingUid, + userId)) { + throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package"); + } + } final AndroidPackage pkg = packageState.getPkg(); if (packageState.isSystem()) { throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE, @@ -134,8 +149,6 @@ public final class MovePackageHelper { final String label = String.valueOf(pm.getApplicationLabel( AndroidPackageUtils.generateAppInfoWithoutState(pkg))); final int targetSdkVersion = pkg.getTargetSdkVersion(); - final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState, - mPm.mUserManager.getUserIds(), true); final String fromCodePath; if (codeFile.getParentFile().getName().startsWith( PackageManagerService.RANDOM_DIR_PREFIX)) { @@ -303,8 +316,8 @@ public final class MovePackageHelper { final PackageLite lite = ret.isSuccess() ? ret.getResult() : null; final InstallingSession installingSession = new InstallingSession(origin, move, installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource, - volumeUuid, user, packageAbiOverride, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, - lite, mPm); + volumeUuid, userForMove, packageAbiOverride, + PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm); installingSession.movePackage(); } diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index f59188e9fd93..0fb1f7a0780c 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -196,8 +196,12 @@ public class PackageArchiver { for (int i = 0, size = mainActivities.length; i < size; ++i) { var mainActivity = mainActivities[i]; Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i); - ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( - mainActivity.title, iconPath, null); + ArchiveActivityInfo activityInfo = + new ArchiveActivityInfo( + mainActivity.title, + mainActivity.originalComponentName, + iconPath, + null); archiveActivityInfos.add(activityInfo); } @@ -215,8 +219,12 @@ public class PackageArchiver { for (int i = 0, size = mainActivities.size(); i < size; i++) { LauncherActivityInfo mainActivity = mainActivities.get(i); Path iconPath = storeIcon(packageName, mainActivity, userId, i); - ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( - mainActivity.getLabel().toString(), iconPath, null); + ArchiveActivityInfo activityInfo = + new ArchiveActivityInfo( + mainActivity.getLabel().toString(), + mainActivity.getComponentName(), + iconPath, + null); archiveActivityInfos.add(activityInfo); } @@ -593,6 +601,7 @@ public class PackageArchiver { } var archivedActivity = new ArchivedActivityParcel(); archivedActivity.title = info.getTitle(); + archivedActivity.originalComponentName = info.getOriginalComponentName(); archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap()); archivedActivity.monochromeIconBitmap = bytesFromBitmapFile( info.getMonochromeIconBitmap()); @@ -624,6 +633,7 @@ public class PackageArchiver { } var archivedActivity = new ArchivedActivityParcel(); archivedActivity.title = info.getLabel().toString(); + archivedActivity.originalComponentName = info.getComponentName(); archivedActivity.iconBitmap = info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap( drawableToBitmap(info.getIcon(/* density= */ 0))); diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index 651845e71924..e7499680b9a2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -747,7 +747,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { @Override public void notifyComponentUsed(@NonNull String packageName, @UserIdInt int userId, - @NonNull String recentCallingPackage, @NonNull String debugInfo) { + @Nullable String recentCallingPackage, @NonNull String debugInfo) { mService.notifyComponentUsed(snapshot(), packageName, userId, recentCallingPackage, debugInfo); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index abeabc96e969..839b6998bd8b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -36,6 +36,7 @@ import static android.os.storage.StorageManager.FLAG_STORAGE_CE; import static android.os.storage.StorageManager.FLAG_STORAGE_DE; import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL; import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; +import static android.util.FeatureFlagUtils.SETTINGS_TREAT_PAUSE_AS_QUARANTINE; import static com.android.internal.annotations.VisibleForTesting.Visibility; import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME; @@ -164,6 +165,7 @@ import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.ExceptionUtils; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -4576,7 +4578,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName, - @UserIdInt int userId, @NonNull String recentCallingPackage, + @UserIdInt int userId, @Nullable String recentCallingPackage, @NonNull String debugInfo) { synchronized (mLock) { final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName); @@ -6133,8 +6135,16 @@ public class PackageManagerService implements PackageSender, TestUtilityService final Computer snapshot = snapshotComputer(); enforceCanSetPackagesSuspendedAsUser(snapshot, callingPackage, callingUid, userId, "setPackagesSuspendedAsUser"); - boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) - && Flags.quarantinedEnabled(); + boolean quarantined = false; + if (Flags.quarantinedEnabled()) { + if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) { + quarantined = true; + } else if (FeatureFlagUtils.isEnabled(mContext, + SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) { + final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing); + quarantined = callingPackage.equals(wellbeingPkg); + } + } return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended, appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid, false /* forQuietMode */, quarantined); diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 9f4e86d527ce..3ca933a66656 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -1228,6 +1228,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal long activityInfoToken = proto.start( PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS); proto.write(ArchiveActivityInfo.TITLE, activityInfo.getTitle()); + proto.write( + ArchiveActivityInfo.ORIGINAL_COMPONENT_NAME, + activityInfo.getOriginalComponentName().flattenToString()); if (activityInfo.getIconBitmap() != null) { proto.write(ArchiveActivityInfo.ICON_BITMAP_PATH, activityInfo.getIconBitmap().toAbsolutePath().toString()); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 451b3a5256ac..e726d91c30a0 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -368,6 +368,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile private static final String ATTR_VALUE = "value"; private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time"; private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title"; + private static final String ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME = "original-component-name"; private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title"; private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path"; private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path"; @@ -2079,6 +2080,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) { String title = parser.getAttributeValue(null, ATTR_ARCHIVE_ACTIVITY_TITLE); + String originalComponentName = + parser.getAttributeValue(null, ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME); String iconAttribute = parser.getAttributeValue(null, ATTR_ARCHIVE_ICON_PATH); Path iconPath = iconAttribute == null ? null : Path.of(iconAttribute); @@ -2087,17 +2090,27 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile Path monochromeIconPath = monochromeAttribute == null ? null : Path.of( monochromeAttribute); - if (title == null || iconPath == null) { - Slog.wtf(TAG, - TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s", - TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title, + if (title == null || originalComponentName == null || iconPath == null) { + Slog.wtf( + TAG, + TextUtils.formatSimple( + "Missing attributes in tag %s. %s: %s, %s: %s, %s: %s", + TAG_ARCHIVE_ACTIVITY_INFO, + ATTR_ARCHIVE_ACTIVITY_TITLE, + title, + ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME, + originalComponentName, ATTR_ARCHIVE_ICON_PATH, iconPath)); continue; } activityInfos.add( - new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath)); + new ArchiveState.ArchiveActivityInfo( + title, + ComponentName.unflattenFromString(originalComponentName), + iconPath, + monochromeIconPath)); } } return activityInfos; @@ -2469,6 +2482,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) { serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO); serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle()); + serializer.attribute( + null, + ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME, + activityInfo.getOriginalComponentName().flattenToString()); if (activityInfo.getIconBitmap() != null) { serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH, activityInfo.getIconBitmap().toAbsolutePath().toString()); @@ -6411,16 +6428,25 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } boolean clearPersistentPreferredActivity(IntentFilter filter, int userId) { + ArrayList<PersistentPreferredActivity> removed = null; PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId); Iterator<PersistentPreferredActivity> it = ppir.filterIterator(); boolean changed = false; while (it.hasNext()) { PersistentPreferredActivity ppa = it.next(); if (IntentFilter.filterEquals(ppa.getIntentFilter(), filter)) { + if (removed == null) { + removed = new ArrayList<>(); + } + removed.add(ppa); + } + } + if (removed != null) { + for (int i = 0; i < removed.size(); i++) { + PersistentPreferredActivity ppa = removed.get(i); ppir.removeFilter(ppa); - changed = true; - break; } + changed = true; } if (changed) { onChanged(); diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java index 4916a4a6d72a..1e40d44bd4ca 100644 --- a/services/core/java/com/android/server/pm/pkg/ArchiveState.java +++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java @@ -16,9 +16,11 @@ package com.android.server.pm.pkg; +import android.content.ComponentName; import android.annotation.NonNull; import android.annotation.Nullable; +import com.android.internal.util.AnnotationValidations; import com.android.internal.util.DataClass; import java.nio.file.Path; @@ -56,6 +58,10 @@ public class ArchiveState { @NonNull private final String mTitle; + /** The component name of the original activity (pre-archival). */ + @NonNull + private final ComponentName mOriginalComponentName; + /** * The path to the stored icon of the activity in the app's locale. Null if the app does * not define any icon (default icon would be shown on the launcher). @@ -96,11 +102,13 @@ public class ArchiveState { @DataClass.Generated.Member public ArchiveActivityInfo( @NonNull String title, + @NonNull ComponentName originalComponentName, @Nullable Path iconBitmap, @Nullable Path monochromeIconBitmap) { this.mTitle = title; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTitle); + this.mOriginalComponentName = originalComponentName; + AnnotationValidations.validate(NonNull.class, null, mTitle); + AnnotationValidations.validate(NonNull.class, null, mOriginalComponentName); this.mIconBitmap = iconBitmap; this.mMonochromeIconBitmap = monochromeIconBitmap; @@ -116,6 +124,14 @@ public class ArchiveState { } /** + * The component name of the original activity (pre-archival). + */ + @DataClass.Generated.Member + public @NonNull ComponentName getOriginalComponentName() { + return mOriginalComponentName; + } + + /** * The path to the stored icon of the activity in the app's locale. Null if the app does * not define any icon (default icon would be shown on the launcher). */ @@ -140,6 +156,7 @@ public class ArchiveState { return "ArchiveActivityInfo { " + "title = " + mTitle + ", " + + "originalComponentName = " + mOriginalComponentName + ", " + "iconBitmap = " + mIconBitmap + ", " + "monochromeIconBitmap = " + mMonochromeIconBitmap + " }"; @@ -159,6 +176,7 @@ public class ArchiveState { //noinspection PointlessBooleanExpression return true && java.util.Objects.equals(mTitle, that.mTitle) + && java.util.Objects.equals(mOriginalComponentName, that.mOriginalComponentName) && java.util.Objects.equals(mIconBitmap, that.mIconBitmap) && java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap); } @@ -171,6 +189,7 @@ public class ArchiveState { int _hash = 1; _hash = 31 * _hash + java.util.Objects.hashCode(mTitle); + _hash = 31* _hash + java.util.Objects.hashCode(mOriginalComponentName); _hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap); _hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap); return _hash; @@ -180,7 +199,8 @@ public class ArchiveState { time = 1693590309015L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)") + inputSignatures = + "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull android.content.ComponentName mOriginalComponentName\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)") @Deprecated private void __metadata() {} @@ -224,11 +244,9 @@ public class ArchiveState { @NonNull List<ArchiveActivityInfo> activityInfos, @NonNull String installerTitle) { this.mActivityInfos = activityInfos; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mActivityInfos); + AnnotationValidations.validate(NonNull.class, null, mActivityInfos); this.mInstallerTitle = installerTitle; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mInstallerTitle); + AnnotationValidations.validate(NonNull.class, null, mInstallerTitle); // onConstructed(); // You can define this method to get a callback } diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp index 1da9dd770ba6..607d435b5410 100644 --- a/services/core/java/com/android/server/power/Android.bp +++ b/services/core/java/com/android/server/power/Android.bp @@ -1,5 +1,5 @@ aconfig_declarations { - name: "power_optimization_flags", + name: "backstage_power_flags", package: "com.android.server.power.optimization", srcs: [ "stats/*.aconfig", @@ -7,6 +7,6 @@ aconfig_declarations { } java_aconfig_library { - name: "power_optimization_flags_lib", - aconfig_declarations: "power_optimization_flags", + name: "backstage_power_flags_lib", + aconfig_declarations: "backstage_power_flags", } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index dfbcbae650cd..4a4214f7af83 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -3347,8 +3347,6 @@ public final class PowerManagerService extends SystemService } else { startDreaming = false; } - Slog.i(TAG, "handleSandman powerGroup=" + groupId + " startDreaming=" + startDreaming - + " wakefulness=" + wakefulnessToString(wakefulness)); } // Start dreaming if needed. @@ -3383,23 +3381,19 @@ public final class PowerManagerService extends SystemService if (startDreaming && isDreaming) { mDreamsBatteryLevelDrain = 0; if (wakefulness == WAKEFULNESS_DOZING) { - Slog.i(TAG, "Dozing powerGroup " + groupId); + Slog.i(TAG, "Dozing..."); } else { - Slog.i(TAG, "Dreaming powerGroup " + groupId); + Slog.i(TAG, "Dreaming..."); } } // If preconditions changed, wait for the next iteration to determine // whether the dream should continue (or be restarted). final PowerGroup powerGroup = mPowerGroups.get(groupId); - final int newWakefulness = powerGroup.getWakefulnessLocked(); if (powerGroup.isSandmanSummonedLocked() - || newWakefulness != wakefulness) { + || powerGroup.getWakefulnessLocked() != wakefulness) { return; // wait for next cycle } - Slog.i(TAG, "handleSandman powerGroup=" + groupId + " isDreaming=" + isDreaming - + " wakefulness=" + newWakefulness); - // Determine whether the dream should continue. long now = mClock.uptimeMillis(); diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig index add806febe67..0f135715ebc3 100644 --- a/services/core/java/com/android/server/power/stats/flags.aconfig +++ b/services/core/java/com/android/server/power/stats/flags.aconfig @@ -2,14 +2,14 @@ package: "com.android.server.power.optimization" flag { name: "power_monitor_api" - namespace: "power_optimization" + namespace: "backstage_power" description: "Feature flag for ODPM API" bug: "295027807" } flag { name: "streamlined_battery_stats" - namespace: "power_optimization" + namespace: "backstage_power" description: "Feature flag for streamlined battery stats" bug: "285646152" } diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS index 174ad3ad2e25..c33f3d9fa264 100644 --- a/services/core/java/com/android/server/stats/OWNERS +++ b/services/core/java/com/android/server/stats/OWNERS @@ -1,11 +1,10 @@ jeffreyhuang@google.com joeo@google.com -jtnguyen@google.com +monicamwang@google.com muhammadq@google.com +rayhdez@google.com rslawik@google.com -ruchirr@google.com sharaienko@google.com singhtejinder@google.com tsaichristine@google.com yaochen@google.com -yro@google.com diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java index becbbf2ea76a..519acec2f7b4 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java @@ -22,11 +22,10 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorInfo; +import android.os.vibrator.Flags; import android.util.Slog; import android.util.SparseArray; import android.view.HapticFeedbackConstants; -import android.view.flags.FeatureFlags; -import android.view.flags.FeatureFlagsImpl; import com.android.internal.annotations.VisibleForTesting; @@ -56,7 +55,8 @@ public final class HapticFeedbackVibrationProvider { // If present and valid, a vibration here will be used for an effect. // Otherwise, the system's default vibration will be used. @Nullable private final SparseArray<VibrationEffect> mHapticCustomizations; - private final FeatureFlags mViewFeatureFlags; + + private float mKeyboardVibrationFixedAmplitude; /** @hide */ public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) { @@ -65,16 +65,14 @@ public final class HapticFeedbackVibrationProvider { /** @hide */ public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) { - this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo), - new FeatureFlagsImpl()); + this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo)); } /** @hide */ @VisibleForTesting HapticFeedbackVibrationProvider( Resources res, VibratorInfo vibratorInfo, - @Nullable SparseArray<VibrationEffect> hapticCustomizations, - FeatureFlags viewFeatureFlags) { + @Nullable SparseArray<VibrationEffect> hapticCustomizations) { mVibratorInfo = vibratorInfo; mHapticTextHandleEnabled = res.getBoolean( com.android.internal.R.bool.config_enableHapticTextHandle); @@ -83,14 +81,17 @@ public final class HapticFeedbackVibrationProvider { hapticCustomizations = null; } mHapticCustomizations = hapticCustomizations; - mViewFeatureFlags = viewFeatureFlags; - mSafeModeEnabledVibrationEffect = effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED) ? mHapticCustomizations.get(HapticFeedbackConstants.SAFE_MODE_ENABLED) : VibrationSettings.createEffectFromResource( res, com.android.internal.R.array.config_safeModeEnabledVibePattern); + mKeyboardVibrationFixedAmplitude = res.getFloat( + com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude); + if (mKeyboardVibrationFixedAmplitude < 0 || mKeyboardVibrationFixedAmplitude > 1) { + mKeyboardVibrationFixedAmplitude = -1; + } } /** @@ -120,6 +121,9 @@ public final class HapticFeedbackVibrationProvider { return getVibration(effectId, VibrationEffect.EFFECT_TEXTURE_TICK); case HapticFeedbackConstants.KEYBOARD_RELEASE: + case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS + return getKeyboardVibration(effectId); + case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE: case HapticFeedbackConstants.ENTRY_BUMP: case HapticFeedbackConstants.DRAG_CROSSING: @@ -128,7 +132,6 @@ public final class HapticFeedbackVibrationProvider { VibrationEffect.EFFECT_TICK, /* fallbackForPredefinedEffect= */ false); - case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS case HapticFeedbackConstants.VIRTUAL_KEY: case HapticFeedbackConstants.EDGE_RELEASE: case HapticFeedbackConstants.CALENDAR_DATE: @@ -204,6 +207,10 @@ public final class HapticFeedbackVibrationProvider { case HapticFeedbackConstants.SCROLL_LIMIT: attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES; break; + case HapticFeedbackConstants.KEYBOARD_TAP: + case HapticFeedbackConstants.KEYBOARD_RELEASE: + attrs = createKeyboardVibrationAttributes(); + break; default: attrs = TOUCH_VIBRATION_ATTRIBUTES; } @@ -212,9 +219,12 @@ public final class HapticFeedbackVibrationProvider { if (bypassVibrationIntensitySetting) { flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; } - if (shouldBypassInterruptionPolicy(effectId, mViewFeatureFlags)) { + if (shouldBypassInterruptionPolicy(effectId)) { flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; } + if (shouldBypassIntensityScale(effectId)) { + flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE; + } return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build(); } @@ -295,6 +305,64 @@ public final class HapticFeedbackVibrationProvider { return mHapticCustomizations != null && mHapticCustomizations.contains(effectId); } + private VibrationEffect getKeyboardVibration(int effectId) { + if (effectHasCustomization(effectId)) { + return mHapticCustomizations.get(effectId); + } + + int primitiveId; + int predefinedEffectId; + boolean predefinedEffectFallback; + + switch (effectId) { + case HapticFeedbackConstants.KEYBOARD_RELEASE: + primitiveId = VibrationEffect.Composition.PRIMITIVE_TICK; + predefinedEffectId = VibrationEffect.EFFECT_TICK; + predefinedEffectFallback = false; + break; + case HapticFeedbackConstants.KEYBOARD_TAP: + default: + primitiveId = VibrationEffect.Composition.PRIMITIVE_CLICK; + predefinedEffectId = VibrationEffect.EFFECT_CLICK; + predefinedEffectFallback = true; + } + if (Flags.keyboardCategoryEnabled() && mKeyboardVibrationFixedAmplitude > 0) { + if (mVibratorInfo.isPrimitiveSupported(primitiveId)) { + return VibrationEffect.startComposition() + .addPrimitive(primitiveId, mKeyboardVibrationFixedAmplitude) + .compose(); + } + } + return getVibration(effectId, predefinedEffectId, + /* fallbackForPredefinedEffect= */ predefinedEffectFallback); + } + + private boolean shouldBypassIntensityScale(int effectId) { + if (!Flags.keyboardCategoryEnabled() || mKeyboardVibrationFixedAmplitude < 0) { + // shouldn't bypass if not support keyboard category or no fixed amplitude + return false; + } + switch (effectId) { + case HapticFeedbackConstants.KEYBOARD_TAP: + return mVibratorInfo.isPrimitiveSupported( + VibrationEffect.Composition.PRIMITIVE_CLICK); + case HapticFeedbackConstants.KEYBOARD_RELEASE: + return mVibratorInfo.isPrimitiveSupported( + VibrationEffect.Composition.PRIMITIVE_TICK); + } + return false; + } + + private static VibrationAttributes createKeyboardVibrationAttributes() { + if (!Flags.keyboardCategoryEnabled()) { + return TOUCH_VIBRATION_ATTRIBUTES; + } + + return new VibrationAttributes.Builder(TOUCH_VIBRATION_ATTRIBUTES) + .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) + .build(); + } + @Nullable private static SparseArray<VibrationEffect> loadHapticCustomizations( Resources res, VibratorInfo vibratorInfo) { @@ -306,8 +374,7 @@ public final class HapticFeedbackVibrationProvider { } } - private static boolean shouldBypassInterruptionPolicy( - int effectId, FeatureFlags viewFeatureFlags) { + private static boolean shouldBypassInterruptionPolicy(int effectId) { switch (effectId) { case HapticFeedbackConstants.SCROLL_TICK: case HapticFeedbackConstants.SCROLL_ITEM_FOCUS: @@ -315,7 +382,7 @@ public final class HapticFeedbackVibrationProvider { // The SCROLL_* constants should bypass interruption filter, so that scroll haptics // can play regardless of focus modes like DND. Guard this behavior by the feature // flag controlling the general scroll feedback APIs. - return viewFeatureFlags.scrollFeedbackApi(); + return android.view.flags.Flags.scrollFeedbackApi(); default: return false; } diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index db8a9ae553cd..1d5cac54d12b 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -16,6 +16,7 @@ package com.android.server.vibrator; +import static android.os.VibrationAttributes.CATEGORY_KEYBOARD; import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY; import static android.os.VibrationAttributes.USAGE_ALARM; import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST; @@ -52,6 +53,7 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.Vibrator.VibrationIntensity; +import android.os.vibrator.Flags; import android.os.vibrator.VibrationConfig; import android.provider.Settings; import android.util.IndentingPrintWriter; @@ -188,6 +190,8 @@ final class VibrationSettings { @GuardedBy("mLock") private boolean mVibrateOn; @GuardedBy("mLock") + private boolean mKeyboardVibrationOn; + @GuardedBy("mLock") private int mRingerMode; @GuardedBy("mLock") private boolean mOnWirelessCharger; @@ -295,6 +299,8 @@ final class VibrationSettings { Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY)); registerSettingsObserver( Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY)); + registerSettingsObserver( + Settings.System.getUriFor(Settings.System.KEYBOARD_VIBRATION_ENABLED)); if (mVibrationConfig.ignoreVibrationsOnWirelessCharger()) { Intent batteryStatus = mContext.registerReceiver( @@ -418,14 +424,9 @@ final class VibrationSettings { } if (!callerInfo.attrs.isFlagSet( - VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) { - if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) { - return Vibration.Status.IGNORED_FOR_SETTINGS; - } - - if (getCurrentIntensity(usage) == Vibrator.VIBRATION_INTENSITY_OFF) { - return Vibration.Status.IGNORED_FOR_SETTINGS; - } + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) + && !shouldVibrateForUserSetting(callerInfo)) { + return Vibration.Status.IGNORED_FOR_SETTINGS; } if (!callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) { @@ -497,6 +498,30 @@ final class VibrationSettings { return mRingerMode != AudioManager.RINGER_MODE_SILENT; } + /** + * Return {@code true} if the device should vibrate for user setting, and + * {@code false} to ignore the vibration. + */ + @GuardedBy("mLock") + private boolean shouldVibrateForUserSetting(Vibration.CallerInfo callerInfo) { + final int usage = callerInfo.attrs.getUsage(); + if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) { + // Main setting disabled. + return false; + } + + if (Flags.keyboardCategoryEnabled()) { + int category = callerInfo.attrs.getCategory(); + if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) { + // Keyboard touch has a different user setting. + return mKeyboardVibrationOn; + } + } + + // Apply individual user setting based on usage. + return getCurrentIntensity(usage) != Vibrator.VIBRATION_INTENSITY_OFF; + } + /** Update all cached settings and triggers registered listeners. */ void update() { updateSettings(); @@ -508,6 +533,8 @@ final class VibrationSettings { synchronized (mLock) { mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0; mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0; + mKeyboardVibrationOn = loadSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, + mVibrationConfig.isDefaultKeyboardVibrationEnabled() ? 1 : 0) > 0; int alarmIntensity = toIntensity( loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1), diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 45bd1521bc35..ace7777c9b58 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -97,7 +97,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { new VibrationAttributes.Builder().build(); private static final int ATTRIBUTES_ALL_BYPASS_FLAGS = VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY - | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; + | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF + | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE; /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */ private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000; @@ -771,8 +772,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private Vibration.EndInfo startVibrationLocked(HalVibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked"); try { - // Scale effect before dispatching it to the input devices or the vibration thread. - vib.scaleEffects(mVibrationScaler::scale); + if (!vib.callerInfo.attrs.isFlagSet( + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) { + // Scale effect before dispatching it to the input devices or the vibration thread. + vib.scaleEffects(mVibrationScaler::scale); + } boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.callerInfo, vib.getEffectToPlay()); if (inputDevicesAvailable) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 0718f2f284a5..4200fbf39ade 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -3982,7 +3982,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (wallpaper == null) { // common case, this is the first lookup post-boot of the system or // unified lock, so we bring up the saved state lazily now and recheck. - int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM; + // if we're loading the system wallpaper for the first time, also load the lock + // wallpaper to determine if the system wallpaper is system+lock or system only. + int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM | FLAG_LOCK; loadSettingsLocked(userId, false, whichLoad); wallpaper = whichSet.get(userId); if (wallpaper == null) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0b673211a1c9..c02178506052 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -68,6 +68,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; @@ -3001,7 +3002,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { || mLastResumedActivity == null) { return; } - var userInfo = mUserManager.getUserInfo(mLastResumedActivity.mUserId); + var userInfo = getUserManager().getUserInfo(mLastResumedActivity.mUserId); if (userInfo == null || !userInfo.isManagedProfile()) { return; } @@ -3686,6 +3687,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { getTransitionController(), mWindowManager.mSyncEngine) : null; + if (r.getTaskFragment() != null && r.getTaskFragment().isEmbeddedWithBoundsOverride() + && transition != null) { + transition.addFlag(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); + } + final Runnable enterPipRunnable = () -> { synchronized (mGlobalLock) { if (r.getParent() == null) { diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 42376685498a..6d59b297de48 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -276,6 +276,10 @@ class BackNavigationController { // activity, we won't close the activity. backType = BackNavigationInfo.TYPE_DIALOG_CLOSE; removedWindowContainer = window; + } else if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) { + // skip if current activity is translucent + backType = BackNavigationInfo.TYPE_CALLBACK; + removedWindowContainer = window; } else if (prevActivity != null) { if (!isOccluded || prevActivity.canShowWhenLocked()) { // We have another Activity in the same currentTask to go to diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index 64a230effb38..823fbc9b0f3e 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -28,6 +28,9 @@ import com.android.window.flags.Flags; * black layers of varying opacity at various Z-levels which create the effect of a Dim. */ public abstract class Dimmer { + + static final boolean DIMMER_REFACTOR = Flags.dimmerRefactor(); + /** * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the * host, some controller of it, or one of the hosts children. @@ -40,7 +43,7 @@ public abstract class Dimmer { // Constructs the correct type of dimmer static Dimmer create(WindowContainer host) { - return Flags.dimmerRefactor() ? new SmoothDimmer(host) : new LegacyDimmer(host); + return DIMMER_REFACTOR ? new SmoothDimmer(host) : new LegacyDimmer(host); } @NonNull @@ -48,32 +51,34 @@ public abstract class Dimmer { return mHost; } - protected abstract void dim( - WindowContainer container, int relativeLayer, float alpha, int blurRadius); - /** - * Place a dim above the given container, which should be a child of the host container. - * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset - * and the child should call dimAbove again to request the Dim to continue. + * Position the dim relatively to the dimming container. + * Normally called together with #setAppearance, it can be called alone to keep the dim parented + * to a visible container until the next dimming container is ready. + * If multiple containers call this method, only the changes relative to the topmost will be + * applied. * - * @param container The container which to dim above. Should be a child of our host. - * @param alpha The alpha at which to Dim. + * For each call to {@link WindowContainer#prepareSurfaces()} the DimState will be reset, and + * the child of the host should call adjustRelativeLayer and {@link Dimmer#adjustAppearance} to + * continue dimming. Indeed, this method won't be able to keep dimming or get a new DimState + * without also adjusting the appearance. + * @param container The container which to dim above. Should be a child of the host. + * @param relativeLayer The position of the dim wrt the container */ - void dimAbove(@NonNull WindowContainer container, float alpha) { - dim(container, 1, alpha, 0); - } + protected abstract void adjustRelativeLayer(WindowContainer container, int relativeLayer); /** - * Like {@link #dimAbove} but places the dim below the given container. - * - * @param container The container which to dim below. Should be a child of our host. - * @param alpha The alpha at which to Dim. - * @param blurRadius The amount of blur added to the Dim. + * Set the aspect of the dim layer, and request to keep dimming. + * For each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset, and the + * child should call setAppearance again to request the Dim to continue. + * If multiple containers call this method, only the changes relative to the topmost will be + * applied. + * @param container Container requesting the dim + * @param alpha Dim amount + * @param blurRadius Blur amount */ - - void dimBelow(@NonNull WindowContainer container, float alpha, int blurRadius) { - dim(container, -1, alpha, blurRadius); - } + protected abstract void adjustAppearance( + WindowContainer container, float alpha, int blurRadius); /** * Mark all dims as pending completion on the next call to {@link #updateDims} diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 02f5c217e5d8..cd114fcf9e21 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -68,6 +68,7 @@ class InsetsSourceProvider { private final Rect mTmpRect = new Rect(); private final InsetsStateController mStateController; private final InsetsSourceControl mFakeControl; + private final Consumer<Transaction> mSetLeashPositionConsumer; private @Nullable InsetsSourceControl mControl; private @Nullable InsetsControlTarget mControlTarget; private @Nullable InsetsControlTarget mPendingControlTarget; @@ -85,16 +86,7 @@ class InsetsSourceProvider { private boolean mInsetsHintStale = true; private @Flags int mFlagsFromFrameProvider; private @Flags int mFlagsFromServer; - - private final Consumer<Transaction> mSetLeashPositionConsumer = t -> { - if (mControl != null) { - final SurfaceControl leash = mControl.getLeash(); - if (leash != null) { - final Point position = mControl.getSurfacePosition(); - t.setPosition(leash, position.x, position.y); - } - } - }; + private boolean mHasPendingPosition; /** The visibility override from the current controlling window. */ private boolean mClientVisible; @@ -129,6 +121,21 @@ class InsetsSourceProvider { source.getId(), source.getType(), null /* leash */, false /* initialVisible */, new Point(), Insets.NONE); mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0; + mSetLeashPositionConsumer = t -> { + if (mControl != null) { + final SurfaceControl leash = mControl.getLeash(); + if (leash != null) { + final Point position = mControl.getSurfacePosition(); + t.setPosition(leash, position.x, position.y); + } + } + if (mHasPendingPosition) { + mHasPendingPosition = false; + if (mPendingControlTarget != mControlTarget) { + mStateController.notifyControlTargetChanged(mPendingControlTarget, this); + } + } + }; } InsetsSource getSource() { @@ -185,9 +192,8 @@ class InsetsSourceProvider { mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this); if (mControllable) { mWindowContainer.setControllableInsetProvider(this); - if (mPendingControlTarget != null) { + if (mPendingControlTarget != mControlTarget) { updateControlForTarget(mPendingControlTarget, true /* force */); - mPendingControlTarget = null; } } } @@ -344,6 +350,7 @@ class InsetsSourceProvider { changed = true; if (windowState != null && windowState.getWindowFrames().didFrameSizeChange() && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) { + mHasPendingPosition = true; windowState.applyWithNextDraw(mSetLeashPositionConsumer); } else { Transaction t = mWindowContainer.getSyncTransaction(); @@ -465,18 +472,23 @@ class InsetsSourceProvider { // to control the window for now. return; } + mPendingControlTarget = target; if (mWindowContainer != null && mWindowContainer.getSurfaceControl() == null) { // if window doesn't have a surface, set it null and return. setWindowContainer(null, null, null); } if (mWindowContainer == null) { - mPendingControlTarget = target; return; } if (target == mControlTarget && !force) { return; } + if (mHasPendingPosition) { + // Don't create a new leash while having a pending position. Otherwise, the position + // will be changed earlier than expected, which can cause flicker. + return; + } if (target == null) { // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields. mWindowContainer.cancelAnimation(); @@ -618,6 +630,7 @@ class InsetsSourceProvider { } pw.print(prefix); pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching); + pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition); pw.println(); if (mWindowContainer != null) { pw.print(prefix + "mWindowContainer="); @@ -631,7 +644,7 @@ class InsetsSourceProvider { pw.print(prefix + "mControlTarget="); pw.println(mControlTarget); } - if (mPendingControlTarget != null) { + if (mPendingControlTarget != mControlTarget) { pw.print(prefix + "mPendingControlTarget="); pw.println(mPendingControlTarget); } @@ -652,7 +665,8 @@ class InsetsSourceProvider { if (mControlTarget != null && mControlTarget.getWindow() != null) { mControlTarget.getWindow().dumpDebug(proto, CONTROL_TARGET, logLevel); } - if (mPendingControlTarget != null && mPendingControlTarget.getWindow() != null) { + if (mPendingControlTarget != null && mPendingControlTarget != mControlTarget + && mPendingControlTarget.getWindow() != null) { mPendingControlTarget.getWindow().dumpDebug(proto, PENDING_CONTROL_TARGET, logLevel); } if (mFakeControlTarget != null && mFakeControlTarget.getWindow() != null) { diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 081ebe0e7cbd..c4d01291f558 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -278,6 +278,12 @@ class InsetsStateController { notifyPendingInsetsControlChanged(); } + void notifyControlTargetChanged(@Nullable InsetsControlTarget target, + InsetsSourceProvider provider) { + onControlTargetChanged(provider, target, false /* fake */); + notifyPendingInsetsControlChanged(); + } + void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget, InsetsSourceProvider provider) { removeFromControlMaps(previousControlTarget, provider, false /* fake */); diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java index ccf956ecef1e..3265e605ab15 100644 --- a/services/core/java/com/android/server/wm/LegacyDimmer.java +++ b/services/core/java/com/android/server/wm/LegacyDimmer.java @@ -134,8 +134,9 @@ public class LegacyDimmer extends Dimmer { boolean mAnimateExit = true; /** - * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for - * details on Dim lifecycle. + * Used for Dims not associated with a WindowContainer. + * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim + * lifecycle. */ boolean mDontReset; SurfaceAnimator mSurfaceAnimator; @@ -218,9 +219,8 @@ public class LegacyDimmer extends Dimmer { } @Override - protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) { + protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) { final DimState d = obtainDimState(container); - if (d == null) { return; } @@ -229,14 +229,21 @@ public class LegacyDimmer extends Dimmer { // in the correct Z from lowest Z to highest. This ensures that the dim layer is always // relative to the highest Z layer with a dim. SurfaceControl.Transaction t = mHost.getPendingTransaction(); - t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); t.setAlpha(d.mDimLayer, alpha); t.setBackgroundBlurRadius(d.mDimLayer, blurRadius); - d.mDimming = true; } @Override + protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) { + final DimState d = mDimState; + if (d != null) { + SurfaceControl.Transaction t = mHost.getPendingTransaction(); + t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); + } + } + + @Override boolean updateDims(SurfaceControl.Transaction t) { if (mDimState == null) { return false; diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS index f6c364008c62..f8c39d0906a0 100644 --- a/services/core/java/com/android/server/wm/OWNERS +++ b/services/core/java/com/android/server/wm/OWNERS @@ -16,6 +16,7 @@ lihongyu@google.com mariiasand@google.com rgl@google.com yunfanc@google.com +wilsonshih@google.com per-file BackgroundActivityStartController.java = set noparent per-file BackgroundActivityStartController.java = brufino@google.com, topjohnwu@google.com, achim@google.com, ogunwale@google.com, louischang@google.com, lus@google.com diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java index 6ddbd2c8eb67..2549bbf70e9f 100644 --- a/services/core/java/com/android/server/wm/SmoothDimmer.java +++ b/services/core/java/com/android/server/wm/SmoothDimmer.java @@ -63,8 +63,9 @@ class SmoothDimmer extends Dimmer { boolean mAnimateExit = true; /** - * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for - * details on Dim lifecycle. + * Used for Dims not associated with a WindowContainer. + * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim + * lifecycle. */ boolean mDontReset; @@ -105,22 +106,34 @@ class SmoothDimmer extends Dimmer { } void setExitParameters(WindowContainer container) { - setRequestedParameters(container, -1, 0, 0); + setRequestedRelativeParent(container, -1 /* relativeLayer */); + setRequestedAppearance(0f /* alpha */, 0 /* blur */); } + // Sets a requested change without applying it immediately - void setRequestedParameters(WindowContainer container, int relativeLayer, float alpha, - int blurRadius) { - mRequestedProperties.mDimmingContainer = container; + void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) { + mRequestedProperties.mDimmingContainer = relativeParent; mRequestedProperties.mRelativeLayer = relativeLayer; + } + + // Sets a requested change without applying it immediately + void setRequestedAppearance(float alpha, int blurRadius) { mRequestedProperties.mAlpha = alpha; mRequestedProperties.mBlurRadius = blurRadius; } /** * Commit the last changes we received. Called after - * {@link Change#setRequestedParameters(WindowContainer, int, float, int)} + * {@link Change#setExitParameters(WindowContainer)}, + * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or + * {@link Change#setRequestedAppearance(float, int)} */ void applyChanges(SurfaceControl.Transaction t) { + if (mRequestedProperties.mDimmingContainer == null) { + Log.e(TAG, this + " does not have a dimming container. Have you forgotten to " + + "call adjustRelativeLayer?"); + return; + } if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) { Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer + "does not have a surface"); @@ -276,14 +289,19 @@ class SmoothDimmer extends Dimmer { } @Override - protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) { + protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) { final DimState d = obtainDimState(container); - - mDimState.mRequestedProperties.mDimmingContainer = container; - mDimState.setRequestedParameters(container, relativeLayer, alpha, blurRadius); + mDimState.setRequestedAppearance(alpha, blurRadius); d.mDimming = true; } + @Override + protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) { + if (mDimState != null) { + mDimState.setRequestedRelativeParent(container, relativeLayer); + } + } + boolean updateDims(SurfaceControl.Transaction t) { if (mDimState == null) { return false; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 016b0ff55826..4922e9028ed9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -489,10 +489,6 @@ class Task extends TaskFragment { private boolean mForceShowForAllUsers; - /** When set, will force the task to report as invisible. */ - static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; - static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; - private int mForceHiddenFlags = 0; private boolean mForceTranslucent = false; // The display category name for this task. @@ -4495,20 +4491,13 @@ class Task extends TaskFragment { * Sets/unsets the forced-hidden state flag for this task depending on {@param set}. * @return Whether the force hidden state changed */ - boolean setForceHidden(int flags, boolean set) { - int newFlags = mForceHiddenFlags; - if (set) { - newFlags |= flags; - } else { - newFlags &= ~flags; - } - if (mForceHiddenFlags == newFlags) { - return false; - } - + @Override + boolean setForceHidden(@FlagForceHidden int flags, boolean set) { final boolean wasHidden = isForceHidden(); final boolean wasVisible = isVisible(); - mForceHiddenFlags = newFlags; + if (!super.setForceHidden(flags, set)) { + return false; + } final boolean nowHidden = isForceHidden(); if (wasHidden != nowHidden) { final String reason = "setForceHidden"; @@ -4539,11 +4528,6 @@ class Task extends TaskFragment { return super.isAlwaysOnTop(); } - @Override - protected boolean isForceHidden() { - return mForceHiddenFlags != 0; - } - boolean isForceHiddenForPinnedTask() { return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0; } @@ -5651,6 +5635,8 @@ class Task extends TaskFragment { if (noAnimation) { mDisplayContent.prepareAppTransition(TRANSIT_NONE); mTaskSupervisor.mNoAnimActivities.add(top); + mTransitionController.collect(top); + mTransitionController.setNoAnimation(top); ActivityOptions.abort(options); } else { updateTransitLocked(TRANSIT_TO_FRONT, options); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 7c5bc6e3bb28..906b3b55e015 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -362,6 +362,19 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ private boolean mIsolatedNav; + /** When set, will force the task to report as invisible. */ + static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; + static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; + static final int FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG = 1 << 2; + + @IntDef(prefix = {"FLAG_FORCE_HIDDEN_"}, value = { + FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, + FLAG_FORCE_HIDDEN_FOR_TASK_ORG, + FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, + }, flag = true) + @interface FlagForceHidden {} + protected int mForceHiddenFlags = 0; + final Point mLastSurfaceSize = new Point(); private final Rect mTmpBounds = new Rect(); @@ -845,7 +858,25 @@ class TaskFragment extends WindowContainer<WindowContainer> { * Returns whether this TaskFragment is currently forced to be hidden for any reason. */ protected boolean isForceHidden() { - return false; + return mForceHiddenFlags != 0; + } + + /** + * Sets/unsets the forced-hidden state flag for this task depending on {@param set}. + * @return Whether the force hidden state changed + */ + boolean setForceHidden(@FlagForceHidden int flags, boolean set) { + int newFlags = mForceHiddenFlags; + if (set) { + newFlags |= flags; + } else { + newFlags &= ~flags; + } + if (mForceHiddenFlags == newFlags) { + return false; + } + mForceHiddenFlags = newFlags; + return true; } protected boolean isForceTranslucent() { diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 04164c20a372..ff766beee337 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -51,7 +51,6 @@ import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOperation; -import android.window.TaskFragmentOrganizerToken; import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; @@ -745,9 +744,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } - boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) { + boolean isSystemOrganizer(@NonNull IBinder organizerToken) { final TaskFragmentOrganizerState state = - mTaskFragmentOrganizerState.get(token.asBinder()); + mTaskFragmentOrganizerState.get(organizerToken); return state != null && state.mIsSystemOrganizer; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 93db1caf5fdf..7d65c61193b5 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -3321,8 +3321,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mFrozen.add(wc); final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc)); changeInfo.mSnapshot = snapshotSurface; - if (isDisplayRotation) { - // This isn't cheap, so only do it for display rotations. + if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) { + // This isn't cheap, so only do it for rotation change. changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma( buffer, screenshotBuffer.getColorSpace()); } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index de7871e3c231..8ac21e41f7f4 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -757,7 +757,7 @@ class TransitionController { final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType, startTaskInfo, pipTaskInfo, remoteTransition, displayChange, - transition.getFlags()); + transition.getFlags(), transition.getSyncId()); transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos(); transition.mLogger.mRequest = request; @@ -1592,8 +1592,8 @@ class TransitionController { TransitionInfo mInfo; private String buildOnSendLog() { - StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId) - .append(" createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs)); + StringBuilder sb = new StringBuilder("Sent Transition (#").append(mSyncId) + .append(") createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs)); if (mRequest != null) { sb.append(" via request=").append(mRequest); } @@ -1617,7 +1617,8 @@ class TransitionController { void logOnSend() { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog()); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " startWCT=%s", mStartWCT); - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " info=%s", mInfo); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " info=%s", + mInfo.toString(" " /* prefix */)); } private static String toMsString(long nanos) { @@ -1625,8 +1626,8 @@ class TransitionController { } private String buildOnFinishLog() { - StringBuilder sb = new StringBuilder("Finish Transition #").append(mSyncId) - .append(": created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs)); + StringBuilder sb = new StringBuilder("Finish Transition (#").append(mSyncId) + .append("): created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs)); sb.append(" collect-started=").append(toMsString(mCollectTimeNs - mCreateTimeNs)); if (mRequestTimeNs != 0) { sb.append(" request-sent=").append(toMsString(mRequestTimeNs - mCreateTimeNs)); diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 674ff487800f..94e66ffd8373 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -536,7 +536,7 @@ class WallpaperController { window.mWallpaperY = y; window.mWallpaperXStep = xStep; window.mWallpaperYStep = yStep; - updateWallpaperOffsetLocked(window, true); + updateWallpaperOffsetLocked(window, !mService.mFlags.mWallpaperOffsetAsync); } } @@ -561,7 +561,7 @@ class WallpaperController { if (window.mWallpaperDisplayOffsetX != x || window.mWallpaperDisplayOffsetY != y) { window.mWallpaperDisplayOffsetX = x; window.mWallpaperDisplayOffsetY = y; - updateWallpaperOffsetLocked(window, true); + updateWallpaperOffsetLocked(window, !mService.mFlags.mWallpaperOffsetAsync); } } diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 50ef52a4d9dd..1ed14310a500 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -117,7 +117,8 @@ class WallpaperWindowToken extends WindowToken { final WallpaperController wallpaperController = mDisplayContent.mWallpaperController; for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { final WindowState wallpaper = mChildren.get(wallpaperNdx); - if (wallpaperController.updateWallpaperOffset(wallpaper, sync)) { + if (wallpaperController.updateWallpaperOffset(wallpaper, + sync && !mWmService.mFlags.mWallpaperOffsetAsync)) { // We only want to be synchronous with one wallpaper. sync = false; } diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java index 5b9acb2f67c4..46677107c670 100644 --- a/services/core/java/com/android/server/wm/WindowManagerFlags.java +++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java @@ -47,5 +47,7 @@ class WindowManagerFlags { final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag(); + final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync(); + /* 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 9663f3aba7f1..88f72f9dbc90 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8461,16 +8461,18 @@ public class WindowManagerService extends IWindowManager.Stub return true; } // For a task session, find the activity identified by the launch cookie. - final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie( + final WindowContainerInfo wci = getTaskWindowContainerInfoForLaunchCookie( incomingSession.getTokenToRecord()); - if (wct == null) { + if (wci == null) { Slog.w(TAG, "Handling a new recording session; unable to find the " + "WindowContainerToken"); return false; } // Replace the launch cookie in the session details with the task's // WindowContainerToken. - incomingSession.setTokenToRecord(wct.asBinder()); + incomingSession.setTokenToRecord(wci.getToken().asBinder()); + // Also replace the UNKNOWN target UID with the actual UID. + incomingSession.setTargetUid(wci.getUid()); mContentRecordingController.setContentRecordingSessionLocked(incomingSession, WindowManagerService.this); return true; @@ -8798,21 +8800,41 @@ public class WindowManagerService extends IWindowManager.Stub mAtmService.setFocusedTask(task.mTaskId, touchedActivity); } + @VisibleForTesting + static class WindowContainerInfo { + private final int mUid; + @NonNull private final WindowContainerToken mToken; + + private WindowContainerInfo(int uid, @NonNull WindowContainerToken token) { + this.mUid = uid; + this.mToken = token; + } + + public int getUid() { + return mUid; + } + + @NonNull + public WindowContainerToken getToken() { + return mToken; + } + } + /** - * Retrieve the {@link WindowContainerToken} of the task that contains the activity started - * with the given launch cookie. + * Retrieve the {@link WindowContainerInfo} of the task that contains the activity started with + * the given launch cookie. * * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an - * activity + * activity * @return a token representing the task containing the activity started with the given launch - * cookie, or {@code null} if the token couldn't be found. + * cookie, or {@code null} if the token couldn't be found. */ @VisibleForTesting @Nullable - WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) { + WindowContainerInfo getTaskWindowContainerInfoForLaunchCookie(@NonNull IBinder launchCookie) { // Find the activity identified by the launch cookie. - final ActivityRecord targetActivity = mRoot.getActivity( - activity -> activity.mLaunchCookie == launchCookie); + final ActivityRecord targetActivity = + mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie); if (targetActivity == null) { Slog.w(TAG, "Unable to find the activity for this launch cookie"); return null; @@ -8827,7 +8849,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName()); return null; } - return taskWindowContainerToken; + return new WindowContainerInfo(targetActivity.getUid(), taskWindowContainerToken); } /** diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index dd9a88f72bde..5ed6caffe1fb 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -24,7 +24,9 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; @@ -34,6 +36,8 @@ import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATI import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS; import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN; +import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE; +import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN; import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION; @@ -61,6 +65,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; +import static com.android.server.wm.TaskFragment.FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -821,6 +826,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return TRANSACT_EFFECTS_NONE; } + int effects = TRANSACT_EFFECTS_NONE; // When the TaskFragment is resized, we may want to create a change transition for it, for // which we want to defer the surface update until we determine whether or not to start // change transition. @@ -843,7 +849,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub c.getConfiguration().windowConfiguration.setBounds(absBounds); taskFragment.setRelativeEmbeddedBounds(relBounds); } - final int effects = applyChanges(taskFragment, c); + if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) { + if (taskFragment.setForceHidden( + FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, c.getHidden())) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + } + effects |= applyChanges(taskFragment, c); + if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) { taskFragment.initializeChangeTransition(mTmpBounds0); } @@ -1393,6 +1406,24 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub taskFragment.setIsolatedNav(isolatedNav); break; } + case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: { + final Task task = taskFragment.getTask(); + if (task != null) { + task.mChildren.remove(taskFragment); + task.mChildren.add(0, taskFragment); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + break; + } + case OP_TYPE_REORDER_TO_TOP_OF_TASK: { + final Task task = taskFragment.getTask(); + if (task != null) { + task.mChildren.remove(taskFragment); + task.mChildren.add(taskFragment); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + break; + } } return effects; } @@ -1420,6 +1451,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } + if ((opType == OP_TYPE_REORDER_TO_BOTTOM_OF_TASK + || opType == OP_TYPE_REORDER_TO_TOP_OF_TASK) + && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { + final Throwable exception = new SecurityException( + "Only a system organizer can perform OP_TYPE_REORDER_TO_BOTTOM_OF_TASK or " + + "OP_TYPE_REORDER_TO_TOP_OF_TASK." + ); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, + opType, exception); + return false; + } + final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken(); return secondaryFragmentToken == null || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType, @@ -1920,6 +1963,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub * For config change on {@link TaskFragment}, we only support the following operations: * {@link WindowContainerTransaction#setRelativeBounds(WindowContainerToken, Rect)}, * {@link WindowContainerTransaction#setWindowingMode(WindowContainerToken, int)}. + * + * For a system organizer, we additionally support + * {@link WindowContainerTransaction#setHidden(WindowContainerToken, boolean)}, and + * {@link WindowContainerTransaction#setFocusable(WindowContainerToken, boolean)}. See + * {@link TaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer, boolean)} */ private void enforceTaskFragmentConfigChangeAllowed(@NonNull String func, @Nullable WindowContainer wc, @NonNull WindowContainerTransaction.Change change, @@ -1938,31 +1986,49 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new SecurityException(msg); } - final int changeMask = change.getChangeMask(); - final int configSetMask = change.getConfigSetMask(); - final int windowSetMask = change.getWindowSetMask(); - if (changeMask == 0 && configSetMask == 0 && windowSetMask == 0 - && change.getWindowingMode() >= 0) { - // The change contains only setWindowingMode, which is allowed. - return; + final int originalChangeMask = change.getChangeMask(); + final int originalConfigSetMask = change.getConfigSetMask(); + final int originalWindowSetMask = change.getWindowSetMask(); + + int changeMaskToBeChecked = originalChangeMask; + int configSetMaskToBeChecked = originalConfigSetMask; + int windowSetMaskToBeChecked = originalWindowSetMask; + + if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { + // System organizer is allowed to update the hidden and focusable state. + // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here. + changeMaskToBeChecked &= ~CHANGE_HIDDEN; + changeMaskToBeChecked &= ~CHANGE_FOCUSABLE; } - if (changeMask != CHANGE_RELATIVE_BOUNDS - || configSetMask != ActivityInfo.CONFIG_WINDOW_CONFIGURATION - || windowSetMask != WindowConfiguration.WINDOW_CONFIG_BOUNDS) { - // None of the change should be requested from a TaskFragment organizer except - // setRelativeBounds and setWindowingMode. - // For setRelativeBounds, we don't need to check whether it is outside of the Task + + // setRelativeBounds is allowed. + if ((changeMaskToBeChecked & CHANGE_RELATIVE_BOUNDS) != 0 + && (configSetMaskToBeChecked & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0 + && (windowSetMaskToBeChecked & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) { + // For setRelativeBounds, we don't need to check whether it is outside the Task // bounds, because it is possible that the Task is also resizing, for which we don't // want to throw an exception. The bounds will be adjusted in // TaskFragment#translateRelativeBoundsToAbsoluteBounds. - String msg = "Permission Denial: " + func + " from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " trying to apply changes of changeMask=" + changeMask - + " configSetMask=" + configSetMask + " windowSetMask=" + windowSetMask - + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer; - Slog.w(TAG, msg); - throw new SecurityException(msg); + changeMaskToBeChecked &= ~CHANGE_RELATIVE_BOUNDS; + configSetMaskToBeChecked &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION; + windowSetMaskToBeChecked &= ~WindowConfiguration.WINDOW_CONFIG_BOUNDS; } + + if (changeMaskToBeChecked == 0 && configSetMaskToBeChecked == 0 + && windowSetMaskToBeChecked == 0) { + // All the changes have been checked. + // Note that setWindowingMode is always allowed, so we don't need to check the mask. + return; + } + + final String msg = "Permission Denial: " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " trying to apply changes of changeMask=" + originalChangeMask + + " configSetMask=" + originalConfigSetMask + + " windowSetMask=" + originalWindowSetMask + + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer; + Slog.w(TAG, msg); + throw new SecurityException(msg); } private void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, @@ -2019,7 +2085,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer(); taskFragment.setTaskFragmentOrganizer(organizerToken, ownerActivity.getUid(), ownerActivity.info.processName, - mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken)); + mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder())); final int position; if (creationParams.getPairedPrimaryFragmentToken() != null) { // When there is a paired primary TaskFragment, we want to place the new TaskFragment diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 7f36aec69480..3a793e921c68 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5136,8 +5136,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private void applyDims() { if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind()) - && mToken.isVisibleRequested() && isVisibleNow() && !mHidden - && mTransitionController.canApplyDim(getTask())) { + && (Dimmer.DIMMER_REFACTOR ? mWinAnimator.getShown() : isVisibleNow()) + && !mHidden && mTransitionController.canApplyDim(getTask())) { // Only show the Dimmer when the following is satisfied: // 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested // 2. The WindowToken is not hidden so dims aren't shown when the window is exiting. @@ -5147,7 +5147,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mIsDimming = true; final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? mAttrs.dimAmount : 0; final int blurRadius = shouldDrawBlurBehind() ? mAttrs.getBlurBehindRadius() : 0; - getDimmer().dimBelow(this, dimAmount, blurRadius); + // If the window is visible from surface flinger perspective (mWinAnimator.getShown()) + // but not window manager visible (!isVisibleNow()), it can still be the parent of the + // dim, but can not create a new surface or continue a dim alone. + if (isVisibleNow()) { + getDimmer().adjustAppearance(this, dimAmount, blurRadius); + } + getDimmer().adjustRelativeLayer(this, -1 /* relativeLayer */); } } @@ -5207,12 +5213,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void prepareSurfaces() { mIsDimming = false; if (mHasSurface) { - applyDims(); + if (!Dimmer.DIMMER_REFACTOR) { + applyDims(); + } updateSurfacePositionNonOrganized(); // Send information to SurfaceFlinger about the priority of the current window. updateFrameRateSelectionPriorityIfNeeded(); updateScaleIfNeeded(); mWinAnimator.prepareSurfaceLocked(getSyncTransaction()); + if (Dimmer.DIMMER_REFACTOR) { + applyDims(); + } } super.prepareSurfaces(); } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index e434f296bbb5..7d21dbf85a66 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -569,6 +569,7 @@ class WindowToken extends WindowContainer<WindowState> { && asActivityRecord() != null && isVisible()) { // Trigger an activity level rotation transition. mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this); + mTransitionController.collectVisibleChange(this); mTransitionController.setReady(this); } final int originalRotation = getWindowConfiguration().getRotation(); diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 709d5e39463c..24ee16389fd2 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -189,7 +189,7 @@ cc_defaults { "android.hardware.thermal@1.0", "android.hardware.thermal-V1-ndk", "android.hardware.tv.input@1.0", - "android.hardware.tv.input-V1-ndk", + "android.hardware.tv.input-V2-ndk", "android.hardware.vibrator-V2-cpp", "android.hardware.vibrator@1.0", "android.hardware.vibrator@1.1", @@ -244,5 +244,5 @@ filegroup { filegroup { name: "lib_oomConnection_native", - srcs: ["com_android_server_am_OomConnection.cpp",], + srcs: ["com_android_server_am_OomConnection.cpp"], } diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp index b256f168f2af..1844d3063cd8 100644 --- a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp +++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp @@ -24,33 +24,33 @@ #include "utils/Log.h" namespace android { -static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids, +static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray jappIds, jfloatArray jthresholds) { - if (juids == nullptr || jthresholds == nullptr) return; + if (jappIds == nullptr || jthresholds == nullptr) return; - ScopedIntArrayRO uids(env, juids); + ScopedIntArrayRO appIds(env, jappIds); ScopedFloatArrayRO thresholds(env, jthresholds); - if (uids.size() != thresholds.size()) { - ALOGE("uids size exceeds thresholds size!"); + if (appIds.size() != thresholds.size()) { + ALOGE("appIds size exceeds thresholds size!"); return; } - std::vector<int32_t> uidVector; + std::vector<int32_t> appIdVector; std::vector<float> thresholdVector; - size_t size = uids.size(); - uidVector.reserve(size); + size_t size = appIds.size(); + appIdVector.reserve(size); thresholdVector.reserve(size); for (int i = 0; i < size; i++) { - uidVector.push_back(static_cast<int32_t>(uids[i])); + appIdVector.push_back(static_cast<int32_t>(appIds[i])); thresholdVector.push_back(static_cast<float>(thresholds[i])); } - SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector); + SurfaceComposerClient::updateSmallAreaDetection(appIdVector, thresholdVector); } -static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid, +static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint appId, jfloat threshold) { - SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold); + SurfaceComposerClient::setSmallAreaDetectionThreshold(appId, threshold); } static const JNINativeMethod gMethods[] = { diff --git a/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp index fc5a11360c8c..1e3cfd049e65 100644 --- a/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp +++ b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp @@ -15,106 +15,99 @@ */ #include <android_runtime/AndroidRuntime.h> -#include <nativehelper/JNIHelp.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> #include <jni.h> +#include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> - -#include <utils/misc.h> +#include <string.h> #include <sys/ioctl.h> #include <sys/mount.h> #include <utils/Log.h> +#include <utils/misc.h> +namespace android { -#include <inttypes.h> -#include <fcntl.h> -#include <errno.h> -#include <string.h> +uint64_t get_block_device_size(int fd) { + uint64_t size = 0; + int ret; -namespace android { + ret = ioctl(fd, BLKGETSIZE64, &size); - uint64_t get_block_device_size(int fd) - { - uint64_t size = 0; - int ret; + if (ret) return 0; - ret = ioctl(fd, BLKGETSIZE64, &size); + return size; +} - if (ret) - return 0; +int wipe_block_device(int fd) { + uint64_t range[2]; + int ret; + uint64_t len = get_block_device_size(fd); - return size; - } + range[0] = 0; + range[1] = len; - int wipe_block_device(int fd) - { - uint64_t range[2]; - int ret; - uint64_t len = get_block_device_size(fd); + if (range[1] == 0) return 0; + ret = ioctl(fd, BLKSECDISCARD, &range); + if (ret < 0) { + ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno)); range[0] = 0; range[1] = len; - - if (range[1] == 0) - return 0; - - ret = ioctl(fd, BLKSECDISCARD, &range); + ret = ioctl(fd, BLKDISCARD, &range); if (ret < 0) { - ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno)); - range[0] = 0; - range[1] = len; - ret = ioctl(fd, BLKDISCARD, &range); - if (ret < 0) { - ALOGE("Discard failed: %s\n", strerror(errno)); - return -1; - } else { - ALOGE("Wipe via secure discard failed, used non-secure discard instead\n"); - return 0; - } - + ALOGE("Discard failed: %s\n", strerror(errno)); + return -1; + } else { + ALOGE("Wipe via secure discard failed, used non-secure discard instead\n"); + return 0; } - - return ret; } - static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath) - { - ScopedUtfChars path(env, jpath); - int fd = open(path.c_str(), O_RDONLY); + return ret; +} - if (fd < 0) - return 0; +static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, + jclass, + jstring jpath) { + ScopedUtfChars path(env, jpath); + int fd = open(path.c_str(), O_RDONLY); - const uint64_t size = get_block_device_size(fd); + if (fd < 0) return 0; - close(fd); + const uint64_t size = get_block_device_size(fd); - return size; - } + close(fd); - static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) { - ScopedUtfChars path(env, jpath); - int fd = open(path.c_str(), O_WRONLY); + return size; +} - if (fd < 0) - return 0; +static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass, + jstring jpath) { + ScopedUtfChars path(env, jpath); + int fd = open(path.c_str(), O_WRONLY); - const int ret = wipe_block_device(fd); + if (fd < 0) return 0; - close(fd); + const int ret = wipe_block_device(fd); - return ret; - } + close(fd); - static const JNINativeMethod sMethods[] = { - /* name, signature, funcPtr */ - {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize}, - {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_pdb_PersistentDataBlockService_wipe}, - }; + return ret; +} - int register_android_server_pdb_PersistentDataBlockService(JNIEnv* env) - { - return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService", - sMethods, NELEM(sMethods)); - } +static const JNINativeMethod sMethods[] = { + /* name, signature, funcPtr */ + {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", + (void *)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize}, + {"nativeWipe", "(Ljava/lang/String;)I", + (void *)com_android_server_pdb_PersistentDataBlockService_wipe}, +}; + +int register_android_server_pdb_PersistentDataBlockService(JNIEnv *env) { + return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService", + sMethods, NELEM(sMethods)); +} } /* namespace android */
\ No newline at end of file diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp index c7366173ecda..dc05462c1a1c 100644 --- a/services/core/jni/tvinput/JTvInputHal.cpp +++ b/services/core/jni/tvinput/JTvInputHal.cpp @@ -368,12 +368,20 @@ JTvInputHal::TvInputEventWrapper JTvInputHal::TvInputEventWrapper::createEventWr } JTvInputHal::TvMessageEventWrapper JTvInputHal::TvMessageEventWrapper::createEventWrapper( - const AidlTvMessageEvent& aidlTvMessageEvent) { + const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage) { + auto messageList = aidlTvMessageEvent.messages; TvMessageEventWrapper event; - event.messages.insert(event.messages.begin(), std::begin(aidlTvMessageEvent.messages) + 1, - std::end(aidlTvMessageEvent.messages)); + // Handle backwards compatibility for V1 + if (isLegacyMessage) { + event.deviceId = messageList[0].groupId; + event.messages.insert(event.messages.begin(), std::begin(messageList) + 1, + std::end(messageList)); + } else { + event.deviceId = aidlTvMessageEvent.deviceId; + event.messages.insert(event.messages.begin(), std::begin(messageList), + std::end(messageList)); + } event.streamId = aidlTvMessageEvent.streamId; - event.deviceId = aidlTvMessageEvent.messages[0].groupId; event.type = aidlTvMessageEvent.type; return event; } @@ -449,15 +457,30 @@ JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) { ::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notifyTvMessageEvent( const AidlTvMessageEvent& event) { const std::string DEVICE_ID_SUBTYPE = "device_id"; - if (event.messages.size() > 1 && event.messages[0].subType == DEVICE_ID_SUBTYPE) { - mHal->mLooper - ->sendMessage(new NotifyTvMessageHandler(mHal, - TvMessageEventWrapper::createEventWrapper( - event)), - static_cast<int>(event.type)); + ::ndk::ScopedAStatus status = ::ndk::ScopedAStatus::ok(); + int32_t aidlVersion = 0; + if (mHal->mTvInput->getAidlInterfaceVersion(&aidlVersion).isOk() && event.messages.size() > 0) { + bool validLegacyMessage = aidlVersion == 1 && + event.messages[0].subType == DEVICE_ID_SUBTYPE && event.messages.size() > 1; + bool validTvMessage = aidlVersion > 1 && event.messages.size() > 0; + if (validLegacyMessage || validTvMessage) { + mHal->mLooper->sendMessage( + new NotifyTvMessageHandler(mHal, + TvMessageEventWrapper:: + createEventWrapper(event, + validLegacyMessage)), + static_cast<int>(event.type)); + } else { + status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + ALOGE("The TVMessage event was malformed for HAL version: %d", aidlVersion); + } + } else { + status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + ALOGE("The TVMessage event was empty or the HAL version (version: %d) could not " + "be inferred.", + aidlVersion); } - - return ::ndk::ScopedAStatus::ok(); + return status; } JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput) @@ -521,4 +544,12 @@ JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aid } } +::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getAidlInterfaceVersion(int32_t* _aidl_return) { + if (mIsHidl) { + return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } else { + return mAidlTvInput->getInterfaceVersion(_aidl_return); + } +} + } // namespace android diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h index 1d8d1629f67d..6026a107c67f 100644 --- a/services/core/jni/tvinput/JTvInputHal.h +++ b/services/core/jni/tvinput/JTvInputHal.h @@ -138,7 +138,7 @@ private: TvMessageEventWrapper() {} static TvMessageEventWrapper createEventWrapper( - const AidlTvMessageEvent& aidlTvMessageEvent); + const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage); int streamId; int deviceId; @@ -195,6 +195,7 @@ private: ::ndk::ScopedAStatus getTvMessageQueueDesc( MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue, int32_t in_deviceId, int32_t in_streamId); + ::ndk::ScopedAStatus getAidlInterfaceVersion(int32_t* _aidl_return); private: ::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index c26aee8af83e..924e2f8ec654 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -338,6 +338,8 @@ public final class SystemServer implements Dumpable { "com.android.clockwork.modes.ModeManagerService"; private static final String WEAR_DISPLAY_SERVICE_CLASS = "com.android.clockwork.display.WearDisplayService"; + private static final String WEAR_DEBUG_SERVICE_CLASS = + "com.android.clockwork.debug.WearDebugService"; private static final String WEAR_TIME_SERVICE_CLASS = "com.android.clockwork.time.WearTimeService"; private static final String WEAR_SETTINGS_SERVICE_CLASS = @@ -2636,6 +2638,12 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS); t.traceEnd(); + if (Build.IS_DEBUGGABLE) { + t.traceBegin("StartWearDebugService"); + mSystemServiceManager.startService(WEAR_DEBUG_SERVICE_CLASS); + t.traceEnd(); + } + t.traceBegin("StartWearTimeService"); mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS); t.traceEnd(); diff --git a/services/midi/Android.bp b/services/midi/Android.bp index 5adcfbaf432e..4b5f8a7bf0ac 100644 --- a/services/midi/Android.bp +++ b/services/midi/Android.bp @@ -19,4 +19,7 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [":services.midi-sources"], libs: ["services.core"], + static_libs: [ + "aconfig_midi_flags_java_lib", + ], } diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index a8902fcf77af..2f47cc7160b7 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -16,6 +16,8 @@ package com.android.server.midi; +import static com.android.media.midi.flags.Flags.virtualUmp; + import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -1549,6 +1551,12 @@ public class MidiService extends IMidiManager.Stub { return; } + if (!virtualUmp()) { + Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName + + ": virtual UMP flag not enabled"); + return; + } + Bundle properties = null; int numPorts = 0; boolean isPrivate = false; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index 952cfc48b583..cbedcaf97358 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -43,6 +43,7 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.PropertyInvalidatedCache; +import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; @@ -873,12 +874,20 @@ public class PackageManagerSettingsTests { .setUid(packageSetting.getAppId()) .hideAsFinal()); - ArchiveState archiveState = new ArchiveState( - List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"), - Path.of("/monochromePath1")), - new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"), - Path.of("/monochromePath2"))), - "installerTitle"); + ArchiveState archiveState = + new ArchiveState( + List.of( + new ArchiveState.ArchiveActivityInfo( + "title1", + new ComponentName("pkg1", "class1"), + Path.of("/path1"), + Path.of("/monochromePath1")), + new ArchiveState.ArchiveActivityInfo( + "title2", + new ComponentName("pkg2", "class2"), + Path.of("/path2"), + Path.of("/monochromePath2"))), + "installerTitle"); packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState( archiveState); settings.mPackages.put(PACKAGE_NAME_1, packageSetting); diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java index 58ae7406580e..87a297b0e86f 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; import android.content.pm.overlay.OverlayPaths; @@ -192,8 +193,8 @@ public class PackageUserStateTest { return new SuspendParams(dialogInfo, appExtras, launcherExtras); } - private static PersistableBundle createPersistableBundle(String lKey, long lValue, String sKey, - String sValue, String dKey, double dValue) { + private static PersistableBundle createPersistableBundle( + String lKey, long lValue, String sKey, String sValue, String dKey, double dValue) { final PersistableBundle result = new PersistableBundle(3); if (lKey != null) { result.putLong("com.unit_test." + lKey, lValue); @@ -320,6 +321,7 @@ public class PackageUserStateTest { assertEquals(0L, state.getLastPackageUsageTimeInMills()[i]); } } + private static void assertLastPackageUsageSet( PackageStateUnserialized state, int reason, long value) throws Exception { for (int i = state.getLastPackageUsageTimeInMills().length - 1; i >= 0; --i) { @@ -330,6 +332,7 @@ public class PackageUserStateTest { } } } + @Test public void testPackageUseReasons() throws Exception { PackageSetting packageSetting = Mockito.mock(PackageSetting.class); @@ -377,6 +380,7 @@ public class PackageUserStateTest { assertTrue(testState.setOverlayPaths(new OverlayPaths.Builder().build())); assertFalse(testState.setOverlayPaths(null)); } + @Test public void testSharedLibOverlayPaths() { final PackageUserStateImpl testState = new PackageUserStateImpl(); @@ -401,8 +405,12 @@ public class PackageUserStateTest { @Test public void archiveState() { PackageUserStateImpl packageUserState = new PackageUserStateImpl(); - ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo( - "appTitle", Path.of("/path1"), Path.of("/path2")); + ArchiveState.ArchiveActivityInfo archiveActivityInfo = + new ArchiveState.ArchiveActivityInfo( + "appTitle", + new ComponentName("pkg", "class"), + Path.of("/path1"), + Path.of("/path2")); ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo), "installerTitle"); packageUserState.setArchiveState(archiveState); 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 d021f1d5aaea..16d72e40fbb5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -117,7 +117,6 @@ 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; @@ -312,7 +311,6 @@ public class DisplayManagerServiceTest { @Mock SensorManager mSensorManager; @Mock DisplayDeviceConfig mMockDisplayDeviceConfig; @Mock PackageManagerInternal mMockPackageManagerInternal; - @Mock UserManagerInternal mMockUserManagerInternal; @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor; @@ -336,8 +334,6 @@ public class DisplayManagerServiceTest { VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); - LocalServices.removeServiceForTest(UserManagerInternal.class); - LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); // TODO: b/287945043 mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResources = Mockito.spy(mContext.getResources()); 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 c4f72b307eb7..6a95d5c57024 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 @@ -102,6 +102,9 @@ import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.testutils.FakeDeviceConfigInterface; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -121,26 +124,28 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import junitparams.JUnitParamsRunner; -import junitparams.Parameters; - @SmallTest @RunWith(JUnitParamsRunner.class) public class DisplayModeDirectorTest { public static Collection<Object[]> getAppRequestedSizeTestCases() { var appRequestedSizeTestCases = Arrays.asList(new Object[][] { - {DEFAULT_MODE_75.getModeId(), Float.POSITIVE_INFINITY, - DEFAULT_MODE_75.getRefreshRate(), Map.of()}, - {APP_MODE_HIGH_90.getModeId(), Float.POSITIVE_INFINITY, - APP_MODE_HIGH_90.getRefreshRate(), - Map.of( + {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(), + /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, + /*expectedAppRequestedRefreshRate*/ DEFAULT_MODE_75.getRefreshRate(), + /*votesWithPriorities*/ Map.of()}, + {/*expectedBaseModeId*/ APP_MODE_HIGH_90.getModeId(), + /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, + /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), APP_MODE_HIGH_90.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()))}, - {LIMIT_MODE_70.getModeId(), Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, - Map.of( + {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), + /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, + /*expectedAppRequestedRefreshRate*/ Float.POSITIVE_INFINITY, + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), APP_MODE_HIGH_90.getPhysicalHeight()), @@ -149,9 +154,10 @@ public class DisplayModeDirectorTest { Vote.PRIORITY_LOW_POWER_MODE, Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight()))}, - {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(), - LIMIT_MODE_70.getRefreshRate(), - Map.of( + {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), + /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_65.getPhysicalWidth(), APP_MODE_65.getPhysicalHeight()), @@ -160,9 +166,10 @@ public class DisplayModeDirectorTest { Vote.PRIORITY_LOW_POWER_MODE, Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight()))}, - {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(), - LIMIT_MODE_70.getRefreshRate(), - Map.of( + {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), + /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_65.getPhysicalWidth(), APP_MODE_65.getPhysicalHeight()), @@ -173,10 +180,12 @@ public class DisplayModeDirectorTest { 0, 0, LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight(), - 0, Float.POSITIVE_INFINITY)), false}, - {APP_MODE_65.getModeId(), APP_MODE_65.getRefreshRate(), - APP_MODE_65.getRefreshRate(), - Map.of( + 0, Float.POSITIVE_INFINITY)), + /*displayResolutionRangeVotingEnabled*/ false}, + {/*expectedBaseModeId*/ APP_MODE_65.getModeId(), + /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_65.getPhysicalWidth(), APP_MODE_65.getPhysicalHeight()), @@ -187,7 +196,40 @@ public class DisplayModeDirectorTest { 0, 0, LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight(), - 0, Float.POSITIVE_INFINITY)), true}}); + 0, Float.POSITIVE_INFINITY)), + /*displayResolutionRangeVotingEnabled*/ true}, + {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(), + /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(), + /*votesWithPriorities*/ Map.of( + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), + APP_MODE_HIGH_90.getPhysicalHeight()), + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forSizeAndPhysicalRefreshRatesRange( + 0, 0, + LIMIT_MODE_70.getPhysicalWidth(), + LIMIT_MODE_70.getPhysicalHeight(), + 0, APP_MODE_65.getRefreshRate())), + /*displayResolutionRangeVotingEnabled*/ false}, + {/*expectedBaseModeId*/ DEFAULT_MODE_60.getModeId(), // Resolution == APP_MODE_65 + /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*votesWithPriorities*/ Map.of( + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), + APP_MODE_HIGH_90.getPhysicalHeight()), + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forSizeAndPhysicalRefreshRatesRange( + 0, 0, + LIMIT_MODE_70.getPhysicalWidth(), + LIMIT_MODE_70.getPhysicalHeight(), + 0, APP_MODE_65.getRefreshRate())), + /*displayResolutionRangeVotingEnabled*/ true}}); final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2); @@ -218,6 +260,8 @@ public class DisplayModeDirectorTest { private static final boolean DEBUG = false; private static final float FLOAT_TOLERANCE = 0.01f; + private static final Display.Mode DEFAULT_MODE_60 = new Display.Mode( + /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60); private static final Display.Mode APP_MODE_65 = new Display.Mode( /*modeId=*/65, /*width=*/1900, /*height=*/1900, 65); private static final Display.Mode LIMIT_MODE_70 = new Display.Mode( @@ -227,8 +271,7 @@ public class DisplayModeDirectorTest { private static final Display.Mode APP_MODE_HIGH_90 = new Display.Mode( /*modeId=*/90, /*width=*/3000, /*height=*/3000, 90); private static final Display.Mode[] TEST_MODES = new Display.Mode[] { - new Display.Mode( - /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60), + DEFAULT_MODE_60, APP_MODE_65, LIMIT_MODE_70, DEFAULT_MODE_75, diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java index 596a3f3d0400..b3605ccfc25d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java @@ -280,7 +280,9 @@ public class AsyncProcessStartTest { 0, 0); // Sleep until timeout should have triggered - SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000); + if (wedge) { + SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000); + } return app; } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 410ae35aa790..367e14b37180 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -228,7 +228,7 @@ public class BroadcastQueueTest { LocalServices.removeServiceForTest(AlarmManagerInternal.class); LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt); doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); - doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt()); + doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any()); doAnswer((invocation) -> { return getUidForPackage(invocation.getArgument(0)); }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM)); @@ -1014,8 +1014,9 @@ public class BroadcastQueueTest { eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER)); // Confirm that we unstopped manifest receivers - verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState( - eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM)); + verify(mAms.mPackageManagerInt, atLeastOnce()).notifyComponentUsed( + eq(receiverApp.info.packageName), eq(UserHandle.USER_SYSTEM), + eq(callerApp.info.packageName), any()); } // Confirm that we've reported expected usage events diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 76b41b70cdf4..c493f8479045 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -2629,7 +2629,7 @@ public class MockingOomAdjusterTests { PROCESS_STATE_NONEXISTENT, PROCESS_STATE_NONEXISTENT, 0, 0, false, false, false, ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE, false, false, false, hasShownUi, false, false, false, false, false, false, null, - 0, 0, 0, true, 0, null, false); + 0, Long.MIN_VALUE, Long.MIN_VALUE, true, 0, null, false); } private ProcessRecord makeProcessRecord(ActivityManagerService service, int pid, int uid, diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java index 1ce79a5b596b..05ac5b5720e6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java @@ -16,8 +16,6 @@ package com.android.server.display; -import static android.os.Process.INVALID_UID; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; @@ -35,7 +33,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; -import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.pkg.PackageStateInternal; import org.junit.Before; import org.junit.Rule; @@ -55,7 +53,10 @@ public class SmallAreaDetectionControllerTest { @Mock private PackageManagerInternal mMockPackageManagerInternal; @Mock - private UserManagerInternal mMockUserManagerInternal; + private PackageStateInternal mMockPkgStateA; + @Mock + private PackageStateInternal mMockPkgStateB; + private SmallAreaDetectionController mSmallAreaDetectionController; @@ -64,29 +65,18 @@ public class SmallAreaDetectionControllerTest { private static final String PKG_NOT_INSTALLED = "com.not.installed"; private static final float THRESHOLD_A = 0.05f; private static final float THRESHOLD_B = 0.07f; - private static final int USER_1 = 110; - private static final int USER_2 = 111; - private static final int UID_A_1 = 11011111; - private static final int UID_A_2 = 11111111; - private static final int UID_B_1 = 11022222; - private static final int UID_B_2 = 11122222; + private static final int APP_ID_A = 11111; + private static final int APP_ID_B = 22222; @Before public void setup() { LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); - LocalServices.removeServiceForTest(UserManagerInternal.class); - LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); - - when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2}); - when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1); - when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2); - when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1); - when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2); - when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn( - INVALID_UID); - when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn( - INVALID_UID); + + when(mMockPackageManagerInternal.getPackageStateInternal(PKG_A)).thenReturn(mMockPkgStateA); + when(mMockPackageManagerInternal.getPackageStateInternal(PKG_B)).thenReturn(mMockPkgStateB); + when(mMockPkgStateA.getAppId()).thenReturn(APP_ID_A); + when(mMockPkgStateB.getAppId()).thenReturn(APP_ID_B); mSmallAreaDetectionController = spy(new SmallAreaDetectionController( new ContextWrapper(ApplicationProvider.getApplicationContext()), @@ -99,9 +89,9 @@ public class SmallAreaDetectionControllerTest { final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B; mSmallAreaDetectionController.updateAllowlist(property); - final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2}; - final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B}; - verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + final int[] resultAppIdArray = {APP_ID_A, APP_ID_B}; + final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray), eq(resultThresholdArray)); } @@ -110,9 +100,9 @@ public class SmallAreaDetectionControllerTest { final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B; mSmallAreaDetectionController.updateAllowlist(property); - final int[] resultUidArray = {UID_B_1, UID_B_2}; - final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B}; - verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + final int[] resultAppIdArray = {APP_ID_B}; + final float[] resultThresholdArray = {THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray), eq(resultThresholdArray)); } @@ -122,9 +112,9 @@ public class SmallAreaDetectionControllerTest { PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B; mSmallAreaDetectionController.updateAllowlist(property); - final int[] resultUidArray = {UID_A_1, UID_A_2}; - final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A}; - verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + final int[] resultAppIdArray = {APP_ID_A}; + final float[] resultThresholdArray = {THRESHOLD_A}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray), eq(resultThresholdArray)); } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index eb50556821eb..610ea903767e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AppOpsManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; @@ -427,6 +428,7 @@ public class PackageArchiverTest { for (LauncherActivityInfo mainActivity : createLauncherActivities()) { ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo( mainActivity.getLabel().toString(), + mainActivity.getComponentName(), ICON_PATH, null); activityInfos.add(activityInfo); } @@ -437,9 +439,11 @@ public class PackageArchiverTest { ActivityInfo activityInfo = mock(ActivityInfo.class); LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class); when(activity1.getLabel()).thenReturn("activity1"); + when(activity1.getComponentName()).thenReturn(new ComponentName("pkg1", "class1")); when(activity1.getActivityInfo()).thenReturn(activityInfo); LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class); when(activity2.getLabel()).thenReturn("activity2"); + when(activity2.getComponentName()).thenReturn(new ComponentName("pkg2", "class2")); when(activity2.getActivityInfo()).thenReturn(activityInfo); return List.of(activity1, activity2); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index 63281b77ade7..71007f53f0e1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -155,7 +155,7 @@ public class AccessibilityUserStateTest { mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString()); mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString()); mUserState.setTouchExplorationEnabledLocked(true); - mUserState.setDisplayMagnificationEnabledLocked(true); + mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true); mUserState.setAutoclickEnabledLocked(true); mUserState.setUserNonInteractiveUiTimeoutLocked(30); mUserState.setUserInteractiveUiTimeoutLocked(30); @@ -177,7 +177,7 @@ public class AccessibilityUserStateTest { assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty()); assertNull(mUserState.getTargetAssignedToAccessibilityButton()); assertFalse(mUserState.isTouchExplorationEnabledLocked()); - assertFalse(mUserState.isDisplayMagnificationEnabledLocked()); + assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked()); assertFalse(mUserState.isAutoclickEnabledLocked()); assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked()); assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked()); 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 e8cbcf9a6874..a3d415e4918f 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 @@ -337,11 +337,7 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); - mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS); - 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); + mSetFlagsRule.initAllFlagsToReleaseConfigDefault(); doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); 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 12d6161eb718..5cc84b197e03 100644 --- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java @@ -59,6 +59,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Collections; import java.util.List; /** @@ -103,6 +104,10 @@ public class ContentCaptureManagerServiceTest { private boolean mDevCfgEnableContentProtectionReceiver; + private List<List<String>> mDevCfgContentProtectionRequiredGroups = List.of(List.of("a")); + + private List<List<String>> mDevCfgContentProtectionOptionalGroups = Collections.emptyList(); + private int mContentProtectionBlocklistManagersCreated; private int mContentProtectionServiceInfosCreated; @@ -374,7 +379,21 @@ public class ContentCaptureManagerServiceTest { } @Test - public void isContentProtectionReceiverEnabled_withoutManagers() { + public void isContentProtectionReceiverEnabled_true() { + when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true); + when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true); + mDevCfgEnableContentProtectionReceiver = true; + mContentCaptureManagerService = new TestContentCaptureManagerService(); + + boolean actual = + mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted( + USER_ID, PACKAGE_NAME); + + assertThat(actual).isTrue(); + } + + @Test + public void isContentProtectionReceiverEnabled_false_withoutManagers() { boolean actual = mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted( USER_ID, PACKAGE_NAME); @@ -385,7 +404,7 @@ public class ContentCaptureManagerServiceTest { } @Test - public void isContentProtectionReceiverEnabled_disabledWithFlag() { + public void isContentProtectionReceiverEnabled_false_disabledWithFlag() { mDevCfgEnableContentProtectionReceiver = true; mContentCaptureManagerService = new TestContentCaptureManagerService(); mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false; @@ -400,6 +419,22 @@ public class ContentCaptureManagerServiceTest { } @Test + public void isContentProtectionReceiverEnabled_false_emptyGroups() { + mDevCfgEnableContentProtectionReceiver = true; + mDevCfgContentProtectionRequiredGroups = Collections.emptyList(); + mDevCfgContentProtectionOptionalGroups = Collections.emptyList(); + mContentCaptureManagerService = new TestContentCaptureManagerService(); + + boolean actual = + mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted( + USER_ID, PACKAGE_NAME); + + assertThat(actual).isFalse(); + verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt()); + verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString()); + } + + @Test public void onLoginDetected_disabledAfterConstructor() { mDevCfgEnableContentProtectionReceiver = true; mContentCaptureManagerService = new TestContentCaptureManagerService(); @@ -525,6 +560,10 @@ public class ContentCaptureManagerServiceTest { super(sContext); this.mDevCfgEnableContentProtectionReceiver = ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver; + this.mDevCfgContentProtectionRequiredGroups = + ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionRequiredGroups; + this.mDevCfgContentProtectionOptionalGroups = + ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionOptionalGroups; } @Override diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java new file mode 100644 index 000000000000..07cdf4df47ae --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java @@ -0,0 +1,103 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.SharedPreferences; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +/** + * Tests for the {@link MediaProjectionSessionIdGenerator} class. + * + * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionSessionIdGeneratorTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class MediaProjectionSessionIdGeneratorTest { + + private static final String TEST_PREFS_FILE = "media-projection-session-id-test"; + + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE); + private final SharedPreferences mSharedPreferences = createSharePreferences(); + private final MediaProjectionSessionIdGenerator mGenerator = + createGenerator(mSharedPreferences); + + @Before + public void setUp() { + mSharedPreferences.edit().clear().commit(); + } + + @After + public void tearDown() { + mSharedPreferences.edit().clear().commit(); + mSharedPreferencesFile.delete(); + } + + @Test + public void getCurrentSessionId_byDefault_returns0() { + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + } + + @Test + public void getCurrentSessionId_multipleTimes_returnsSameValue() { + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + } + + @Test + public void createAndGetNewSessionId_returnsIncrementedId() { + int previousValue = mGenerator.getCurrentSessionId(); + + int newValue = mGenerator.createAndGetNewSessionId(); + + assertThat(newValue).isEqualTo(previousValue + 1); + } + + @Test + public void createAndGetNewSessionId_persistsNewValue() { + int newValue = mGenerator.createAndGetNewSessionId(); + + MediaProjectionSessionIdGenerator newInstance = createGenerator(createSharePreferences()); + + assertThat(newInstance.getCurrentSessionId()).isEqualTo(newValue); + } + + private SharedPreferences createSharePreferences() { + return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE); + } + + private MediaProjectionSessionIdGenerator createGenerator(SharedPreferences sharedPreferences) { + return new MediaProjectionSessionIdGenerator(sharedPreferences); + } +} diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java new file mode 100644 index 000000000000..7723541dc160 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java @@ -0,0 +1,104 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.SharedPreferences; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.time.Duration; +import java.time.Instant; +import java.time.InstantSource; + +/** + * Tests for the {@link MediaProjectionTimestampStore} class. + * + * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionTimestampStoreTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class MediaProjectionTimestampStoreTest { + + private static final String TEST_PREFS_FILE = "media-projection-timestamp-test"; + + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE); + private final SharedPreferences mSharedPreferences = createSharePreferences(); + + private Instant mCurrentInstant = Instant.ofEpochMilli(0); + + private final InstantSource mInstantSource = () -> mCurrentInstant; + private final MediaProjectionTimestampStore mStore = + new MediaProjectionTimestampStore(mSharedPreferences, mInstantSource); + + @Before + public void setUp() { + mSharedPreferences.edit().clear().commit(); + } + + @After + public void tearDown() { + mSharedPreferences.edit().clear().commit(); + mSharedPreferencesFile.delete(); + } + + @Test + public void timeSinceLastActiveSession_byDefault_returnsNull() { + assertThat(mStore.timeSinceLastActiveSession()).isNull(); + } + + @Test + public void timeSinceLastActiveSession_returnsBasedOnLastActiveSessionEnded() { + mCurrentInstant = Instant.ofEpochMilli(0); + mStore.registerActiveSessionEnded(); + + mCurrentInstant = mCurrentInstant.plusSeconds(60); + + assertThat(mStore.timeSinceLastActiveSession()).isEqualTo(Duration.ofSeconds(60)); + } + + @Test + public void timeSinceLastActiveSession_valueIsPersisted() { + mCurrentInstant = Instant.ofEpochMilli(0); + mStore.registerActiveSessionEnded(); + + MediaProjectionTimestampStore newStoreInstance = + new MediaProjectionTimestampStore(createSharePreferences(), mInstantSource); + mCurrentInstant = mCurrentInstant.plusSeconds(123); + + assertThat(newStoreInstance.timeSinceLastActiveSession()) + .isEqualTo(Duration.ofSeconds(123)); + } + + private SharedPreferences createSharePreferences() { + return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE); + } +} 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 3d4b4a62e5ac..75d012a8e1f2 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -7769,6 +7769,74 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(NotificationManagerService.MAX_PACKAGE_TOASTS, mService.mToastQueue.size()); } + @Test + public void testPrioritizeSystemToasts() throws Exception { + // Insert non-system toasts + final String testPackage = "testPackageName"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = false; + mService.isSystemAppId = false; + setToastRateIsWithinQuota(true); + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false); + + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId)) + .thenReturn(false); + + INotificationManager nmService = (INotificationManager) mService.mService; + + // Enqueue maximum number of toasts for test package + for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) { + enqueueTextToast(testPackage, "Text"); + } + + // Enqueue system toast + final String testPackageSystem = "testPackageNameSystem"; + mService.isSystemUid = true; + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem, false); + when(mPackageManager.isPackageSuspendedForUser(testPackageSystem, mUserId)) + .thenReturn(false); + + enqueueToast(testPackageSystem, new TestableToastCallback()); + + // System toast is inserted at the front of the queue, behind current showing toast + assertEquals(testPackageSystem, mService.mToastQueue.get(1).pkg); + } + + @Test + public void testPrioritizeSystemToasts_enqueueAfterExistingSystemToast() throws Exception { + // Insert system toasts + final String testPackageSystem1 = "testPackageNameSystem1"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = true; + setToastRateIsWithinQuota(true); + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem1, false); + + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(testPackageSystem1, mUserId)) + .thenReturn(false); + + INotificationManager nmService = (INotificationManager) mService.mService; + + // Enqueue maximum number of toasts for test package + for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) { + enqueueTextToast(testPackageSystem1, "Text"); + } + + // Enqueue another system toast + final String testPackageSystem2 = "testPackageNameSystem2"; + mService.isSystemUid = true; + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem2, false); + when(mPackageManager.isPackageSuspendedForUser(testPackageSystem2, mUserId)) + .thenReturn(false); + + enqueueToast(testPackageSystem2, new TestableToastCallback()); + + // System toast is inserted at the back of the queue, after the other system toasts + assertEquals(testPackageSystem2, + mService.mToastQueue.get(mService.mToastQueue.size() - 1).pkg); + } + private void setAppInForegroundForToasts(int uid, boolean inForeground) { int importance = (inForeground) ? IMPORTANCE_FOREGROUND : IMPORTANCE_NONE; when(mActivityManager.getUidImportance(mUid)).thenReturn(importance); diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp index 95441060f0e5..6f37967bf7f0 100644 --- a/services/tests/vibrator/Android.bp +++ b/services/tests/vibrator/Android.bp @@ -35,6 +35,7 @@ android_test { "platform-test-annotations", "service-permission.stubs.system_server", "services.core", + "flag-junit", ], platform_apis: true, diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java index 000355598281..3d0dca0de87e 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java @@ -16,20 +16,24 @@ package com.android.server.vibrator; +import static android.os.VibrationAttributes.CATEGORY_KEYBOARD; import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; +import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE; import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK; import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK; +import static android.os.VibrationEffect.EFFECT_CLICK; import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK; import static android.os.VibrationEffect.EFFECT_TICK; import static android.view.HapticFeedbackConstants.CLOCK_TICK; import static android.view.HapticFeedbackConstants.CONTEXT_CLICK; +import static android.view.HapticFeedbackConstants.KEYBOARD_RELEASE; +import static android.view.HapticFeedbackConstants.KEYBOARD_TAP; import static android.view.HapticFeedbackConstants.SAFE_MODE_ENABLED; -import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE; import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS; import static android.view.HapticFeedbackConstants.SCROLL_LIMIT; import static android.view.HapticFeedbackConstants.SCROLL_TICK; - +import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -42,9 +46,10 @@ import android.hardware.vibrator.IVibrator; import android.os.VibrationAttributes; 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; -import android.view.flags.FeatureFlags; import androidx.test.InstrumentationRegistry; @@ -62,6 +67,8 @@ import java.io.FileOutputStream; public class HapticFeedbackVibrationProviderTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final VibrationEffect PRIMITIVE_TICK_EFFECT = VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose(); private static final VibrationEffect PRIMITIVE_CLICK_EFFECT = @@ -69,11 +76,15 @@ public class HapticFeedbackVibrationProviderTest { private static final int[] SCROLL_FEEDBACK_CONSTANTS = new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}; + private static final int[] KEYBOARD_FEEDBACK_CONSTANTS = + new int[] {KEYBOARD_TAP, KEYBOARD_RELEASE}; + + private static final float KEYBOARD_VIBRATION_FIXED_AMPLITUDE = 0.62f; + private Context mContext = InstrumentationRegistry.getContext(); private VibratorInfo mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO; @Mock private Resources mResourcesMock; - @Mock private FeatureFlags mViewFeatureFlags; @Test public void testNonExistentCustomization_useDefault() throws Exception { @@ -214,6 +225,62 @@ public class HapticFeedbackVibrationProviderTest { } @Test + public void testKeyboardHaptic_noFixedAmplitude_defaultVibrationReturned() { + mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); + SparseArray<VibrationEffect> customizations = new SparseArray<>(); + customizations.put(KEYBOARD_TAP, PRIMITIVE_CLICK_EFFECT); + customizations.put(KEYBOARD_RELEASE, PRIMITIVE_TICK_EFFECT); + + // Test with a customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`. + HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations); + + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP)) + .isEqualTo(PRIMITIVE_CLICK_EFFECT); + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE)) + .isEqualTo(PRIMITIVE_TICK_EFFECT); + + // Test with no customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`. + hapticProvider = createProviderWithDefaultCustomizations(); + + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP)) + .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */)); + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE)) + .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */)); + } + + @Test + public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOff_defaultVibrationReturned() { + mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); + mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE); + + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP)) + .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */)); + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE)) + .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */)); + } + + @Test + public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOn_keyboardVibrationReturned() { + mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); + mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE); + + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP)) + .isEqualTo(VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_CLICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE) + .compose()); + assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE)) + .isEqualTo(VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_TICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE) + .compose()); + } + + @Test public void testVibrationAttribute_forNotBypassingIntensitySettings() { HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); @@ -235,7 +302,7 @@ public class HapticFeedbackVibrationProviderTest { @Test public void testVibrationAttribute_scrollFeedback_scrollApiFlagOn_bypassInterruptPolicy() { - when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true); + mSetFlagsRule.enableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API); HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { @@ -248,7 +315,7 @@ public class HapticFeedbackVibrationProviderTest { @Test public void testVibrationAttribute_scrollFeedback_scrollApiFlagOff_noBypassInterruptPolicy() { - when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(false); + mSetFlagsRule.disableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API); HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { @@ -259,14 +326,71 @@ public class HapticFeedbackVibrationProviderTest { } } + @Test + public void testVibrationAttribute_keyboardCategoryOff_notUseKeyboardCategory() { + mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ false); + assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId) + .that(attrs.getCategory()).isEqualTo(0); + } + } + + @Test + public void testVibrationAttribute_keyboardCategoryOn_useKeyboardCategory() { + mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ false); + assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId) + .that(attrs.getCategory()).isEqualTo(CATEGORY_KEYBOARD); + } + } + + @Test + public void testVibrationAttribute_noFixAmplitude_keyboardCategoryOn_noBypassIntensityScale() { + mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); + mockKeyboardVibrationFixedAmplitude(-1); + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ false); + assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect " + + effectId) + .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse(); + } + } + + @Test + public void testVibrationAttribute_fixAmplitude_keyboardCategoryOn_bypassIntensityScale() { + mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); + mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE); + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ false); + assertWithMessage("Expected FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect " + + effectId) + .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isTrue(); + } + } + private HapticFeedbackVibrationProvider createProviderWithDefaultCustomizations() { return createProvider(/* customizations= */ null); } private HapticFeedbackVibrationProvider createProvider( SparseArray<VibrationEffect> customizations) { - return new HapticFeedbackVibrationProvider( - mResourcesMock, mVibratorInfo, customizations, mViewFeatureFlags); + return new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations); } private void mockVibratorPrimitiveSupport(int... supportedPrimitives) { @@ -287,6 +411,11 @@ public class HapticFeedbackVibrationProviderTest { .thenReturn(vibrationPattern); } + private void mockKeyboardVibrationFixedAmplitude(float amplitude) { + when(mResourcesMock.getFloat(R.dimen.config_keyboardHapticFeedbackFixedAmplitude)) + .thenReturn(amplitude); + } + private void setupCustomizationFile(String xml) throws Exception { File file = new File(mContext.getCacheDir(), "test.xml"); file.createNewFile(); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java index 1ae096617dce..7a2bb5a90846 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -69,7 +69,11 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.test.TestLooper; +import android.os.vibrator.Flags; import android.os.vibrator.VibrationConfig; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.util.ArraySet; import android.view.Display; @@ -95,6 +99,9 @@ import java.util.Set; public class VibrationSettingsTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final int UID = 1; private static final int VIRTUAL_DISPLAY_ID = 1; private static final String SYSUI_PACKAGE_NAME = "sysui"; @@ -606,6 +613,47 @@ public class VibrationSettingsTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED) + public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() { + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM); + setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0); + + // Keyboard touch ignored. + assertVibrationIgnoredForAttributes( + new VibrationAttributes.Builder() + .setUsage(USAGE_TOUCH) + .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) + .build(), + Vibration.Status.IGNORED_FOR_SETTINGS); + + // General touch and keyboard touch with bypass flag not ignored. + assertVibrationNotIgnoredForUsage(USAGE_TOUCH); + assertVibrationNotIgnoredForAttributes( + new VibrationAttributes.Builder() + .setUsage(USAGE_TOUCH) + .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) + .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) + .build()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED) + public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() { + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1); + + // General touch ignored. + assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS); + + // Keyboard touch not ignored. + assertVibrationNotIgnoredForAttributes( + new VibrationAttributes.Builder() + .setUsage(USAGE_TOUCH) + .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) + .build()); + } + + @Test public void shouldIgnoreVibrationFromVirtualDisplays_displayNonVirtual_neverIgnored() { // Vibrations from the primary display is never ignored regardless of the creation and // removal of virtual displays and of the changes of apps running on virtual displays. @@ -895,6 +943,14 @@ public class VibrationSettingsTest { mVibrationSettings.shouldIgnoreVibration(callerInfo)); } + private void assertVibrationIgnoredForAttributes(VibrationAttributes attrs, + Vibration.Status expectedStatus) { + Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID, + Display.DEFAULT_DISPLAY, null, null); + assertEquals(errorMessageForAttributes(attrs), expectedStatus, + mVibrationSettings.shouldIgnoreVibration(callerInfo)); + } + private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) { assertVibrationNotIgnoredForUsageAndFlags(usage, /* flags= */ 0); } @@ -919,10 +975,21 @@ public class VibrationSettingsTest { mVibrationSettings.shouldIgnoreVibration(callerInfo)); } + private void assertVibrationNotIgnoredForAttributes(VibrationAttributes attrs) { + Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID, + Display.DEFAULT_DISPLAY, null, null); + assertNull(errorMessageForAttributes(attrs), + mVibrationSettings.shouldIgnoreVibration(callerInfo)); + } + private String errorMessageForUsage(int usage) { return "Error for usage " + VibrationAttributes.usageToString(usage); } + private String errorMessageForAttributes(VibrationAttributes attrs) { + return "Error for attributes " + attrs; + } + private void setDefaultIntensity(@Vibrator.VibrationIntensity int intensity) { when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt())).thenReturn(intensity); } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 40e0e84dca59..3dfaed69dea6 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -82,6 +82,7 @@ import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationConfig; import android.os.vibrator.VibrationEffectSegment; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.util.ArraySet; import android.util.SparseArray; @@ -89,7 +90,7 @@ import android.util.SparseBooleanArray; import android.view.Display; import android.view.HapticFeedbackConstants; import android.view.InputDevice; -import android.view.flags.FeatureFlags; +import android.view.flags.Flags; import androidx.test.InstrumentationRegistry; import androidx.test.filters.FlakyTest; @@ -155,6 +156,8 @@ public class VibratorManagerServiceTest { @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock; @Mock @@ -175,8 +178,6 @@ public class VibratorManagerServiceTest { private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock; @Mock private AudioManager mAudioManagerMock; - @Mock - private FeatureFlags mViewFeatureFlags; private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>(); @@ -326,8 +327,7 @@ public class VibratorManagerServiceTest { HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider( Resources resources, VibratorInfo vibratorInfo) { return new HapticFeedbackVibrationProvider( - resources, vibratorInfo, mHapticFeedbackVibrationMap, - mViewFeatureFlags); + resources, vibratorInfo, mHapticFeedbackVibrationMap); } }); return mService; @@ -1354,7 +1354,7 @@ public class VibratorManagerServiceTest { denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE); denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING); // Flag override to enable the scroll feedack constants to bypass interruption policies. - when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true); + mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API); mHapticFeedbackVibrationMap.put( HapticFeedbackConstants.SCROLL_TICK, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 8e7ba7030e82..dd7dec0bdb2b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -130,12 +130,22 @@ public class BackNavigationControllerTests extends WindowTestsBase { // verify if back animation would start. assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); - // reset drawing status + // reset drawing status to test translucent activity backNavigationInfo.onBackNavigationFinished(false); mBackNavigationController.clearBackAnimations(); - topTask.forAllWindows(w -> { - makeWindowVisibleAndDrawn(w); - }, true); + final ActivityRecord topActivity = topTask.getTopMostActivity(); + makeWindowVisibleAndDrawn(topActivity.findMainWindow()); + // simulate translucent + topActivity.setOccludesParent(false); + backNavigationInfo = startBackNavigation(); + assertThat(typeToString(backNavigationInfo.getType())) + .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); + + // reset drawing status to test keyguard occludes + topActivity.setOccludesParent(true); + backNavigationInfo.onBackNavigationFinished(false); + mBackNavigationController.clearBackAnimations(); + makeWindowVisibleAndDrawn(topActivity.findMainWindow()); setupKeyguardOccluded(); backNavigationInfo = startBackNavigation(); assertThat(typeToString(backNavigationInfo.getType())) @@ -201,9 +211,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { // reset drawing status backNavigationInfo.onBackNavigationFinished(false); mBackNavigationController.clearBackAnimations(); - testCase.recordFront.forAllWindows(w -> { - makeWindowVisibleAndDrawn(w); - }, true); + makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow()); setupKeyguardOccluded(); backNavigationInfo = startBackNavigation(); assertThat(typeToString(backNavigationInfo.getType())) diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index 84d42d42f834..6a738befba9a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -163,8 +163,8 @@ public class DimmerTests extends WindowTestsBase { public void testUpdateDimsAppliesCrop() { mHost.addChild(mChild, 0); - final float alpha = 0.8f; - mDimmer.dimAbove(mChild, alpha); + mDimmer.adjustAppearance(mChild, 1, 1); + mDimmer.adjustRelativeLayer(mChild, -1); int width = 100; int height = 300; @@ -176,42 +176,13 @@ public class DimmerTests extends WindowTestsBase { } @Test - public void testDimAboveWithChildCreatesSurfaceAboveChild_Smooth() { - assumeTrue(Flags.dimmerRefactor()); - final float alpha = 0.8f; - mHost.addChild(mChild, 0); - mDimmer.dimAbove(mChild, alpha); - SurfaceControl dimLayer = mDimmer.getDimLayer(); - - assertNotNull("Dimmer should have created a surface", dimLayer); - - mDimmer.updateDims(mTransaction); - verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction), - anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); - verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, 1); - verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha); - } - - @Test - public void testDimAboveWithChildCreatesSurfaceAboveChild_Legacy() { - assumeFalse(Flags.dimmerRefactor()); - final float alpha = 0.8f; - mHost.addChild(mChild, 0); - mDimmer.dimAbove(mChild, alpha); - SurfaceControl dimLayer = mDimmer.getDimLayer(); - - assertNotNull("Dimmer should have created a surface", dimLayer); - - verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha); - verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, 1); - } - - @Test public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() { assumeTrue(Flags.dimmerRefactor()); final float alpha = 0.7f; + final int blur = 50; mHost.addChild(mChild, 0); - mDimmer.dimBelow(mChild, alpha, 50); + mDimmer.adjustAppearance(mChild, alpha, blur); + mDimmer.adjustRelativeLayer(mChild, -1); SurfaceControl dimLayer = mDimmer.getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); @@ -221,7 +192,7 @@ public class DimmerTests extends WindowTestsBase { anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, -1); verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha); - verify(mTransaction).setBackgroundBlurRadius(dimLayer, 50); + verify(mTransaction).setBackgroundBlurRadius(dimLayer, blur); } @Test @@ -229,7 +200,8 @@ public class DimmerTests extends WindowTestsBase { assumeFalse(Flags.dimmerRefactor()); final float alpha = 0.7f; mHost.addChild(mChild, 0); - mDimmer.dimBelow(mChild, alpha, 50); + mDimmer.adjustAppearance(mChild, alpha, 20); + mDimmer.adjustRelativeLayer(mChild, -1); SurfaceControl dimLayer = mDimmer.getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); @@ -244,12 +216,15 @@ public class DimmerTests extends WindowTestsBase { mHost.addChild(mChild, 0); final float alpha = 0.8f; + final int blur = 50; // Dim once - mDimmer.dimBelow(mChild, alpha, 0); + mDimmer.adjustAppearance(mChild, alpha, blur); + mDimmer.adjustRelativeLayer(mChild, -1); SurfaceControl dimLayer = mDimmer.getDimLayer(); mDimmer.updateDims(mTransaction); // Reset, and don't dim mDimmer.resetDimStates(); + mDimmer.adjustRelativeLayer(mChild, -1); mDimmer.updateDims(mTransaction); verify(mTransaction).show(dimLayer); verify(mTransaction).remove(dimLayer); @@ -261,7 +236,8 @@ public class DimmerTests extends WindowTestsBase { mHost.addChild(mChild, 0); final float alpha = 0.8f; - mDimmer.dimAbove(mChild, alpha); + mDimmer.adjustAppearance(mChild, alpha, 20); + mDimmer.adjustRelativeLayer(mChild, -1); SurfaceControl dimLayer = mDimmer.getDimLayer(); mDimmer.resetDimStates(); @@ -278,13 +254,16 @@ public class DimmerTests extends WindowTestsBase { mHost.addChild(mChild, 0); final float alpha = 0.8f; + final int blur = 20; // Dim once - mDimmer.dimBelow(mChild, alpha, 0); + mDimmer.adjustAppearance(mChild, alpha, blur); + mDimmer.adjustRelativeLayer(mChild, -1); SurfaceControl dimLayer = mDimmer.getDimLayer(); mDimmer.updateDims(mTransaction); // Reset and dim again mDimmer.resetDimStates(); - mDimmer.dimAbove(mChild, alpha); + mDimmer.adjustAppearance(mChild, alpha, blur); + mDimmer.adjustRelativeLayer(mChild, -1); mDimmer.updateDims(mTransaction); verify(mTransaction).show(dimLayer); verify(mTransaction, never()).remove(dimLayer); @@ -294,7 +273,8 @@ public class DimmerTests extends WindowTestsBase { public void testDimUpdateWhileDimming() { mHost.addChild(mChild, 0); final float alpha = 0.8f; - mDimmer.dimAbove(mChild, alpha); + mDimmer.adjustAppearance(mChild, alpha, 20); + mDimmer.adjustRelativeLayer(mChild, -1); final Rect bounds = mDimmer.getDimBounds(); SurfaceControl dimLayer = mDimmer.getDimLayer(); @@ -314,7 +294,8 @@ public class DimmerTests extends WindowTestsBase { public void testRemoveDimImmediately_Smooth() { assumeTrue(Flags.dimmerRefactor()); mHost.addChild(mChild, 0); - mDimmer.dimAbove(mChild, 1); + mDimmer.adjustAppearance(mChild, 1, 2); + mDimmer.adjustRelativeLayer(mChild, -1); SurfaceControl dimLayer = mDimmer.getDimLayer(); mDimmer.updateDims(mTransaction); verify(mTransaction, times(1)).show(dimLayer); @@ -333,7 +314,8 @@ public class DimmerTests extends WindowTestsBase { public void testRemoveDimImmediately_Legacy() { assumeFalse(Flags.dimmerRefactor()); mHost.addChild(mChild, 0); - mDimmer.dimAbove(mChild, 1); + mDimmer.adjustAppearance(mChild, 1, 0); + mDimmer.adjustRelativeLayer(mChild, -1); SurfaceControl dimLayer = mDimmer.getDimLayer(); mDimmer.updateDims(mTransaction); verify(mTransaction, times(1)).show(dimLayer); @@ -351,16 +333,16 @@ public class DimmerTests extends WindowTestsBase { @Test public void testDimmerWithBlurUpdatesTransaction_Legacy() { assumeFalse(Flags.dimmerRefactor()); - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + mHost.addChild(mChild, 0); final int blurRadius = 50; - mDimmer.dimBelow(child, 0, blurRadius); + mDimmer.adjustAppearance(mChild, 1, blurRadius); + mDimmer.adjustRelativeLayer(mChild, -1); SurfaceControl dimLayer = mDimmer.getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius); - verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1); + verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 2bf13857e537..6235b3b67145 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -26,7 +26,9 @@ import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; @@ -1655,6 +1657,127 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertEquals(frontMostTaskFragment, tf0); } + @Test + public void testApplyTransaction_reorderToBottomOfTask() { + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Reorder TaskFragment to bottom + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK).build(); + mTransaction.addTaskFragmentOperation(tf1.getFragmentToken(), operation); + assertApplyTransactionAllowed(mTransaction); + + // Ensure correct order of the children after the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf0, task.getChildAt(2).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(1).asActivityRecord()); + assertEquals(tf1, task.getChildAt(0).asTaskFragment()); + } + + @Test + public void testApplyTransaction_reorderToTopOfTask() { + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Reorder TaskFragment to top + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REORDER_TO_TOP_OF_TASK).build(); + mTransaction.addTaskFragmentOperation(tf0.getFragmentToken(), operation); + assertApplyTransactionAllowed(mTransaction); + + // Ensure correct order of the children after the operation + assertEquals(tf0, task.getChildAt(3).asTaskFragment()); + assertEquals(topActivity, task.getChildAt(2).asActivityRecord()); + assertEquals(tf1, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + } + + @Test + public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() { + testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK); + } + + @Test + public void testApplyTransaction_reorderToTopOfTask_failsIfNotSystemOrganizer() { + testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + OP_TYPE_REORDER_TO_TOP_OF_TASK); + } + + private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + @TaskFragmentOperation.OperationType int opType) { + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Apply reorder transaction, which is expected to fail for non-system organizer. + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + opType).build(); + mTransaction + .addTaskFragmentOperation(tf0.getFragmentToken(), operation) + .setErrorCallbackToken(mErrorToken); + assertApplyTransactionAllowed(mTransaction); + // The pending event will be dispatched on the handler (from requestTraversal). + waitHandlerIdle(mWm.mAnimationHandler); + + assertTaskFragmentErrorTransaction(opType, SecurityException.class); + + // Ensure no change to the order of the children after the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + } + /** * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the @@ -1782,6 +1905,19 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertEquals(activityToken, change.getActivityToken()); } + /** Setups an embedded TaskFragment. */ + private TaskFragment createTaskFragment(Task task) { + final IBinder token = new Binder(); + TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setFragmentToken(token) + .setOrganizer(mOrganizer) + .createActivityCount(1) + .build(); + mWindowOrganizerController.mLaunchTaskFragments.put(token, taskFragment); + return taskFragment; + } + /** Setups an embedded TaskFragment in a PIP Task. */ private void setupTaskFragmentInPip() { mTaskFragment = new TaskFragmentBuilder(mAtm) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index e86fc366a631..eaeb8049b81a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -100,6 +100,9 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.AdoptShellPermissionsRule; import com.android.internal.os.IResultReceiver; import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerService.WindowContainerInfo; + +import com.google.common.truth.Expect; import org.junit.Rule; import org.junit.Test; @@ -125,6 +128,9 @@ public class WindowManagerServiceTests extends WindowTestsBase { InstrumentationRegistry.getInstrumentation().getUiAutomation(), ADD_TRUSTED_DISPLAY); + @Rule + public Expect mExpect = Expect.create(); + @Test public void testIsRequestedOrientationMapped() { mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true, @@ -674,64 +680,68 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() { - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null); - assertThat(wct).isNull(); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(null); + assertThat(wci).isNull(); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() { Binder cookie = new Binder("test cookie"); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); - assertThat(wct).isNull(); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + assertThat(wci).isNull(); final ActivityRecord testActivity = new ActivityBuilder(mAtm) .setCreateTask(true) .build(); - wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); - assertThat(wct).isNull(); + wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + assertThat(wci).isNull(); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() { final Binder cookie = new Binder("ginger cookie"); final WindowContainerToken launchRootTask = mock(WindowContainerToken.class); - setupActivityWithLaunchCookie(cookie, launchRootTask); + final int uid = 123; + setupActivityWithLaunchCookie(cookie, launchRootTask, uid); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); - assertThat(wct).isEqualTo(launchRootTask); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + mExpect.that(wci.getToken()).isEqualTo(launchRootTask); + mExpect.that(wci.getUid()).isEqualTo(uid); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() { final Binder cookie1 = new Binder("ginger cookie"); final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class); - setupActivityWithLaunchCookie(cookie1, launchRootTask1); + final int uid1 = 123; + setupActivityWithLaunchCookie(cookie1, launchRootTask1, uid1); setupActivityWithLaunchCookie(new Binder("choc chip cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 456); setupActivityWithLaunchCookie(new Binder("peanut butter cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 789); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1); - assertThat(wct).isEqualTo(launchRootTask1); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie1); + mExpect.that(wci.getToken()).isEqualTo(launchRootTask1); + mExpect.that(wci.getUid()).isEqualTo(uid1); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() { setupActivityWithLaunchCookie(new Binder("ginger cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 123); setupActivityWithLaunchCookie(new Binder("choc chip cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 456); setupActivityWithLaunchCookie(new Binder("peanut butter cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 789); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie( + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie( new Binder("some other cookie")); - assertThat(wct).isNull(); + assertThat(wci).isNull(); } @Test @@ -778,17 +788,18 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() { + public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerInfo() { WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); Task task = createTask(mDefaultDisplay); ActivityRecord activityRecord = createActivityRecord(task); - ContentRecordingSession session = ContentRecordingSession.createTaskSession( - activityRecord.mLaunchCookie); + ContentRecordingSession session = + ContentRecordingSession.createTaskSession(activityRecord.mLaunchCookie); wmInternal.setContentRecordingSession(session); - assertThat(session.getTokenToRecord()).isEqualTo( - task.mRemoteToken.toWindowContainerToken().asBinder()); + mExpect.that(session.getTokenToRecord()) + .isEqualTo(task.mRemoteToken.toWindowContainerToken().asBinder()); + mExpect.that(session.getTargetUid()).isEqualTo(activityRecord.getUid()); } @Test @@ -1010,12 +1021,12 @@ public class WindowManagerServiceTests extends WindowTestsBase { } } - private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) { + private void setupActivityWithLaunchCookie( + IBinder launchCookie, WindowContainerToken wct, int uid) { final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class); when(remoteToken.toWindowContainerToken()).thenReturn(wct); - final ActivityRecord testActivity = new ActivityBuilder(mAtm) - .setCreateTask(true) - .build(); + final ActivityRecord testActivity = + new ActivityBuilder(mAtm).setCreateTask(true).setUid(uid).build(); testActivity.mLaunchCookie = launchCookie; testActivity.getTask().mRemoteToken = remoteToken; } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 7168670f9652..0b77fd828745 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -40,6 +40,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.wm.testing.Assert.assertThrows; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowContainer.SYNC_STATE_READY; @@ -58,6 +59,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityOptions; @@ -77,11 +79,13 @@ import android.util.Rational; import android.view.Display; import android.view.SurfaceControl; import android.view.WindowInsets; +import android.window.ITaskFragmentOrganizer; import android.window.ITaskOrganizer; import android.window.IWindowContainerTransactionCallback; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; import android.window.TaskAppearedInfo; +import android.window.TaskFragmentOrganizer; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -579,6 +583,87 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test + public void testTaskFragmentHiddenAndFocusableChanges() { + removeGlobalMinSizeRestriction(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + + final WindowContainerTransaction t = new WindowContainerTransaction(); + final TaskFragmentOrganizer organizer = + createTaskFragmentOrganizer(t, true /* isSystemOrganizer */); + + final IBinder token = new Binder(); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(rootTask) + .setFragmentToken(token) + .setOrganizer(organizer) + .createActivityCount(1) + .build(); + + // Should be visible and focusable initially. + assertTrue(rootTask.shouldBeVisible(null)); + assertTrue(taskFragment.shouldBeVisible(null)); + assertTrue(taskFragment.isFocusable()); + assertTrue(taskFragment.isTopActivityFocusable()); + + // Apply transaction to the TaskFragment hidden and not focusable. + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */); + + // Should be not visible and not focusable after the transaction. + assertFalse(taskFragment.shouldBeVisible(null)); + assertFalse(taskFragment.isFocusable()); + assertFalse(taskFragment.isTopActivityFocusable()); + + // Apply transaction to the TaskFragment not hidden and focusable. + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true); + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */); + + // Should be visible and focusable after the transaction. + assertTrue(taskFragment.shouldBeVisible(null)); + assertTrue(taskFragment.isFocusable()); + assertTrue(taskFragment.isTopActivityFocusable()); + } + + @Test + public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() { + removeGlobalMinSizeRestriction(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + + final WindowContainerTransaction t = new WindowContainerTransaction(); + final TaskFragmentOrganizer organizer = + createTaskFragmentOrganizer(t, false /* isSystemOrganizer */); + + final IBinder token = new Binder(); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(rootTask) + .setFragmentToken(token) + .setOrganizer(organizer) + .createActivityCount(1) + .build(); + + assertTrue(rootTask.shouldBeVisible(null)); + assertTrue(taskFragment.shouldBeVisible(null)); + + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); + + // Non-system organizers are not allow to update the hidden and focusable states. + assertThrows(SecurityException.class, () -> + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */) + ); + } + + @Test public void testContainerTranslucentChanges() { removeGlobalMinSizeRestriction(); final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) @@ -1600,4 +1685,20 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(taskIds.contains(expectedTasks[i].mTaskId)); } } + + @NonNull + private TaskFragmentOrganizer createTaskFragmentOrganizer( + @NonNull WindowContainerTransaction t, boolean isSystemOrganizer) { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final ITaskFragmentOrganizer organizerInterface = + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()); + mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController + .registerOrganizerInternal( + ITaskFragmentOrganizer.Stub.asInterface( + organizer.getOrganizerToken().asBinder()), + isSystemOrganizer); + t.setTaskFragmentOrganizer(organizerInterface); + + return organizer; + } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 2e6278d9a75d..55b5d11d938a 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -2127,15 +2127,13 @@ public class UsageStatsService extends SystemService implements } private boolean canReportUsageStats() { - final boolean isSystem = isCallingUidSystem(); - if (!Flags.reportUsageStatsPermission()) { - // If the flag is disabled, do no check for the new permission and instead return - // true only if the calling uid is system since System UID can always report stats. - return isSystem; - } - return isSystem - || getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS) - == PackageManager.PERMISSION_GRANTED; + if (isCallingUidSystem()) { + // System UID can always report UsageStats + return true; + } + + return getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS) + == PackageManager.PERMISSION_GRANTED; } private boolean hasObserverPermission() { @@ -2627,9 +2625,12 @@ public class UsageStatsService extends SystemService implements return; } - if (!canReportUsageStats()) { - throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS" - + " permission are allowed to call reportChooserSelection"); + if (Flags.reportUsageStatsPermission()) { + if (!canReportUsageStats()) { + throw new SecurityException( + "Only the system or holders of the REPORT_USAGE_STATS" + + " permission are allowed to call reportChooserSelection"); + } } // Verify if this package exists before reporting an event for it. @@ -2649,9 +2650,17 @@ public class UsageStatsService extends SystemService implements @Override public void reportUserInteraction(String packageName, int userId) { Objects.requireNonNull(packageName); - if (!canReportUsageStats()) { - throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS" - + " permission are allowed to call reportUserInteraction"); + if (Flags.reportUsageStatsPermission()) { + if (!canReportUsageStats()) { + throw new SecurityException( + "Only the system or holders of the REPORT_USAGE_STATS" + + " permission are allowed to call reportUserInteraction"); + } + } else { + if (!isCallingUidSystem()) { + throw new SecurityException("Only system is allowed to call" + + " reportUserInteraction"); + } } final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime()); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java index 42b08e3c5442..93b5a40a5fc0 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java @@ -20,6 +20,8 @@ import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.LOG_COMPAT_CHANGE; import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG; import static android.Manifest.permission.RECORD_AUDIO; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_DEFAULT; import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL; import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT; @@ -753,11 +755,21 @@ abstract class DetectorSession { "Failed to obtain permission RECORD_AUDIO for identity " + mVoiceInteractorIdentity); } - mAppOpsManager.noteOpNoThrow( - AppOpsPolicy.getVoiceActivationOp(), - mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, - mVoiceInteractorIdentity.attributionTag, - HOTWORD_DETECTION_OP_MESSAGE); + int opMode = mAppOpsManager.unsafeCheckOpNoThrow( + AppOpsManager.opToPublicName(AppOpsPolicy.getVoiceActivationOp()), + mVoiceInteractorIdentity.uid, + mVoiceInteractorIdentity.packageName); + if (opMode == MODE_DEFAULT || opMode == MODE_ALLOWED) { + mAppOpsManager.noteOpNoThrow( + AppOpsPolicy.getVoiceActivationOp(), + mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, + mVoiceInteractorIdentity.attributionTag, + HOTWORD_DETECTION_OP_MESSAGE); + } else { + throw new SecurityException( + "The app op OP_RECEIVE_SANDBOX_TRIGGER_AUDIO is denied for " + + "identity" + mVoiceInteractorIdentity); + } } else { enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity, RECORD_AUDIO, HOTWORD_DETECTION_OP_MESSAGE); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 138e575a6872..1c689d0d5ce3 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -58,6 +58,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; +import android.os.PermissionEnforcer; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteCallbackList; @@ -67,6 +68,7 @@ import android.os.SharedMemory; import android.os.ShellCallback; import android.os.Trace; import android.os.UserHandle; +import android.permission.flags.Flags; import android.provider.Settings; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback; @@ -1286,6 +1288,17 @@ public class VoiceInteractionManagerService extends SystemService { } } + // Enforce permissions that are flag controlled. The flag value decides if the permission + // should be enforced. + private void initAndVerifyDetector_enforcePermissionWithFlags() { + PermissionEnforcer enforcer = mContext.getSystemService(PermissionEnforcer.class); + if (Flags.voiceActivationPermissionApis()) { + enforcer.enforcePermission( + android.Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO, + getCallingPid(), getCallingUid()); + } + } + @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) @Override public void initAndVerifyDetector( @@ -1295,7 +1308,13 @@ public class VoiceInteractionManagerService extends SystemService { @NonNull IBinder token, IHotwordRecognitionStatusCallback callback, int detectorType) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #initAndVerifyDetector(Identity, PersistableBundle, ShareMemory, IBinder, + // IHotwordRecognitionStatusCallback, int)} + // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully + // launched. super.initAndVerifyDetector_enforcePermission(); + initAndVerifyDetector_enforcePermissionWithFlags(); synchronized (this) { enforceIsCurrentVoiceInteractionService(); diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java index 971fc781a719..e20e4d200251 100644 --- a/telephony/java/android/telephony/BarringInfo.java +++ b/telephony/java/android/telephony/BarringInfo.java @@ -202,6 +202,24 @@ public final class BarringInfo implements Parcelable { && mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds; } + private static String barringTypeToString(@BarringType int barringType) { + return switch (barringType) { + case BARRING_TYPE_NONE -> "NONE"; + case BARRING_TYPE_CONDITIONAL -> "CONDITIONAL"; + case BARRING_TYPE_UNCONDITIONAL -> "UNCONDITIONAL"; + case BARRING_TYPE_UNKNOWN -> "UNKNOWN"; + default -> "UNKNOWN(" + barringType + ")"; + }; + } + + @Override + public String toString() { + return "BarringServiceInfo {mBarringType=" + barringTypeToString(mBarringType) + + ", mIsConditionallyBarred=" + mIsConditionallyBarred + + ", mConditionalBarringFactor=" + mConditionalBarringFactor + + ", mConditionalBarringTimeSeconds=" + mConditionalBarringTimeSeconds + "}"; + } + /** @hide */ public BarringServiceInfo(Parcel p) { mBarringType = p.readInt(); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 90fa69ff8a15..73220c353b36 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -15033,15 +15033,6 @@ public class TelephonyManager { @TestApi public static final int HAL_SERVICE_IMS = 7; - /** - * HAL service type that supports the HAL APIs implementation of IRadioSatellite - * {@link RadioSatelliteProxy} - * @hide - */ - @TestApi - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) - public static final int HAL_SERVICE_SATELLITE = 8; - /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"HAL_SERVICE_"}, @@ -15054,7 +15045,6 @@ public class TelephonyManager { HAL_SERVICE_SIM, HAL_SERVICE_VOICE, HAL_SERVICE_IMS, - HAL_SERVICE_SATELLITE }) public @interface HalService {} diff --git a/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl new file mode 100644 index 000000000000..54cab48dd1e4 --- /dev/null +++ b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl @@ -0,0 +1,31 @@ +/* + * 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 android.telephony.satellite; + +import android.telephony.satellite.NtnSignalStrength; + +/** + * Interface for non-terrestrial signal strength notify callback. + * @hide + */ +oneway interface INtnSignalStrengthCallback { + /** + * Called when NTN signal strength changes. + * @param ntnSignalStrength The new NTN signal strength. + */ + void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength); +} diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl new file mode 100644 index 000000000000..a79cb695ef24 --- /dev/null +++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.telephony.satellite; + +parcelable NtnSignalStrength; diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.java b/telephony/java/android/telephony/satellite/NtnSignalStrength.java new file mode 100644 index 000000000000..16d765455d21 --- /dev/null +++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.java @@ -0,0 +1,149 @@ +/* + * 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.telephony.satellite; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.internal.telephony.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * NTN signal strength related information. + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +public final class NtnSignalStrength implements Parcelable { + /** Non-terrestrial network signal strength is not available. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int NTN_SIGNAL_STRENGTH_NONE = 0; + /** Non-terrestrial network signal strength is poor. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int NTN_SIGNAL_STRENGTH_POOR = 1; + /** Non-terrestrial network signal strength is moderate. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2; + /** Non-terrestrial network signal strength is good. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int NTN_SIGNAL_STRENGTH_GOOD = 3; + /** Non-terrestrial network signal strength is great. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int NTN_SIGNAL_STRENGTH_GREAT = 4; + @NtnSignalStrengthLevel private int mLevel; + + /** @hide */ + @IntDef(prefix = "NTN_SIGNAL_STRENGTH_", value = { + NTN_SIGNAL_STRENGTH_NONE, + NTN_SIGNAL_STRENGTH_POOR, + NTN_SIGNAL_STRENGTH_MODERATE, + NTN_SIGNAL_STRENGTH_GOOD, + NTN_SIGNAL_STRENGTH_GREAT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NtnSignalStrengthLevel {} + + /** + * Create a parcelable object to inform the current non-terrestrial signal strength + * @hide + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public NtnSignalStrength(@NtnSignalStrengthLevel int level) { + this.mLevel = level; + } + + /** + * This constructor is used to create a copy of an existing NtnSignalStrength object. + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public NtnSignalStrength(@Nullable NtnSignalStrength source) { + this.mLevel = (source == null) ? NTN_SIGNAL_STRENGTH_NONE : source.getLevel(); + } + + private NtnSignalStrength(Parcel in) { + readFromParcel(in); + } + + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @NtnSignalStrengthLevel public int getLevel() { + return mLevel; + } + + /** + * @return 0 + */ + @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public int describeContents() { + return 0; + } + + /** + * @param out The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. + */ + @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(mLevel); + } + + private void readFromParcel(Parcel in) { + mLevel = in.readInt(); + } + + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @NonNull public static final Creator<NtnSignalStrength> CREATOR = + new Creator<NtnSignalStrength>() { + @Override public NtnSignalStrength createFromParcel(Parcel in) { + return new NtnSignalStrength(in); + } + + @Override public NtnSignalStrength[] newArray(int size) { + return new NtnSignalStrength[size]; + } + }; + + @Override + public int hashCode() { + return mLevel; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + + NtnSignalStrength that = (NtnSignalStrength) obj; + return mLevel == that.mLevel; + } + + @Override public String toString() { + return "NtnSignalStrength{" + + "mLevel=" + mLevel + + '}'; + } +} diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java new file mode 100644 index 000000000000..4b79590b9bc6 --- /dev/null +++ b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java @@ -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 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 notifying satellite signal strength change. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +public interface NtnSignalStrengthCallback { + /** + * Called when non-terrestrial network signal strength changes. + * @param ntnSignalStrength The new non-terrestrial network signal strength. + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + void onNtnSignalStrengthChanged(@NonNull NtnSignalStrength ntnSignalStrength); +} diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 7322aeb8bfca..21d93bd3077f 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -35,7 +35,9 @@ import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.ResultReceiver; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyFrameworkInitializer; +import android.telephony.TelephonyManager; import com.android.internal.telephony.IIntegerConsumer; import com.android.internal.telephony.ITelephony; @@ -77,6 +79,8 @@ public final class SatelliteManager { private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback, ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap<NtnSignalStrengthCallback, INtnSignalStrengthCallback> + sNtnSignalStrengthCallbackMap = new ConcurrentHashMap<>(); private final int mSubId; @@ -192,6 +196,14 @@ public final class SatelliteManager { public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility"; /** + * Bundle key to get the response from + * {@link #requestNtnSignalStrength(Executor, OutcomeReceiver)}. + * @hide + */ + + public static final String KEY_NTN_SIGNAL_STRENGTH = "ntn_signal_strength"; + + /** * The request was successfully processed. */ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1866,6 +1878,165 @@ public final class SatelliteManager { return new HashSet<>(); } + /** + * Request to get the signal strength of the satellite connection. + * + * <p> + * Note: This API is specifically designed for OEM enabled satellite connectivity only. + * For satellite connectivity enabled using carrier roaming, please refer to + * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * </p> + * + * @param executor The executor on which the callback will be called. + * @param callback The callback object to which the result will be delivered. If the request is + * successful, {@link OutcomeReceiver#onResult(Object)} will return an instance of + * {@link NtnSignalStrength} with a value of {@link NtnSignalStrength.NtnSignalStrengthLevel} + * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no + * signal strength data available. + * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} 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 or + * satellite is not supported. + */ + @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @NonNull + public void requestNtnSignalStrength(@NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver<NtnSignalStrength, SatelliteException> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + ResultReceiver receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode == SATELLITE_RESULT_SUCCESS) { + if (resultData.containsKey(KEY_NTN_SIGNAL_STRENGTH)) { + NtnSignalStrength ntnSignalStrength = + resultData.getParcelable(KEY_NTN_SIGNAL_STRENGTH, + NtnSignalStrength.class); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onResult(ntnSignalStrength))); + } else { + loge("KEY_NTN_SIGNAL_STRENGTH does not exist."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onError(new SatelliteException( + SATELLITE_RESULT_REQUEST_FAILED)))); + } + } else { + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onError(new SatelliteException(resultCode)))); + } + } + }; + telephony.requestNtnSignalStrength(mSubId, receiver); + } else { + throw new IllegalStateException("Telephony service is null."); + } + } catch (RemoteException ex) { + loge("requestNtnSignalStrength() RemoteException: " + ex); + ex.rethrowFromSystemServer(); + } + } + + /** + * Registers for NTN signal strength changed from satellite modem. + * + * <p> + * Note: This API is specifically designed for OEM enabled satellite connectivity only. + * For satellite connectivity enabled using carrier roaming, please refer to + * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * </p> + * + * @param executor The executor on which the callback will be called. + * @param callback The callback to handle the NTN signal strength changed event. + * + * @return 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. + */ + @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @SatelliteResult public int registerForNtnSignalStrengthChanged( + @NonNull @CallbackExecutor Executor executor, + @NonNull NtnSignalStrengthCallback callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + INtnSignalStrengthCallback internalCallback = + new INtnSignalStrengthCallback.Stub() { + @Override + public void onNtnSignalStrengthChanged( + NtnSignalStrength ntnSignalStrength) { + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> callback.onNtnSignalStrengthChanged( + ntnSignalStrength))); + } + }; + sNtnSignalStrengthCallbackMap.put(callback, internalCallback); + return telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback); + } else { + throw new IllegalStateException("Telephony service is null."); + } + } catch (RemoteException ex) { + loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex); + ex.rethrowFromSystemServer(); + } + return SATELLITE_RESULT_REQUEST_FAILED; + } + + /** + * Unregisters for NTN signal strength changed from satellite modem. + * If callback was not registered before, the request will be ignored. + * + * <p> + * Note: This API is specifically designed for OEM enabled satellite connectivity only. + * For satellite connectivity enabled using carrier roaming, please refer to + * {@link TelephonyManager#unregisterTelephonyCallback(TelephonyCallback)}.. + * </p> + * + * @param callback The callback that was passed to + * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}. + * + * @throws SecurityException if the caller doesn't have required permission. + * @throws IllegalStateException if the Telephony process is not currently available. + */ + @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public void unregisterForNtnSignalStrengthChanged(@NonNull NtnSignalStrengthCallback callback) { + Objects.requireNonNull(callback); + INtnSignalStrengthCallback internalCallback = + sNtnSignalStrengthCallbackMap.remove(callback); + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + if (internalCallback != null) { + telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback); + } else { + loge("unregisterForNtnSignalStrengthChanged: No internal callback."); + } + } else { + throw new IllegalStateException("Telephony service is null."); + } + } catch (RemoteException ex) { + loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex); + ex.rethrowFromSystemServer(); + } + + } + + private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer .getTelephonyServiceManager() diff --git a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl new file mode 100644 index 000000000000..b7712bd83cf6 --- /dev/null +++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl @@ -0,0 +1,28 @@ +/* + * 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.telephony.satellite.stub; + +import android.telephony.satellite.NtnSignalStrength; + +/** + * Consumer pattern for a request that receives the signal strength of non-terrestrial network from + * the SatelliteService. + * @hide + */ +oneway interface INtnSignalStrengthConsumer { + void accept(in NtnSignalStrength result); +} diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl index 7fda550c599c..6b47db1e2251 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -16,6 +16,7 @@ package android.telephony.satellite.stub; +import android.telephony.satellite.stub.INtnSignalStrengthConsumer; import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer; import android.telephony.satellite.stub.ISatelliteListener; import android.telephony.satellite.stub.SatelliteDatagram; @@ -454,4 +455,44 @@ oneway interface ISatellite { */ void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback, in IBooleanConsumer callback); + + /** + * Request to get the signal strength of the satellite connection. + * + * @param resultCallback The {@link SatelliteError} result of the operation. + * @param callback The callback to handle the NTN signal strength changed event. + */ + void requestSignalStrength(in IIntegerConsumer resultCallback, + in INtnSignalStrengthConsumer callback); + + /** + * The satellite service should report the NTN signal strength via + * ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes. + * + * @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_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + */ + void startSendingNtnSignalStrength(in IIntegerConsumer resultCallback); + + /** + * The satellite service should stop reporting NTN signal strength to the framework. This will + * be called when device is screen off to save power by not letting signal strength updates to + * wake up application processor. + * + * @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_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + */ + void stopSendingNtnSignalStrength(in IIntegerConsumer resultCallback); } diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl index d68716291b8e..d44ddfa1ee7f 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl @@ -16,6 +16,7 @@ package android.telephony.satellite.stub; +import android.telephony.satellite.stub.NtnSignalStrength; import android.telephony.satellite.stub.NTRadioTechnology; import android.telephony.satellite.stub.PointingInfo; import android.telephony.satellite.stub.SatelliteDatagram; @@ -58,4 +59,10 @@ oneway interface ISatelliteListener { * @param state The current satellite modem state. */ void onSatelliteModemStateChanged(in SatelliteModemState state); + + /** + * Called when NTN signal strength changes. + * @param ntnSignalStrength The new NTN signal strength. + */ + void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength); } diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl new file mode 100644 index 000000000000..f48900583167 --- /dev/null +++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl @@ -0,0 +1,30 @@ +/* + * 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.telephony.satellite.stub; + +import android.telephony.satellite.stub.NtnSignalStrengthLevel; + +/** + * @hide + */ +parcelable NtnSignalStrength { + /** + * Non-terrestrial signal strength. The value represents the level of signal strength which can + * be translated to the number of signal bars. + */ + NtnSignalStrengthLevel signalStrengthLevel; +} diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl new file mode 100644 index 000000000000..53b13733941c --- /dev/null +++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl @@ -0,0 +1,29 @@ +/* + * 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.telephony.satellite.stub; + +/** + * {@hide} + */ +@Backing(type="int") +enum NtnSignalStrengthLevel { + NTN_SIGNAL_STRENGTH_NONE = 0, + NTN_SIGNAL_STRENGTH_POOR = 1, + NTN_SIGNAL_STRENGTH_MODERATE = 2, + NTN_SIGNAL_STRENGTH_GOOD = 3, + NTN_SIGNAL_STRENGTH_GREAT = 4 +} diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java index 4cee01e8bd39..a636a6128e61 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -241,6 +241,30 @@ public class SatelliteImplBase extends SatelliteService { "requestIsSatelliteEnabledForCarrier"); } + @Override + public void requestSignalStrength(IIntegerConsumer resultCallback, + INtnSignalStrengthConsumer callback) throws RemoteException { + executeMethodAsync( + () -> SatelliteImplBase.this.requestSignalStrength(resultCallback, callback), + "requestSignalStrength"); + } + + @Override + public void startSendingNtnSignalStrength(IIntegerConsumer resultCallback) + throws RemoteException { + executeMethodAsync( + () -> SatelliteImplBase.this.startSendingNtnSignalStrength(resultCallback), + "startSendingNtnSignalStrength"); + } + + @Override + public void stopSendingNtnSignalStrength(IIntegerConsumer resultCallback) + throws RemoteException { + executeMethodAsync( + () -> SatelliteImplBase.this.stopSendingNtnSignalStrength(resultCallback), + "stopSendingNtnSignalStrength"); + } + // Call the methods with a clean calling identity on the executor and wait indefinitely for // the future to return. private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException { @@ -728,4 +752,35 @@ public class SatelliteImplBase extends SatelliteService { @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } + + /** + * Request to get the signal strength of the satellite connection. + * + * @param resultCallback The {@link SatelliteError} result of the operation. + * @param callback The callback to handle the NTN signal strength changed event. + */ + public void requestSignalStrength(@NonNull IIntegerConsumer resultCallback, + INtnSignalStrengthConsumer callback) { + // stub implementation + } + + /** + * Requests to deliver signal strength changed events through the + * {@link ISatelliteListener#onNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength)} + * callback. + * + * @param resultCallback The {@link SatelliteError} result of the operation. + */ + public void startSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback) { + // stub implementation + } + + /** + * Requests to stop signal strength changed events + * + * @param resultCallback The {@link SatelliteError} result of the operation. + */ + public void stopSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback){ + // stub implementation + } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 3aa5a5a14bc8..58e702688bd3 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -67,10 +67,12 @@ import android.telephony.ims.aidl.IImsRcsFeature; import android.telephony.ims.aidl.IImsRegistration; import android.telephony.ims.aidl.IImsRegistrationCallback; import android.telephony.ims.aidl.IRcsConfigCallback; +import android.telephony.satellite.INtnSignalStrengthCallback; import android.telephony.satellite.ISatelliteDatagramCallback; import android.telephony.satellite.ISatelliteTransmissionUpdateCallback; import android.telephony.satellite.ISatelliteProvisionStateCallback; import android.telephony.satellite.ISatelliteStateCallback; +import android.telephony.satellite.NtnSignalStrength; import android.telephony.satellite.SatelliteCapabilities; import android.telephony.satellite.SatelliteDatagram; import com.android.ims.internal.IImsServiceFeatureCallback; @@ -2837,7 +2839,6 @@ interface ITelephony { + "android.Manifest.permission.SATELLITE_COMMUNICATION)") void deprovisionSatelliteService(int subId, in String token, in IIntegerConsumer callback); - /** * Registers for provision state changed from satellite modem. * @@ -3071,4 +3072,40 @@ interface ITelephony { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") int[] getSatelliteAttachRestrictionReasonsForCarrier(int subId); + + /** + * Request to get the signal strength of the satellite connection. + * + * @param subId The subId of the subscription to request for. + * @param receiver Result receiver to get the error code of the request and the current signal + * strength of the satellite connection. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.SATELLITE_COMMUNICATION)") + void requestNtnSignalStrength(int subId, in ResultReceiver receiver); + + /** + * Registers for NTN signal strength changed from satellite modem. + * + * @param subId The subId of the subscription to request for. + * @param callback The callback to handle the NTN signal strength changed event. + * + * @return The {@link SatelliteResult} result of the operation. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.SATELLITE_COMMUNICATION)") + int registerForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback); + + /** + * Unregisters for NTN signal strength changed from satellite modem. + * If callback was not registered before, the request will be ignored. + * + * @param subId The subId of the subscription to unregister for provision state changed. + * @param callback The callback that was passed to + * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.SATELLITE_COMMUNICATION)") + void unregisterForNtnSignalStrengthChanged(int subId, + in INtnSignalStrengthCallback callback); } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 72e4389e0788..a20f26c1807b 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -120,31 +120,6 @@ public interface RILConstants { int BLOCKED_DUE_TO_CALL = 69; /* SMS is blocked due to call control */ int RF_HARDWARE_ISSUE = 70; /* RF HW issue is detected */ int NO_RF_CALIBRATION_INFO = 71; /* No RF calibration in device */ - int ENCODING_NOT_SUPPORTED = 72; /* The encoding scheme is not supported by - either the network or the MS. */ - int FEATURE_NOT_SUPPORTED = 73; /* The requesting feature is not supported by - the service provider. */ - int INVALID_CONTACT = 74; /* The contact to be added is either not - existing or not valid. */ - int MODEM_INCOMPATIBLE = 75; /* The modem of the MS is not compatible with - the service provider. */ - int NETWORK_TIMEOUT = 76; /* Modem timeout to receive ACK or response from - network after sending a request to it. */ - int NO_SATELLITE_SIGNAL = 77; /* Modem fails to communicate with the satellite - network since there is no satellite signal.*/ - int NOT_SUFFICIENT_ACCOUNT_BALANCE = 78; /* The request cannot be performed since the - subscriber's account balance is not - sufficient. */ - int RADIO_TECHNOLOGY_NOT_SUPPORTED = 79; /* The radio technology is not supported by the - service provider. */ - int SUBSCRIBER_NOT_AUTHORIZED = 80; /* The subscription is not authorized to - register with the service provider. */ - int SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL = 81; /* While processing a request from the - Framework the satellite modem detects - terrestrial signal, aborts the request, and - switches to the terrestrial network. */ - int UNIDENTIFIED_SUBSCRIBER = 82; /* The subscriber is not registered with the - service provider */ // Below is list of OEM specific error codes which can by used by OEMs in case they don't want to // reveal particular replacement for Generic failure @@ -568,22 +543,7 @@ public interface RILConstants { int RIL_REQUEST_IS_N1_MODE_ENABLED = 242; int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243; int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244; - int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245; - int RIL_REQUEST_SET_SATELLITE_POWER = 246; - int RIL_REQUEST_GET_SATELLITE_POWER = 247; - int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248; - int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249; - int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250; - int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251; - int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252; - int RIL_REQUEST_GET_SATELLITE_MODE = 253; - int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254; - int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255; - int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256; - int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257; - int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258; - int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 259; - int RIL_REQUEST_SET_SATELLITE_PLMN = 260; + int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 245; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; @@ -645,13 +605,6 @@ public interface RILConstants { int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053; int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054; int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055; - int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056; - int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057; - int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058; - int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059; - int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060; - int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061; - int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062; /* The following unsols are not defined in RIL.h */ int RIL_UNSOL_HAL_NON_RIL_BASE = 1100; diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt index 359845dc0de6..47d6d235848f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt @@ -36,7 +36,8 @@ import org.junit.runners.Parameterized /** * Test launching a secondary Activity into Picture-In-Picture mode. * - * Setup: Start from a split A|B. Transition: B enters PIP, observe the window shrink to the bottom + * Setup: Start from a split A|B. + * Transition: B enters PIP, observe the window first goes fullscreen then shrink to the bottom * right corner on screen. * * To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest` @@ -63,7 +64,16 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) : } } - /** Main and secondary activity start from a split each taking half of the screen. */ + /** + * We expect the background layer to be visible during this transition. + */ + @Presubmit + @Test + override fun backgroundLayerNeverVisible(): Unit {} + + /** + * Main and secondary activity start from a split each taking half of the screen. + */ @Presubmit @Test fun layersStartFromEqualSplit() { @@ -109,7 +119,7 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) : .isVisible(TRANSITION_SNAPSHOT) .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) .then() - .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT, isOptional = true) } flicker.assertLayersEnd { visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) diff --git a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp index bfd35083366e..c901efa707f4 100644 --- a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp +++ b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp @@ -26,4 +26,5 @@ package { android_test { name: "AaptAutoVersionTest", sdk_version: "current", + use_resource_processor: false, } diff --git a/tools/aapt2/integration-tests/BasicTest/Android.bp b/tools/aapt2/integration-tests/BasicTest/Android.bp index 7db9d2698cc7..d0649ea4ef9c 100644 --- a/tools/aapt2/integration-tests/BasicTest/Android.bp +++ b/tools/aapt2/integration-tests/BasicTest/Android.bp @@ -26,4 +26,5 @@ package { android_test { name: "AaptBasicTest", sdk_version: "current", + use_resource_processor: false, } diff --git a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp index 80404eeb8d8e..ebb4e9f479d6 100644 --- a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp +++ b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp @@ -24,9 +24,9 @@ package { } android_test { - name: "AaptTestStaticLib_App", sdk_version: "current", + use_resource_processor: false, srcs: ["src/**/*.java"], asset_dirs: [ "assets", diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp index a84da43c70c8..ee12a92906a8 100644 --- a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp @@ -26,6 +26,7 @@ package { android_library { name: "AaptTestStaticLib_LibOne", sdk_version: "current", + use_resource_processor: false, srcs: ["src/**/*.java"], resource_dirs: ["res"], } diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp index d386c3a35d20..83b2362496fc 100644 --- a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp @@ -26,6 +26,7 @@ package { android_library { name: "AaptTestStaticLib_LibTwo", sdk_version: "current", + use_resource_processor: false, srcs: ["src/**/*.java"], resource_dirs: ["res"], libs: ["AaptTestStaticLib_LibOne"], diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp index 1e8cf86ed811..15a6a207d6d1 100644 --- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp +++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp @@ -26,4 +26,5 @@ package { android_test { name: "AaptSymlinkTest", sdk_version: "current", + use_resource_processor: false, } diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index 0599c1d01906..226e2fadf735 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -7,9 +7,38 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +// Visibility only for ravenwood prototype uses. +genrule_defaults { + name: "hoststubgen-for-prototype-only-genrule", + visibility: [ + ":__subpackages__", + "//frameworks/base/ravenwood:__subpackages__", + ], +} + +// Visibility only for ravenwood prototype uses. +java_defaults { + name: "hoststubgen-for-prototype-only-java", + visibility: [ + ":__subpackages__", + "//frameworks/base/ravenwood:__subpackages__", + ], +} + +// Visibility only for ravenwood prototype uses. +filegroup_defaults { + name: "hoststubgen-for-prototype-only-filegroup", + visibility: [ + ":__subpackages__", + "//frameworks/base/ravenwood:__subpackages__", + ], +} + // This library contains the standard hoststubgen annotations. +// This is only for the prototype. The productionized version is "ravenwood-annotations". java_library { name: "hoststubgen-annotations", + defaults: ["hoststubgen-for-prototype-only-java"], srcs: [ "annotations-src/**/*.java", ], @@ -18,7 +47,6 @@ java_library { // Seems like we need it to avoid circular deps. // Copied it from "app-compat-annotations". sdk_version: "core_current", - visibility: ["//visibility:public"], } // This library contains helper classes used in the host side test environment at runtime. @@ -55,12 +83,13 @@ java_binary_host { } // File that contains the standard command line argumetns to hoststubgen. +// This is only for the prototype. The productionized version is "ravenwood-standard-options". filegroup { name: "hoststubgen-standard-options", + defaults: ["hoststubgen-for-prototype-only-filegroup"], srcs: [ "hoststubgen-standard-options.txt", ], - visibility: ["//visibility:public"], } hoststubgen_common_options = "$(location hoststubgen) " + @@ -93,7 +122,6 @@ genrule_defaults { "hoststubgen_keep_all.txt", "hoststubgen_dump.txt", ], - // visibility: ["//visibility:public"], } // Generate the stub/impl from framework-all, with hidden APIs. @@ -111,8 +139,10 @@ java_genrule_host { } // Extract the stub jar from "framework-all-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-hidden-api-host-stub", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-hidden-api-host{host_stub.jar}", @@ -120,12 +150,13 @@ java_genrule_host { out: [ "host_stub.jar", ], - visibility: ["//visibility:public"], } // Extract the impl jar from "framework-all-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-hidden-api-host-impl", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-hidden-api-host{host_impl.jar}", @@ -133,11 +164,11 @@ java_genrule_host { out: [ "host_impl.jar", ], - visibility: ["//visibility:public"], } // Generate the stub/impl from framework-all, with only public/system/test APIs, without // hidden APIs. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-test-api-host", defaults: ["hoststubgen-command-defaults"], @@ -154,8 +185,10 @@ java_genrule_host { } // Extract the stub jar from "framework-all-test-api-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-test-api-host-stub", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-test-api-host{host_stub.jar}", @@ -163,12 +196,13 @@ java_genrule_host { out: [ "host_stub.jar", ], - visibility: ["//visibility:public"], } // Extract the impl jar from "framework-all-test-api-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-test-api-host-impl", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-test-api-host{host_impl.jar}", @@ -176,7 +210,6 @@ java_genrule_host { out: [ "host_impl.jar", ], - visibility: ["//visibility:public"], } // This library contains helper classes to build hostside tests/targets. @@ -186,6 +219,7 @@ java_genrule_host { // Ideally this library should be empty. java_library_host { name: "hoststubgen-helper-framework-buildtime", + defaults: ["hoststubgen-for-prototype-only-java"], srcs: [ "helper-framework-buildtime-src/**/*.java", ], @@ -195,13 +229,13 @@ java_library_host { "framework-all-hidden-api-host-impl", "junit", ], - visibility: ["//visibility:public"], } // This module contains "fake" libcore/dalvik classes, framework native substitution, etc, // that are needed at runtime. java_library_host { name: "hoststubgen-helper-framework-runtime", + defaults: ["hoststubgen-for-prototype-only-java"], srcs: [ "helper-framework-runtime-src/**/*.java", ], @@ -209,7 +243,6 @@ java_library_host { "hoststubgen-helper-runtime", "framework-all-hidden-api-host-impl", ], - visibility: ["//visibility:public"], } // Defaults for host side test modules. |