summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java25
-rw-r--r--apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt8
-rw-r--r--core/java/android/app/StatusBarManager.java17
-rw-r--r--core/java/android/app/notification.aconfig7
-rw-r--r--core/java/android/app/servertransaction/ActivityRelaunchItem.java7
-rw-r--r--core/java/android/app/servertransaction/LaunchActivityItem.java9
-rw-r--r--core/java/android/app/servertransaction/MoveToDisplayItem.java7
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutor.java71
-rw-r--r--core/java/android/app/servertransaction/WindowStateResizeItem.java24
-rw-r--r--core/java/android/content/Context.java2
-rw-r--r--core/java/android/net/LocalSocket.java6
-rw-r--r--core/java/android/os/flags.aconfig7
-rw-r--r--core/java/android/service/contentcapture/ContentCaptureService.java3
-rw-r--r--core/java/android/view/InsetsAnimationThreadControlRunner.java8
-rw-r--r--core/java/android/view/InsetsController.java9
-rw-r--r--core/java/android/view/View.java4
-rw-r--r--core/java/android/view/ViewRootImpl.java103
-rw-r--r--core/java/android/view/contentcapture/ChildContentCaptureSession.java86
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureManager.java54
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureSession.java82
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java342
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSessionV2.java1184
-rw-r--r--core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java6
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java73
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java239
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java67
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java99
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java89
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java65
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java82
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java6
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java61
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java530
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java428
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java33
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java22
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java23
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java57
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java4
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java8
-rw-r--r--libs/incident/libincident.map.txt20
-rw-r--r--media/TEST_MAPPING4
-rw-r--r--media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java191
-rw-r--r--native/android/libandroid.map.txt8
-rw-r--r--packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml25
-rw-r--r--packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml30
-rw-r--r--packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml30
-rw-r--r--packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml37
-rw-r--r--packages/CredentialManager/res/values/colors.xml38
-rw-r--r--packages/CredentialManager/res/values/dimens.xml22
-rw-r--r--packages/CredentialManager/res/values/styles.xml38
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt77
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt88
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-rw-r--r--packages/SystemUI/animation/Android.bp (renamed from packages/SystemUI/animation/core/Android.bp)0
-rw-r--r--packages/SystemUI/animation/AndroidManifest.xml (renamed from packages/SystemUI/animation/core/AndroidManifest.xml)0
-rw-r--r--packages/SystemUI/animation/build.gradle (renamed from packages/SystemUI/animation/core/build.gradle)4
-rw-r--r--packages/SystemUI/animation/res/anim/launch_dialog_enter.xml (renamed from packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml)0
-rw-r--r--packages/SystemUI/animation/res/anim/launch_dialog_exit.xml (renamed from packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml)0
-rw-r--r--packages/SystemUI/animation/res/values/ids.xml (renamed from packages/SystemUI/animation/core/res/values/ids.xml)0
-rw-r--r--packages/SystemUI/animation/res/values/styles.xml (renamed from packages/SystemUI/animation/core/res/values/styles.xml)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt)42
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt)152
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/util/AnimatorExtensions.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt)0
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt (renamed from packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt5
-rw-r--r--packages/SystemUI/shared/res/values/ids.xml24
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/CoreStartable.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java138
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java87
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt180
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java67
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt26
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java4
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java11
-rw-r--r--services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java8
-rw-r--r--services/core/java/com/android/server/media/AudioManagerRouteController.java (renamed from services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java)8
-rw-r--r--services/core/java/com/android/server/media/DeviceRouteController.java2
-rw-r--r--services/core/java/com/android/server/media/TEST_MAPPING5
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyLogger.java2
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java69
-rw-r--r--services/core/java/com/android/server/notification/ZenModeEventLogger.java42
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java7
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java92
-rw-r--r--services/core/java/com/android/server/policy/WindowWakeUpPolicy.java176
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java16
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java25
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java4
-rw-r--r--services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java (renamed from services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java)6
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java74
-rw-r--r--services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java3
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java76
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java5
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java100
-rw-r--r--services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java3
178 files changed, 4869 insertions, 1716 deletions
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
index e5a06c9bd146..3c361d772d3d 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
@@ -16,12 +16,14 @@
package android.graphics.perftests;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Color;
+import android.graphics.ColorSpace;
import android.graphics.ImageDecoder;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
@@ -104,15 +106,36 @@ public class CanvasPerfTest {
}
@Test
- public void testCreateScaledBitmap() throws IOException {
+ public void testCreateScaledSrgbBitmap() throws IOException {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final Context context = InstrumentationRegistry.getContext();
Bitmap source = ImageDecoder.decodeBitmap(
ImageDecoder.createSource(context.getResources(), R.drawable.fountain_night),
(decoder, info, source1) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB));
});
source.setGainmap(null);
+ assertEquals(source.getColorSpace().getId(), ColorSpace.Named.SRGB.ordinal());
+
+ while (state.keepRunning()) {
+ Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
+ .recycle();
+ }
+ }
+
+ @Test
+ public void testCreateScaledP3Bitmap() throws IOException {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getContext();
+ Bitmap source = ImageDecoder.decodeBitmap(
+ ImageDecoder.createSource(context.getResources(), R.drawable.fountain_night),
+ (decoder, info, source1) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+ });
+ source.setGainmap(null);
+ assertEquals(source.getColorSpace().getId(), ColorSpace.Named.DISPLAY_P3.ordinal());
while (state.keepRunning()) {
Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index 4352c8ae982e..ea10690bd672 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -24,11 +24,11 @@ import android.content.pm.parsing.result.ParseTypeImpl
import android.content.res.TypedArray
import android.perftests.utils.BenchmarkState
import android.perftests.utils.PerfStatusReporter
-import android.util.ArraySet
import androidx.test.filters.LargeTest
import com.android.internal.pm.parsing.pkg.PackageImpl
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
import com.android.internal.util.ConcurrentUtils
+import com.android.server.SystemConfig
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.ArrayBlockingQueue
@@ -217,8 +217,10 @@ public class PackageParsingPerfTest {
isCoreApp,
this,
)
- override fun getHiddenApiWhitelistedApps() = ArraySet<String>()
- override fun getInstallConstraintsAllowlist() = ArraySet<String>()
+ override fun getHiddenApiWhitelistedApps() =
+ SystemConfig.getInstance().hiddenApiWhitelistedApps
+ override fun getInstallConstraintsAllowlist() =
+ SystemConfig.getInstance().installConstraintsAllowlist
})
override fun parseImpl(file: File) =
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 385fd509757b..14195c473c6d 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -241,6 +241,23 @@ public class StatusBarManager {
public static final int CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE = 3;
/**
+ * Broadcast action: sent to apps that hold the status bar permission when
+ * KeyguardManager#setPrivateNotificationsAllowed() is changed.
+ *
+ * Extras: #EXTRA_KM_PRIVATE_NOTIFS_ALLOWED
+ * @hide
+ */
+ public static final String ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED
+ = "android.app.action.KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED";
+
+ /**
+ * Boolean, the latest value of KeyguardManager#getPrivateNotificationsAllowed()
+ * @hide
+ */
+ public static final String EXTRA_KM_PRIVATE_NOTIFS_ALLOWED
+ = "android.app.extra.KM_PRIVATE_NOTIFS_ALLOWED";
+
+ /**
* Session flag for {@link #registerSessionListener} indicating the listener
* is interested in sessions on the keygaurd.
* Keyguard Session Boundaries:
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index fb0edb954539..d11c6c50da3b 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -36,3 +36,10 @@ flag {
description: "Guards the security fix that ensures all URIs in intents and Person.java are valid"
bug: "281044385"
}
+
+flag {
+ name: "keyguard_private_notifications"
+ namespace: "systemui"
+ description: "Fixes the behavior of KeyguardManager#setPrivateNotificationsAllowed()"
+ bug: "309920145"
+}
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index 3ce094ef7467..cbb0ae784f82 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.ResultInfo;
+import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.os.IBinder;
import android.os.Parcel;
@@ -85,6 +86,12 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
client.reportRelaunch(r);
}
+ @Nullable
+ @Override
+ public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
+ return client.getActivity(getActivityToken());
+ }
+
// ObjectPoolItem implementation
private ActivityRelaunchItem() {}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index d2ef65aec698..1190bf6a604b 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -24,12 +24,14 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityClient;
import android.app.ActivityOptions;
+import android.app.ActivityThread;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.IActivityClientController;
import android.app.ProfilerInfo;
import android.app.ResultInfo;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
@@ -115,6 +117,13 @@ public class LaunchActivityItem extends ClientTransactionItem {
client.countLaunchingActivities(-1);
}
+ @Nullable
+ @Override
+ public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
+ // LaunchActivityItem may update the global config with #mCurConfig.
+ return ActivityThread.currentApplication();
+ }
+
// ObjectPoolItem implementation
private LaunchActivityItem() {}
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 961da19daeed..1353d1679427 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
+import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
@@ -55,6 +56,12 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
+ @Nullable
+ @Override
+ public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
+ return client.getActivity(getActivityToken());
+ }
+
// ObjectPoolItem implementation
private MoveToDisplayItem() {}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index ee48e431ba8e..5dd4eb72c201 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -41,6 +41,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Process;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
@@ -62,8 +63,11 @@ public class TransactionExecutor {
private final PendingTransactionActions mPendingActions = new PendingTransactionActions();
private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper();
- /** Keeps track of display ids whose Configuration got updated within a transaction. */
- private final ArraySet<Integer> mConfigUpdatedDisplayIds = new ArraySet<>();
+ /**
+ * Keeps track of the Context whose Configuration got updated within a transaction, mapping to
+ * the config before the transaction.
+ */
+ private final ArrayMap<Context, Configuration> mContextToPreChangedConfigMap = new ArrayMap<>();
/** Initialize an instance with transaction handler, that will execute all requested actions. */
public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) {
@@ -91,16 +95,35 @@ public class TransactionExecutor {
executeLifecycleState(transaction);
}
- if (!mConfigUpdatedDisplayIds.isEmpty()) {
+ if (!mContextToPreChangedConfigMap.isEmpty()) {
// Whether this transaction should trigger DisplayListener#onDisplayChanged.
- final ClientTransactionListenerController controller =
- ClientTransactionListenerController.getInstance();
- final int displayCount = mConfigUpdatedDisplayIds.size();
- for (int i = 0; i < displayCount; i++) {
- final int displayId = mConfigUpdatedDisplayIds.valueAt(i);
- controller.onDisplayChanged(displayId);
+ try {
+ // Calculate display ids that have config changed.
+ final ArraySet<Integer> configUpdatedDisplayIds = new ArraySet<>();
+ final int contextCount = mContextToPreChangedConfigMap.size();
+ for (int i = 0; i < contextCount; i++) {
+ final Context context = mContextToPreChangedConfigMap.keyAt(i);
+ final Configuration preTransactionConfig =
+ mContextToPreChangedConfigMap.valueAt(i);
+ final Configuration postTransactionConfig = context.getResources()
+ .getConfiguration();
+ if (!areConfigurationsEqualForDisplay(
+ postTransactionConfig, preTransactionConfig)) {
+ configUpdatedDisplayIds.add(context.getDisplayId());
+ }
+ }
+
+ // Dispatch the display changed callbacks.
+ final ClientTransactionListenerController controller =
+ ClientTransactionListenerController.getInstance();
+ final int displayCount = configUpdatedDisplayIds.size();
+ for (int i = 0; i < displayCount; i++) {
+ final int displayId = configUpdatedDisplayIds.valueAt(i);
+ controller.onDisplayChanged(displayId);
+ }
+ } finally {
+ mContextToPreChangedConfigMap.clear();
}
- mConfigUpdatedDisplayIds.clear();
}
mPendingActions.clear();
@@ -182,26 +205,24 @@ public class TransactionExecutor {
}
}
- // Can't read flag from isolated process.
- final boolean isBundleClientTransactionFlagEnabled = !Process.isIsolated()
- && bundleClientTransactionFlag();
- final Context configUpdatedContext = isBundleClientTransactionFlagEnabled
+ final boolean shouldTrackConfigUpdatedContext =
+ // No configuration change for local transaction.
+ !mTransactionHandler.isExecutingLocalTransaction()
+ // Can't read flag from isolated process.
+ && !Process.isIsolated()
+ && bundleClientTransactionFlag();
+ final Context configUpdatedContext = shouldTrackConfigUpdatedContext
? item.getContextToUpdate(mTransactionHandler)
: null;
- final Configuration preExecutedConfig = configUpdatedContext != null
- ? new Configuration(configUpdatedContext.getResources().getConfiguration())
- : null;
+ if (configUpdatedContext != null
+ && !mContextToPreChangedConfigMap.containsKey(configUpdatedContext)) {
+ // Keep track of the first pre-executed config of each changed Context.
+ mContextToPreChangedConfigMap.put(configUpdatedContext,
+ new Configuration(configUpdatedContext.getResources().getConfiguration()));
+ }
item.execute(mTransactionHandler, mPendingActions);
- if (configUpdatedContext != null) {
- final Configuration postExecutedConfig = configUpdatedContext.getResources()
- .getConfiguration();
- if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
- mConfigUpdatedDisplayIds.add(configUpdatedContext.getDisplayId());
- }
- }
-
item.postExecute(mTransactionHandler, mPendingActions);
if (r == null) {
// Launch activity request will create an activity record.
diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
index 7d3eb8783c04..193b03ced0b0 100644
--- a/core/java/android/app/servertransaction/WindowStateResizeItem.java
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -22,9 +22,12 @@ import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.app.ClientTransactionHandler;
+import android.content.Context;
import android.os.Parcel;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.MergedConfiguration;
import android.view.IWindow;
import android.view.InsetsState;
@@ -52,6 +55,11 @@ public class WindowStateResizeItem extends ClientTransactionItem {
@Override
public void execute(@NonNull ClientTransactionHandler client,
@NonNull PendingTransactionActions pendingActions) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
+ mReportDraw ? "windowResizedReport" : "windowResized");
+ if (mWindow instanceof ResizeListener listener) {
+ listener.onExecutingWindowStateResizeItem();
+ }
try {
mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout,
mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing);
@@ -59,6 +67,14 @@ public class WindowStateResizeItem extends ClientTransactionItem {
// Should be a local call.
throw new RuntimeException(e);
}
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ @Nullable
+ @Override
+ public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
+ // WindowStateResizeItem may update the global config with #mConfiguration.
+ return ActivityThread.currentApplication();
}
// ObjectPoolItem implementation
@@ -80,7 +96,7 @@ public class WindowStateResizeItem extends ClientTransactionItem {
instance.mFrames = new ClientWindowFrames(frames);
instance.mReportDraw = reportDraw;
instance.mConfiguration = new MergedConfiguration(configuration);
- instance.mInsetsState = new InsetsState(insetsState);
+ instance.mInsetsState = new InsetsState(insetsState, true /* copySources */);
instance.mForceLayout = forceLayout;
instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
instance.mDisplayId = displayId;
@@ -190,4 +206,10 @@ public class WindowStateResizeItem extends ClientTransactionItem {
+ ", configuration=" + mConfiguration
+ "}";
}
+
+ /** The interface for IWindow to perform resize directly if possible. */
+ public interface ResizeListener {
+ /** Notifies that IWindow#resized is going to be called from WindowStateResizeItem. */
+ void onExecutingWindowStateResizeItem();
+ }
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b75c64dcc3c1..fa76e3976a58 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3547,6 +3547,8 @@ public abstract class Context {
*
* @param receiver The BroadcastReceiver to unregister.
*
+ * @throws IllegalArgumentException if the {@code receiver} was not previously registered or
+ * already unregistered.
* @see #registerReceiver
*/
public abstract void unregisterReceiver(BroadcastReceiver receiver);
diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java
index b69410cf7d73..a86396cd7c8d 100644
--- a/core/java/android/net/LocalSocket.java
+++ b/core/java/android/net/LocalSocket.java
@@ -196,7 +196,8 @@ public class LocalSocket implements Closeable {
}
/**
- * Retrieves the input stream for this instance.
+ * Retrieves the input stream for this instance. Closing this stream is equivalent to closing
+ * the entire socket and its associated streams using {@link #close()}.
*
* @return input stream
* @throws IOException if socket has been closed or cannot be created.
@@ -207,7 +208,8 @@ public class LocalSocket implements Closeable {
}
/**
- * Retrieves the output stream for this instance.
+ * Retrieves the output stream for this instance. Closing this stream is equivalent to closing
+ * the entire socket and its associated streams using {@link #close()}.
*
* @return output stream
* @throws IOException if socket has been closed or cannot be created.
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 83d237d6e53b..d64614628b61 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -91,3 +91,10 @@ flag {
is_fixed_read_only: true
bug: "315037695"
}
+
+flag {
+ name: "strict_mode_restricted_network"
+ namespace: "backstage_power"
+ description: "Guards StrictMode APIs for detecting restricted network access."
+ bug: "317250784"
+}
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 78248d9775f4..e1965ef25562 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -53,7 +53,6 @@ import android.view.contentcapture.ContentCaptureSessionId;
import android.view.contentcapture.DataRemovalRequest;
import android.view.contentcapture.DataShareRequest;
import android.view.contentcapture.IContentCaptureDirectManager;
-import android.view.contentcapture.MainContentCaptureSession;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.FrameworkStatsLog;
@@ -724,7 +723,7 @@ public abstract class ContentCaptureService extends Service {
final Bundle extras;
if (binder != null) {
extras = new Bundle();
- extras.putBinder(MainContentCaptureSession.EXTRA_BINDER, binder);
+ extras.putBinder(ContentCaptureSession.EXTRA_BINDER, binder);
} else {
extras = null;
}
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index f7b9aa26ad96..079991a81e77 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -76,7 +76,7 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW,
"InsetsAsyncAnimation: " + WindowInsets.Type.toString(runner.getTypes()),
runner.getTypes());
- releaseControls(mControl.getControls());
+ InsetsController.releaseControls(mControl.getControls());
mMainThreadHandler.post(() ->
mOuterCallbacks.notifyFinished(InsetsAnimationThreadControlRunner.this, shown));
}
@@ -130,12 +130,6 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro
});
}
- private void releaseControls(SparseArray<InsetsSourceControl> controls) {
- for (int i = controls.size() - 1; i >= 0; i--) {
- controls.valueAt(i).release(SurfaceControl::release);
- }
- }
-
@Override
@UiThread
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 147c15bea076..dd091571fbaf 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1353,6 +1353,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
});
}
+ // The leashes are copied, but they won't be used.
+ releaseControls(controls);
+
// The requested visibilities should be delayed as well. Otherwise, we might override
// the insets visibility before playing animation.
setRequestedVisibleTypes(mReportedRequestedVisibleTypes, types);
@@ -1422,6 +1425,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
}
+ static void releaseControls(SparseArray<InsetsSourceControl> controls) {
+ for (int i = controls.size() - 1; i >= 0; i--) {
+ controls.valueAt(i).release(SurfaceControl::release);
+ }
+ }
+
// TODO(b/242962223): Make this setter restrictive.
@Override
public void setSystemDrivenInsetsAnimationLoggingListener(
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ec994590e339..442ea661585c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10760,11 +10760,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return;
}
- session.internalNotifyViewTreeEvent(/* started= */ true);
+ session.notifyViewTreeEvent(/* started= */ true);
try {
dispatchProvideContentCaptureStructure();
} finally {
- session.internalNotifyViewTreeEvent(/* started= */ false);
+ session.notifyViewTreeEvent(/* started= */ false);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1f81a6418962..32afe065857d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -111,6 +111,7 @@ import android.app.ICompatCameraControlCallback;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
import android.app.compat.CompatChanges;
+import android.app.servertransaction.WindowStateResizeItem;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -208,7 +209,6 @@ import android.view.animation.Interpolator;
import android.view.autofill.AutofillManager;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.ContentCaptureSession;
-import android.view.contentcapture.MainContentCaptureSession;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
@@ -2012,26 +2012,24 @@ public final class ViewRootImpl implements ViewParent,
}
/** Handles messages {@link #MSG_RESIZED} and {@link #MSG_RESIZED_REPORT}. */
- private void handleResized(int msg, SomeArgs args) {
+ private void handleResized(ClientWindowFrames frames, boolean reportDraw,
+ MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
+ boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) {
if (!mAdded) {
return;
}
- final ClientWindowFrames frames = (ClientWindowFrames) args.arg1;
- final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2;
CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration);
- final boolean forceNextWindowRelayout = args.argi1 != 0;
- final int displayId = args.argi3;
- final boolean dragResizing = args.argi5 != 0;
-
final Rect frame = frames.frame;
final Rect displayFrame = frames.displayFrame;
final Rect attachedFrame = frames.attachedFrame;
if (mTranslator != null) {
+ mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
mTranslator.translateRectInScreenToAppWindow(frame);
mTranslator.translateRectInScreenToAppWindow(displayFrame);
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
+ mInsetsController.onStateChanged(insetsState);
final float compatScale = frames.compatScale;
final boolean frameChanged = !mWinFrame.equals(frame);
final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
@@ -2040,8 +2038,8 @@ public final class ViewRootImpl implements ViewParent,
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
final boolean dragResizingChanged = mPendingDragResizing != dragResizing;
- if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
- && !displayChanged && !forceNextWindowRelayout
+ if (!reportDraw && !frameChanged && !configChanged && !attachedFrameChanged
+ && !displayChanged && !forceLayout
&& !compatScaleChanged && !dragResizingChanged) {
return;
}
@@ -2073,11 +2071,11 @@ public final class ViewRootImpl implements ViewParent,
}
}
- mForceNextWindowRelayout |= forceNextWindowRelayout;
- mPendingAlwaysConsumeSystemBars = args.argi2 != 0;
- mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId;
+ mForceNextWindowRelayout |= forceLayout;
+ mPendingAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+ mSyncSeqId = syncSeqId > mSyncSeqId ? syncSeqId : mSyncSeqId;
- if (msg == MSG_RESIZED_REPORT) {
+ if (reportDraw) {
reportNextDraw("resized");
}
@@ -4104,7 +4102,7 @@ public final class ViewRootImpl implements ViewParent,
final ContentCaptureManager manager = mAttachInfo.mContentCaptureManager;
if (manager != null && mAttachInfo.mContentCaptureEvents != null) {
- final MainContentCaptureSession session = manager.getMainContentCaptureSession();
+ final ContentCaptureSession session = manager.getMainContentCaptureSession();
session.notifyContentCaptureEvents(mAttachInfo.mContentCaptureEvents);
}
mAttachInfo.mContentCaptureEvents = null;
@@ -5021,7 +5019,7 @@ public final class ViewRootImpl implements ViewParent,
// Initial dispatch of window bounds to content capture
if (mAttachInfo.mContentCaptureManager != null) {
- MainContentCaptureSession session =
+ ContentCaptureSession session =
mAttachInfo.mContentCaptureManager.getMainContentCaptureSession();
session.notifyWindowBoundsChanged(session.getId(),
getConfiguration().windowConfiguration.getBounds());
@@ -6232,8 +6230,17 @@ public final class ViewRootImpl implements ViewParent,
case MSG_RESIZED:
case MSG_RESIZED_REPORT: {
final SomeArgs args = (SomeArgs) msg.obj;
- mInsetsController.onStateChanged((InsetsState) args.arg3);
- handleResized(msg.what, args);
+ final ClientWindowFrames frames = (ClientWindowFrames) args.arg1;
+ final boolean reportDraw = msg.what == MSG_RESIZED_REPORT;
+ final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2;
+ final InsetsState insetsState = (InsetsState) args.arg3;
+ final boolean forceLayout = args.argi1 != 0;
+ final boolean alwaysConsumeSystemBars = args.argi2 != 0;
+ final int displayId = args.argi3;
+ final int syncSeqId = args.argi4;
+ final boolean dragResizing = args.argi5 != 0;
+ handleResized(frames, reportDraw, mergedConfiguration, insetsState, forceLayout,
+ alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
args.recycle();
break;
}
@@ -8797,7 +8804,7 @@ public final class ViewRootImpl implements ViewParent,
mSurfaceControl.setTransformHint(transformHint);
if (mAttachInfo.mContentCaptureManager != null) {
- MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
+ ContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
.getMainContentCaptureSession();
mainSession.notifyWindowBoundsChanged(mainSession.getId(),
getConfiguration().windowConfiguration.getBounds());
@@ -9379,20 +9386,8 @@ public final class ViewRootImpl implements ViewParent,
boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) {
Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
SomeArgs args = SomeArgs.obtain();
- final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
- if (sameProcessCall) {
- insetsState = new InsetsState(insetsState, true /* copySource */);
- }
- if (mTranslator != null) {
- mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
- }
- if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
- ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchResized",
- getInsetsController().getHost().getInputMethodManager(), null /* icProto */);
- }
- args.arg1 = sameProcessCall ? new ClientWindowFrames(frames) : frames;
- args.arg2 = sameProcessCall && mergedConfiguration != null
- ? new MergedConfiguration(mergedConfiguration) : mergedConfiguration;
+ args.arg1 = frames;
+ args.arg2 = mergedConfiguration;
args.arg3 = insetsState;
args.argi1 = forceLayout ? 1 : 0;
args.argi2 = alwaysConsumeSystemBars ? 1 : 0;
@@ -10815,9 +10810,10 @@ public final class ViewRootImpl implements ViewParent,
}
}
- static class W extends IWindow.Stub {
+ static class W extends IWindow.Stub implements WindowStateResizeItem.ResizeListener {
private final WeakReference<ViewRootImpl> mViewAncestor;
private final IWindowSession mWindowSession;
+ private boolean mIsFromResizeItem;
W(ViewRootImpl viewAncestor) {
mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
@@ -10825,17 +10821,46 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
+ public void onExecutingWindowStateResizeItem() {
+ mIsFromResizeItem = true;
+ }
+
+ @Override
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
boolean dragResizing) {
+ final boolean isFromResizeItem = mIsFromResizeItem;
+ mIsFromResizeItem = false;
// Although this is a AIDL method, it will only be triggered in local process through
// either WindowStateResizeItem or WindowlessWindowManager.
final ViewRootImpl viewAncestor = mViewAncestor.get();
- if (viewAncestor != null) {
- viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
+ if (viewAncestor == null) {
+ return;
+ }
+ if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
+ ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#resized",
+ viewAncestor.getInsetsController().getHost().getInputMethodManager(),
+ null /* icProto */);
+ }
+ // If the UI thread is the same as the current thread that is dispatching
+ // WindowStateResizeItem, then it can run directly.
+ if (isFromResizeItem && viewAncestor.mHandler.getLooper()
+ == ActivityThread.currentActivityThread().getLooper()) {
+ viewAncestor.handleResized(frames, reportDraw, mergedConfiguration, insetsState,
forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
+ return;
+ }
+ // The the parameters from WindowStateResizeItem are already copied.
+ final boolean needCopy =
+ !isFromResizeItem && (Binder.getCallingPid() == Process.myPid());
+ if (needCopy) {
+ insetsState = new InsetsState(insetsState, true /* copySource */);
+ frames = new ClientWindowFrames(frames);
+ mergedConfiguration = new MergedConfiguration(mergedConfiguration);
}
+ viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
+ forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
}
@Override
@@ -11910,6 +11935,12 @@ public final class ViewRootImpl implements ViewParent,
if (syncBuffer) {
boolean result = mBlastBufferQueue.syncNextTransaction(transaction -> {
+ Runnable timeoutRunnable = () -> Log.e(mTag,
+ "Failed to submit the sync transaction after 4s. Likely to ANR "
+ + "soon");
+ mHandler.postDelayed(timeoutRunnable, 4L * Build.HW_TIMEOUT_MULTIPLIER);
+ transaction.addTransactionCommittedListener(mSimpleExecutor,
+ () -> mHandler.removeCallbacks(timeoutRunnable));
surfaceSyncGroup.addTransaction(transaction);
surfaceSyncGroup.markSyncReady();
});
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index 44b4353871a2..70c899f1efc7 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -17,10 +17,16 @@ package android.view.contentcapture;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.SparseArray;
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import java.util.ArrayList;
+
/**
* A session that is explicitly created by the app (and hence is a descendant of
* {@link MainContentCaptureSession}).
@@ -40,17 +46,30 @@ final class ChildContentCaptureSession extends ContentCaptureSession {
}
@Override
- MainContentCaptureSession getMainCaptureSession() {
- if (mParent instanceof MainContentCaptureSession) {
- return (MainContentCaptureSession) mParent;
- }
+ ContentCaptureSession getMainCaptureSession() {
return mParent.getMainCaptureSession();
}
@Override
+ void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
+ @NonNull ComponentName component, int flags) {
+ getMainCaptureSession().start(token, shareableActivityToken, component, flags);
+ }
+
+ @Override
+ boolean isDisabled() {
+ return getMainCaptureSession().isDisabled();
+ }
+
+ @Override
+ boolean setDisabled(boolean disabled) {
+ return getMainCaptureSession().setDisabled(disabled);
+ }
+
+ @Override
ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
- getMainCaptureSession().notifyChildSessionStarted(mId, child.mId, clientContext);
+ internalNotifyChildSessionStarted(mId, child.mId, clientContext);
return child;
}
@@ -61,51 +80,80 @@ final class ChildContentCaptureSession extends ContentCaptureSession {
@Override
public void updateContentCaptureContext(@Nullable ContentCaptureContext context) {
- getMainCaptureSession().notifyContextUpdated(mId, context);
+ internalNotifyContextUpdated(mId, context);
}
@Override
void onDestroy() {
- getMainCaptureSession().notifyChildSessionFinished(mParent.mId, mId);
+ internalNotifyChildSessionFinished(mParent.mId, mId);
+ }
+
+ @Override
+ void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId,
+ @NonNull ContentCaptureContext clientContext) {
+ getMainCaptureSession()
+ .internalNotifyChildSessionStarted(parentSessionId, childSessionId, clientContext);
+ }
+
+ @Override
+ void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) {
+ getMainCaptureSession().internalNotifyChildSessionFinished(parentSessionId, childSessionId);
+ }
+
+ @Override
+ void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
+ getMainCaptureSession().internalNotifyContextUpdated(sessionId, context);
}
@Override
- void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
- getMainCaptureSession().notifyViewAppeared(mId, node);
+ void internalNotifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
+ getMainCaptureSession().internalNotifyViewAppeared(sessionId, node);
}
@Override
- void internalNotifyViewDisappeared(@NonNull AutofillId id) {
- getMainCaptureSession().notifyViewDisappeared(mId, id);
+ void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
+ getMainCaptureSession().internalNotifyViewDisappeared(sessionId, id);
}
@Override
- void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
- getMainCaptureSession().notifyViewTextChanged(mId, id, text);
+ void internalNotifyViewTextChanged(
+ int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
+ getMainCaptureSession().internalNotifyViewTextChanged(sessionId, id, text);
}
@Override
- void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets) {
- getMainCaptureSession().notifyViewInsetsChanged(mId, viewInsets);
+ void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
+ getMainCaptureSession().internalNotifyViewInsetsChanged(mId, viewInsets);
}
@Override
- public void internalNotifyViewTreeEvent(boolean started) {
- getMainCaptureSession().notifyViewTreeEvent(mId, started);
+ public void internalNotifyViewTreeEvent(int sessionId, boolean started) {
+ getMainCaptureSession().internalNotifyViewTreeEvent(sessionId, started);
}
@Override
void internalNotifySessionResumed() {
- getMainCaptureSession().notifySessionResumed();
+ getMainCaptureSession().internalNotifySessionResumed();
}
@Override
void internalNotifySessionPaused() {
- getMainCaptureSession().notifySessionPaused();
+ getMainCaptureSession().internalNotifySessionPaused();
}
@Override
boolean isContentCaptureEnabled() {
return getMainCaptureSession().isContentCaptureEnabled();
}
+
+ @Override
+ public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
+ getMainCaptureSession().notifyWindowBoundsChanged(sessionId, bounds);
+ }
+
+ @Override
+ public void notifyContentCaptureEvents(
+ @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
+ getMainCaptureSession().notifyContentCaptureEvents(contentCaptureEvents);
+ }
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index a8297472445f..bcef37f6e0c4 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -499,10 +499,14 @@ public final class ContentCaptureManager {
@Nullable
@GuardedBy("mLock")
- private Handler mHandler;
+ private Handler mUiHandler;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private Handler mContentCaptureHandler;
@GuardedBy("mLock")
- private MainContentCaptureSession mMainSession;
+ private ContentCaptureSession mMainSession;
@Nullable // set on-demand by addDumpable()
private Dumper mDumpable;
@@ -587,11 +591,10 @@ public final class ContentCaptureManager {
*/
@NonNull
@UiThread
- public MainContentCaptureSession getMainContentCaptureSession() {
+ public ContentCaptureSession getMainContentCaptureSession() {
synchronized (mLock) {
if (mMainSession == null) {
- mMainSession = new MainContentCaptureSession(
- mContext, this, prepareContentCaptureHandler(), mService);
+ mMainSession = prepareMainSession();
if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession);
}
return mMainSession;
@@ -600,15 +603,36 @@ public final class ContentCaptureManager {
@NonNull
@GuardedBy("mLock")
+ private ContentCaptureSession prepareMainSession() {
+ if (runOnBackgroundThreadEnabled()) {
+ return new MainContentCaptureSessionV2(
+ mContext,
+ this,
+ prepareUiHandler(),
+ prepareContentCaptureHandler(),
+ mService
+ );
+ } else {
+ return new MainContentCaptureSession(mContext, this, prepareUiHandler(), mService);
+ }
+ }
+
+ @NonNull
+ @GuardedBy("mLock")
private Handler prepareContentCaptureHandler() {
- if (mHandler == null) {
- if (runOnBackgroundThreadEnabled()) {
- mHandler = BackgroundThread.getHandler();
- } else {
- mHandler = Handler.createAsync(Looper.getMainLooper());
- }
+ if (mContentCaptureHandler == null) {
+ mContentCaptureHandler = BackgroundThread.getHandler();
+ }
+ return mContentCaptureHandler;
+ }
+
+ @NonNull
+ @GuardedBy("mLock")
+ private Handler prepareUiHandler() {
+ if (mUiHandler == null) {
+ mUiHandler = Handler.createAsync(Looper.getMainLooper());
}
- return mHandler;
+ return mUiHandler;
}
/** @hide */
@@ -726,7 +750,7 @@ public final class ContentCaptureManager {
public boolean isContentCaptureEnabled() {
if (mOptions.lite) return false;
- final MainContentCaptureSession mainSession;
+ final ContentCaptureSession mainSession;
synchronized (mLock) {
mainSession = mMainSession;
}
@@ -777,7 +801,7 @@ public final class ContentCaptureManager {
Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext);
}
- MainContentCaptureSession mainSession;
+ ContentCaptureSession mainSession;
synchronized (mLock) {
if (enabled) {
mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP;
@@ -803,7 +827,7 @@ public final class ContentCaptureManager {
final boolean flagSecureEnabled =
(params.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
- MainContentCaptureSession mainSession;
+ ContentCaptureSession mainSession;
boolean alreadyDisabledByApp;
synchronized (mLock) {
alreadyDisabledByApp = (mFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0;
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index bb815c0e8317..0ca36ba28e3a 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -27,9 +27,13 @@ import android.annotation.Nullable;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
+import android.content.ComponentName;
import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.IBinder;
import android.util.DebugUtils;
import android.util.Log;
+import android.util.SparseArray;
import android.view.View;
import android.view.ViewStructure;
import android.view.autofill.AutofillId;
@@ -37,6 +41,7 @@ import android.view.contentcapture.ViewNode.ViewStructureImpl;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.IResultReceiver;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
@@ -60,6 +65,18 @@ public abstract class ContentCaptureSession implements AutoCloseable {
private static final SecureRandom ID_GENERATOR = new SecureRandom();
/**
+ * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service.
+ * @hide
+ */
+ public static final String EXTRA_BINDER = "binder";
+
+ /**
+ * Name of the {@link IResultReceiver} extra used to pass the content capture enabled state.
+ * @hide
+ */
+ public static final String EXTRA_ENABLED_STATE = "enabled";
+
+ /**
* Initial state, when there is no session.
*
* @hide
@@ -262,7 +279,19 @@ public abstract class ContentCaptureSession implements AutoCloseable {
/** @hide */
@NonNull
- abstract MainContentCaptureSession getMainCaptureSession();
+ abstract ContentCaptureSession getMainCaptureSession();
+
+ abstract void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
+ @NonNull ComponentName component, int flags);
+
+ abstract boolean isDisabled();
+
+ /**
+ * Sets the disabled state of content capture.
+ *
+ * @return whether disabled state was changed.
+ */
+ abstract boolean setDisabled(boolean disabled);
/**
* Gets the id used to identify this session.
@@ -400,10 +429,11 @@ public abstract class ContentCaptureSession implements AutoCloseable {
throw new IllegalArgumentException("Invalid node class: " + node.getClass());
}
- internalNotifyViewAppeared((ViewStructureImpl) node);
+ internalNotifyViewAppeared(mId, (ViewStructureImpl) node);
}
- abstract void internalNotifyViewAppeared(@NonNull ViewNode.ViewStructureImpl node);
+ abstract void internalNotifyViewAppeared(
+ int sessionId, @NonNull ViewNode.ViewStructureImpl node);
/**
* Notifies the Content Capture Service that a node has been removed from the view structure.
@@ -420,10 +450,10 @@ public abstract class ContentCaptureSession implements AutoCloseable {
Objects.requireNonNull(id);
if (!isContentCaptureEnabled()) return;
- internalNotifyViewDisappeared(id);
+ internalNotifyViewDisappeared(mId, id);
}
- abstract void internalNotifyViewDisappeared(@NonNull AutofillId id);
+ abstract void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id);
/**
* Notifies the Content Capture Service that a list of nodes has appeared in the view structure.
@@ -445,12 +475,12 @@ public abstract class ContentCaptureSession implements AutoCloseable {
}
}
- internalNotifyViewTreeEvent(/* started= */ true);
+ internalNotifyViewTreeEvent(mId, /* started= */ true);
for (int i = 0; i < appearedNodes.size(); i++) {
ViewStructure v = appearedNodes.get(i);
- internalNotifyViewAppeared((ViewStructureImpl) v);
+ internalNotifyViewAppeared(mId, (ViewStructureImpl) v);
}
- internalNotifyViewTreeEvent(/* started= */ false);
+ internalNotifyViewTreeEvent(mId, /* started= */ false);
}
/**
@@ -476,15 +506,15 @@ public abstract class ContentCaptureSession implements AutoCloseable {
if (!isContentCaptureEnabled()) return;
if (CompatChanges.isChangeEnabled(NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS)) {
- internalNotifyViewTreeEvent(/* started= */ true);
+ internalNotifyViewTreeEvent(mId, /* started= */ true);
}
// TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is
// parcelized
for (long id : virtualIds) {
- internalNotifyViewDisappeared(new AutofillId(hostId, id, mId));
+ internalNotifyViewDisappeared(mId, new AutofillId(hostId, id, mId));
}
if (CompatChanges.isChangeEnabled(NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS)) {
- internalNotifyViewTreeEvent(/* started= */ false);
+ internalNotifyViewTreeEvent(mId, /* started= */ false);
}
}
@@ -499,10 +529,10 @@ public abstract class ContentCaptureSession implements AutoCloseable {
if (!isContentCaptureEnabled()) return;
- internalNotifyViewTextChanged(id, text);
+ internalNotifyViewTextChanged(mId, id, text);
}
- abstract void internalNotifyViewTextChanged(@NonNull AutofillId id,
+ abstract void internalNotifyViewTextChanged(int sessionId, @NonNull AutofillId id,
@Nullable CharSequence text);
/**
@@ -513,13 +543,18 @@ public abstract class ContentCaptureSession implements AutoCloseable {
if (!isContentCaptureEnabled()) return;
- internalNotifyViewInsetsChanged(viewInsets);
+ internalNotifyViewInsetsChanged(mId, viewInsets);
}
- abstract void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets);
+ abstract void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets);
+
+ /** @hide */
+ public void notifyViewTreeEvent(boolean started) {
+ internalNotifyViewTreeEvent(mId, started);
+ }
/** @hide */
- public abstract void internalNotifyViewTreeEvent(boolean started);
+ abstract void internalNotifyViewTreeEvent(int sessionId, boolean started);
/**
* Notifies the Content Capture Service that a session has resumed.
@@ -543,6 +578,21 @@ public abstract class ContentCaptureSession implements AutoCloseable {
abstract void internalNotifySessionPaused();
+ abstract void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId,
+ @NonNull ContentCaptureContext clientContext);
+
+ abstract void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId);
+
+ abstract void internalNotifyContextUpdated(
+ int sessionId, @Nullable ContentCaptureContext context);
+
+ /** @hide */
+ public abstract void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds);
+
+ /** @hide */
+ public abstract void notifyContentCaptureEvents(
+ @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents);
+
/**
* Creates a {@link ViewStructure} for a "standard" view.
*
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 19ba316257a3..a90c94eac967 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -31,7 +31,6 @@ import static android.view.contentcapture.ContentCaptureHelper.getSanitizedStrin
import static android.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE;
-import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -70,10 +69,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
-import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+// TODO(b/309411951): Replace V2 as the only main session once the experiment is done.
/**
* Main session associated with a context.
*
@@ -82,6 +81,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*
* @hide
*/
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class MainContentCaptureSession extends ContentCaptureSession {
private static final String TAG = MainContentCaptureSession.class.getSimpleName();
@@ -97,18 +97,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
*/
private static final int MSG_FLUSH = 1;
- /**
- * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service.
- * @hide
- */
- public static final String EXTRA_BINDER = "binder";
-
- /**
- * Name of the {@link IResultReceiver} extra used to pass the content capture enabled state.
- * @hide
- */
- public static final String EXTRA_ENABLED_STATE = "enabled";
-
@NonNull
private final AtomicBoolean mDisabled = new AtomicBoolean(false);
@@ -154,15 +142,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
public ComponentName mComponentName;
/**
- * Thread-safe queue of events held to be processed as a batch.
- *
- * Because it is not guaranteed that the events will be enqueued from a single thread, the
- * implementation must be thread-safe to prevent unexpected behaviour.
- */
- @NonNull
- private final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
-
- /**
* List of events held to be sent to the {@link ContentCaptureService} as a batch.
*
* @hide
@@ -221,14 +200,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
binder = resultData.getBinder(EXTRA_BINDER);
if (binder == null) {
Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
- mainSession.runOnContentCaptureThread(() -> mainSession.resetSession(
+ mainSession.mHandler.post(() -> mainSession.resetSession(
STATE_DISABLED | STATE_INTERNAL_ERROR));
return;
}
} else {
binder = null;
}
- mainSession.runOnContentCaptureThread(() ->
+ mainSession.mHandler.post(() ->
mainSession.onSessionStarted(resultCode, binder));
}
}
@@ -249,39 +228,27 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null;
mSessionStateReceiver = new SessionStateReceiver(this);
-
- mEventProcessQueue = new ConcurrentLinkedQueue<>();
}
@Override
- MainContentCaptureSession getMainCaptureSession() {
+ ContentCaptureSession getMainCaptureSession() {
return this;
}
@Override
ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
- notifyChildSessionStarted(mId, child.mId, clientContext);
+ internalNotifyChildSessionStarted(mId, child.mId, clientContext);
return child;
}
/**
* Starts this session.
*/
+ @Override
void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
@NonNull ComponentName component, int flags) {
- if (runOnBackgroundThreadEnabled()) {
- runOnContentCaptureThread(
- () -> startImpl(token, shareableActivityToken, component, flags));
- } else {
- // Preserve the control arm behaviour.
- startImpl(token, shareableActivityToken, component, flags);
- }
- }
-
- private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
- @NonNull ComponentName component, int flags) {
- checkOnContentCaptureThread();
+ checkOnUiThread();
if (!isContentCaptureEnabled()) return;
if (sVerbose) {
@@ -315,15 +282,17 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e);
}
}
+
@Override
void onDestroy() {
- clearAndRunOnContentCaptureThread(() -> {
+ mHandler.removeMessages(MSG_FLUSH);
+ mHandler.post(() -> {
try {
flush(FLUSH_REASON_SESSION_FINISHED);
} finally {
destroySession();
}
- }, MSG_FLUSH);
+ });
}
/**
@@ -336,7 +305,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public void onSessionStarted(int resultCode, @Nullable IBinder binder) {
- checkOnContentCaptureThread();
+ checkOnUiThread();
if (binder != null) {
mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
mDirectServiceVulture = () -> {
@@ -385,7 +354,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
- checkOnContentCaptureThread();
+ checkOnUiThread();
final int eventType = event.getType();
if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED
@@ -429,14 +398,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) {
- checkOnContentCaptureThread();
+ checkOnUiThread();
if (mContentProtectionEventProcessor != null) {
mContentProtectionEventProcessor.processEvent(event);
}
}
private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
- checkOnContentCaptureThread();
+ checkOnUiThread();
final int eventType = event.getType();
final int maxBufferSize = mManager.mOptions.maxBufferSize;
if (mEvents == null) {
@@ -571,12 +540,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
private boolean hasStarted() {
- checkOnContentCaptureThread();
+ checkOnUiThread();
return mState != UNKNOWN_STATE;
}
private void scheduleFlush(@FlushReason int reason, boolean checkExisting) {
- checkOnContentCaptureThread();
+ checkOnUiThread();
if (sVerbose) {
Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason)
+ ", checkExisting=" + checkExisting);
@@ -617,11 +586,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
+ flushFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
}
// Post using a Runnable directly to trim a few μs from PooledLambda.obtainMessage()
- mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs);
+ mHandler.postDelayed(() ->
+ flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs);
}
private void flushIfNeeded(@FlushReason int reason) {
- checkOnContentCaptureThread();
+ checkOnUiThread();
if (mEvents == null || mEvents.isEmpty()) {
if (sVerbose) Log.v(TAG, "Nothing to flush");
return;
@@ -633,16 +603,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@Override
public void flush(@FlushReason int reason) {
- if (runOnBackgroundThreadEnabled()) {
- runOnContentCaptureThread(() -> flushImpl(reason));
- } else {
- // Preserve the control arm behaviour.
- flushImpl(reason);
- }
- }
-
- private void flushImpl(@FlushReason int reason) {
- checkOnContentCaptureThread();
+ checkOnUiThread();
if (mEvents == null || mEvents.size() == 0) {
if (sVerbose) {
Log.v(TAG, "Don't flush for empty event buffer.");
@@ -703,7 +664,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
@Override
public void updateContentCaptureContext(@Nullable ContentCaptureContext context) {
- notifyContextUpdated(mId, context);
+ internalNotifyContextUpdated(mId, context);
}
/**
@@ -711,7 +672,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
*/
@NonNull
private ParceledListSlice<ContentCaptureEvent> clearEvents() {
- checkOnContentCaptureThread();
+ checkOnUiThread();
// NOTE: we must save a reference to the current mEvents and then set it to to null,
// otherwise clearing it would clear it in the receiving side if the service is also local.
if (mEvents == null) {
@@ -726,7 +687,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
/** hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public void destroySession() {
- checkOnContentCaptureThread();
+ checkOnUiThread();
if (sDebug) {
Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
+ (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
@@ -746,9 +707,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
mDirectServiceInterface = null;
mContentProtectionEventProcessor = null;
- if (runOnBackgroundThreadEnabled()) {
- mEventProcessQueue.clear();
- }
}
// TODO(b/122454205): once we support multiple sessions, we might need to move some of these
@@ -756,7 +714,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
/** @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public void resetSession(int newState) {
- checkOnContentCaptureThread();
+ checkOnUiThread();
if (sVerbose) {
Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
+ getStateAsString(mState) + " to " + getStateAsString(newState));
@@ -781,91 +739,22 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
@Override
- void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
- notifyViewAppeared(mId, node);
- }
-
- @Override
- void internalNotifyViewDisappeared(@NonNull AutofillId id) {
- notifyViewDisappeared(mId, id);
- }
-
- @Override
- void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
- notifyViewTextChanged(mId, id, text);
- }
-
- @Override
- void internalNotifyViewInsetsChanged(@NonNull Insets viewInsets) {
- notifyViewInsetsChanged(mId, viewInsets);
- }
-
- @Override
- public void internalNotifyViewTreeEvent(boolean started) {
- notifyViewTreeEvent(mId, started);
- }
-
- @Override
- public void internalNotifySessionResumed() {
- notifySessionResumed(mId);
- }
-
- @Override
- public void internalNotifySessionPaused() {
- notifySessionPaused(mId);
- }
-
- @Override
- boolean isContentCaptureEnabled() {
- return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
- }
-
- // Called by ContentCaptureManager.isContentCaptureEnabled
- boolean isDisabled() {
- return mDisabled.get();
- }
-
- /**
- * Sets the disabled state of content capture.
- *
- * @return whether disabled state was changed.
- */
- boolean setDisabled(boolean disabled) {
- return mDisabled.compareAndSet(!disabled, disabled);
- }
-
- // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is
- // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
- // change should also get get rid of the "internalNotifyXXXX" methods above
- void notifyChildSessionStarted(int parentSessionId, int childSessionId,
- @NonNull ContentCaptureContext clientContext) {
- final ContentCaptureEvent event =
- new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
- .setParentSessionId(parentSessionId)
- .setClientContext(clientContext);
- enqueueEvent(event, FORCE_FLUSH);
- }
-
- void notifyChildSessionFinished(int parentSessionId, int childSessionId) {
- final ContentCaptureEvent event =
- new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
- .setParentSessionId(parentSessionId);
- enqueueEvent(event, FORCE_FLUSH);
- }
-
- void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
+ void internalNotifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
.setViewNode(node.mNode);
- enqueueEvent(event);
+ mHandler.post(() -> sendEvent(event));
}
- void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
+ @Override
+ void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED)
.setAutofillId(id);
- enqueueEvent(event);
+ mHandler.post(() -> sendEvent(event));
}
- void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
+ @Override
+ void internalNotifyViewTextChanged(
+ int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
// Since the same CharSequence instance may be reused in the TextView, we need to make
// a copy of its content so that its value will not be changed by subsequent updates
// in the TextView.
@@ -891,113 +780,108 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
.setAutofillId(id).setText(eventText)
.setComposingIndex(composingStart, composingEnd)
.setSelectionIndex(startIndex, endIndex);
- enqueueEvent(event);
+ mHandler.post(() -> sendEvent(event));
}
- void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
+ @Override
+ void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
final ContentCaptureEvent event =
new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
.setInsets(viewInsets);
- enqueueEvent(event);
+ mHandler.post(() -> sendEvent(event));
}
- void notifyViewTreeEvent(int sessionId, boolean started) {
+ @Override
+ public void internalNotifyViewTreeEvent(int sessionId, boolean started) {
final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled();
final boolean forceFlush = disableFlush ? !started : FORCE_FLUSH;
final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type);
- enqueueEvent(event, forceFlush);
+ mHandler.post(() -> sendEvent(event, FORCE_FLUSH));
}
- void notifySessionResumed(int sessionId) {
- final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED);
- enqueueEvent(event, FORCE_FLUSH);
+ @Override
+ public void internalNotifySessionResumed() {
+ final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_RESUMED);
+ mHandler.post(() -> sendEvent(event, FORCE_FLUSH));
}
- void notifySessionPaused(int sessionId) {
- final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED);
- enqueueEvent(event, FORCE_FLUSH);
+ @Override
+ public void internalNotifySessionPaused() {
+ final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_PAUSED);
+ mHandler.post(() -> sendEvent(event, FORCE_FLUSH));
}
- void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
- final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
- .setClientContext(context);
- enqueueEvent(event, FORCE_FLUSH);
+ @Override
+ boolean isContentCaptureEnabled() {
+ return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
}
- /** public because is also used by ViewRootImpl */
- public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
+ @Override
+ boolean isDisabled() {
+ return mDisabled.get();
+ }
+
+ @Override
+ boolean setDisabled(boolean disabled) {
+ return mDisabled.compareAndSet(!disabled, disabled);
+ }
+
+ @Override
+ void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId,
+ @NonNull ContentCaptureContext clientContext) {
final ContentCaptureEvent event =
- new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED)
- .setBounds(bounds);
- enqueueEvent(event);
+ new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
+ .setParentSessionId(parentSessionId)
+ .setClientContext(clientContext);
+ mHandler.post(() -> sendEvent(event, FORCE_FLUSH));
}
- private List<ContentCaptureEvent> clearBufferEvents() {
- final ArrayList<ContentCaptureEvent> bufferEvents = new ArrayList<>();
- ContentCaptureEvent event;
- while ((event = mEventProcessQueue.poll()) != null) {
- bufferEvents.add(event);
- }
- return bufferEvents;
+ @Override
+ void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) {
+ final ContentCaptureEvent event =
+ new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+ .setParentSessionId(parentSessionId);
+ mHandler.post(() -> sendEvent(event, FORCE_FLUSH));
}
- private void enqueueEvent(@NonNull final ContentCaptureEvent event) {
- enqueueEvent(event, /* forceFlush */ false);
+ @Override
+ void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
+ final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
+ .setClientContext(context);
+ mHandler.post(() -> sendEvent(event, FORCE_FLUSH));
}
- /**
- * Enqueue the event into {@code mEventProcessBuffer} if it is not an urgent request. Otherwise,
- * clear the buffer events then starting sending out current event.
- */
- private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) {
- if (runOnBackgroundThreadEnabled()) {
- if (forceFlush) {
- // The buffer events are cleared in the same thread first to prevent new events
- // being added during the time of context switch. This would disrupt the sequence
- // of events.
- final List<ContentCaptureEvent> batchEvents = clearBufferEvents();
- runOnContentCaptureThread(() -> {
- for (int i = 0; i < batchEvents.size(); i++) {
- sendEvent(batchEvents.get(i));
- }
- sendEvent(event, /* forceFlush= */ true);
- });
- } else {
- mEventProcessQueue.offer(event);
- }
- } else {
- mHandler.post(() -> sendEvent(event, forceFlush));
- }
+ @Override
+ public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
+ final ContentCaptureEvent event =
+ new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED)
+ .setBounds(bounds);
+ mHandler.post(() -> sendEvent(event));
}
- /** public because is also used by ViewRootImpl */
+ @Override
public void notifyContentCaptureEvents(
@NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
- if (runOnBackgroundThreadEnabled()) {
- runOnContentCaptureThread(() -> notifyContentCaptureEventsImpl(contentCaptureEvents));
- } else {
- // Preserve the control arm behaviour.
- notifyContentCaptureEventsImpl(contentCaptureEvents);
- }
+ notifyContentCaptureEventsImpl(contentCaptureEvents);
}
private void notifyContentCaptureEventsImpl(
@NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
- checkOnContentCaptureThread();
+ checkOnUiThread();
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents");
}
for (int i = 0; i < contentCaptureEvents.size(); i++) {
int sessionId = contentCaptureEvents.keyAt(i);
- notifyViewTreeEvent(sessionId, /* started= */ true);
+ internalNotifyViewTreeEvent(sessionId, /* started= */ true);
ArrayList<Object> events = contentCaptureEvents.valueAt(i);
for_each_event: for (int j = 0; j < events.size(); j++) {
Object event = events.get(j);
if (event instanceof AutofillId) {
- notifyViewDisappeared(sessionId, (AutofillId) event);
+ internalNotifyViewDisappeared(sessionId, (AutofillId) event);
} else if (event instanceof View) {
View view = (View) event;
ContentCaptureSession session = view.getContentCaptureSession();
@@ -1015,12 +899,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
view.onProvideContentCaptureStructure(structure, /* flags= */ 0);
session.notifyViewAppeared(structure);
} else if (event instanceof Insets) {
- notifyViewInsetsChanged(sessionId, (Insets) event);
+ internalNotifyViewInsetsChanged(sessionId, (Insets) event);
} else {
Log.w(TAG, "invalid content capture event: " + event);
}
}
- notifyViewTreeEvent(sessionId, /* started= */ false);
+ internalNotifyViewTreeEvent(sessionId, /* started= */ false);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
@@ -1131,9 +1015,9 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
* Therefore, accessing internal properties in {@link MainContentCaptureSession} should
* always delegate to the assigned thread from {@code mHandler} for synchronization.</p>
*/
- private void checkOnContentCaptureThread() {
- final boolean onContentCaptureThread = mHandler.getLooper().isCurrentThread();
- if (!onContentCaptureThread) {
+ private void checkOnUiThread() {
+ final boolean onUiThread = mHandler.getLooper().isCurrentThread();
+ if (!onUiThread) {
mWrongThreadCount.incrementAndGet();
Log.e(TAG, "MainContentCaptureSession running on " + Thread.currentThread());
}
@@ -1144,38 +1028,4 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
Counter.logIncrement(
CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID, mWrongThreadCount.getAndSet(0));
}
-
- /**
- * Ensures that {@code r} will be running on the assigned thread.
- *
- * <p>This is to prevent unnecessary delegation to Handler that results in fragmented runnable.
- * </p>
- */
- private void runOnContentCaptureThread(@NonNull Runnable r) {
- if (runOnBackgroundThreadEnabled()) {
- if (!mHandler.getLooper().isCurrentThread()) {
- mHandler.post(r);
- } else {
- r.run();
- }
- } else {
- // Preserve the control arm behaviour to always post to the handler.
- mHandler.post(r);
- }
- }
-
- private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) {
- if (runOnBackgroundThreadEnabled()) {
- if (!mHandler.getLooper().isCurrentThread()) {
- mHandler.removeMessages(what);
- mHandler.post(r);
- } else {
- r.run();
- }
- } else {
- // Preserve the control arm behaviour to always post to the handler.
- mHandler.removeMessages(what);
- mHandler.post(r);
- }
- }
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
new file mode 100644
index 000000000000..bf1d31c8496d
--- /dev/null
+++ b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
@@ -0,0 +1,1184 @@
+/*
+ * 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 android.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_INSETS_CHANGED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_WINDOW_BOUNDS_CHANGED;
+import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
+import static android.view.contentcapture.ContentCaptureHelper.sDebug;
+import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
+import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.pm.ParceledListSlice;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.service.contentcapture.ContentCaptureService;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.contentprotection.ContentProtectionEventProcessor;
+import android.view.inputmethod.BaseInputConnection;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.IResultReceiver;
+import com.android.modules.expresslog.Counter;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Main session associated with a context.
+ *
+ * <p>This is forked from {@link MainContentCaptureSession} to hold the logic of running operations
+ * in the background thread.</p>
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class MainContentCaptureSessionV2 extends ContentCaptureSession {
+
+ private static final String TAG = MainContentCaptureSession.class.getSimpleName();
+
+ private static final String CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID =
+ "content_capture.value_content_capture_wrong_thread_count";
+
+ // For readability purposes...
+ private static final boolean FORCE_FLUSH = true;
+
+ /**
+ * Handler message used to flush the buffer.
+ */
+ private static final int MSG_FLUSH = 1;
+
+ @NonNull
+ private final AtomicBoolean mDisabled = new AtomicBoolean(false);
+
+ @NonNull
+ private final ContentCaptureManager.StrippedContext mContext;
+
+ @NonNull
+ private final ContentCaptureManager mManager;
+
+ @NonNull
+ private final Handler mUiHandler;
+
+ @NonNull
+ private final Handler mContentCaptureHandler;
+
+ /**
+ * Interface to the system_server binder object - it's only used to start the session (and
+ * notify when the session is finished).
+ */
+ @NonNull
+ private final IContentCaptureManager mSystemServerInterface;
+
+ /**
+ * Direct interface to the service binder object - it's used to send the events, including the
+ * last ones (when the session is finished)
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public IContentCaptureDirectManager mDirectServiceInterface;
+
+ @Nullable
+ private DeathRecipient mDirectServiceVulture;
+
+ private int mState = UNKNOWN_STATE;
+
+ @Nullable
+ private IBinder mApplicationToken;
+ @Nullable
+ private IBinder mShareableActivityToken;
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public ComponentName mComponentName;
+
+ /**
+ * Thread-safe queue of events held to be processed as a batch.
+ *
+ * Because it is not guaranteed that the events will be enqueued from a single thread, the
+ * implementation must be thread-safe to prevent unexpected behaviour.
+ */
+ @NonNull
+ private final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
+
+ /**
+ * List of events held to be sent to the {@link ContentCaptureService} as a batch.
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public ArrayList<ContentCaptureEvent> mEvents;
+
+ // Used just for debugging purposes (on dump)
+ private long mNextFlush;
+
+ /**
+ * Whether the next buffer flush is queued by a text changed event.
+ */
+ private boolean mNextFlushForTextChanged = false;
+
+ @Nullable
+ private final LocalLog mFlushHistory;
+
+ private final AtomicInteger mWrongThreadCount = new AtomicInteger(0);
+
+ /**
+ * Binder object used to update the session state.
+ */
+ @NonNull
+ private final SessionStateReceiver mSessionStateReceiver;
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
+ private static class SessionStateReceiver extends IResultReceiver.Stub {
+ private final WeakReference<MainContentCaptureSessionV2> mMainSession;
+
+ SessionStateReceiver(MainContentCaptureSessionV2 session) {
+ mMainSession = new WeakReference<>(session);
+ }
+
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ final MainContentCaptureSessionV2 mainSession = mMainSession.get();
+ if (mainSession == null) {
+ Log.w(TAG, "received result after mina session released");
+ return;
+ }
+ final IBinder binder;
+ if (resultData != null) {
+ // Change in content capture enabled.
+ final boolean hasEnabled = resultData.getBoolean(EXTRA_ENABLED_STATE);
+ if (hasEnabled) {
+ final boolean disabled = (resultCode == RESULT_CODE_FALSE);
+ mainSession.mDisabled.set(disabled);
+ return;
+ }
+ binder = resultData.getBinder(EXTRA_BINDER);
+ if (binder == null) {
+ Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
+ mainSession.runOnContentCaptureThread(() -> mainSession.resetSession(
+ STATE_DISABLED | STATE_INTERNAL_ERROR));
+ return;
+ }
+ } else {
+ binder = null;
+ }
+ mainSession.runOnContentCaptureThread(() ->
+ mainSession.onSessionStarted(resultCode, binder));
+ }
+ }
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public MainContentCaptureSessionV2(
+ @NonNull ContentCaptureManager.StrippedContext context,
+ @NonNull ContentCaptureManager manager,
+ @NonNull Handler uiHandler,
+ @NonNull Handler contentCaptureHandler,
+ @NonNull IContentCaptureManager systemServerInterface) {
+ mContext = context;
+ mManager = manager;
+ mUiHandler = uiHandler;
+ mContentCaptureHandler = contentCaptureHandler;
+ mSystemServerInterface = systemServerInterface;
+
+ final int logHistorySize = mManager.mOptions.logHistorySize;
+ mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null;
+
+ mSessionStateReceiver = new SessionStateReceiver(this);
+
+ mEventProcessQueue = new ConcurrentLinkedQueue<>();
+ }
+
+ @Override
+ ContentCaptureSession getMainCaptureSession() {
+ return this;
+ }
+
+ @Override
+ ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
+ final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
+ internalNotifyChildSessionStarted(mId, child.mId, clientContext);
+ return child;
+ }
+
+ /**
+ * Starts this session.
+ */
+ @Override
+ void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
+ @NonNull ComponentName component, int flags) {
+ runOnContentCaptureThread(
+ () -> startImpl(token, shareableActivityToken, component, flags));
+ }
+
+ private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
+ @NonNull ComponentName component, int flags) {
+ checkOnContentCaptureThread();
+ if (!isContentCaptureEnabled()) return;
+
+ if (sVerbose) {
+ Log.v(TAG, "start(): token=" + token + ", comp="
+ + ComponentName.flattenToShortString(component));
+ }
+
+ if (hasStarted()) {
+ // TODO(b/122959591): make sure this is expected (and when), or use Log.w
+ if (sDebug) {
+ Log.d(TAG, "ignoring handleStartSession(" + token + "/"
+ + ComponentName.flattenToShortString(component) + " while on state "
+ + getStateAsString(mState));
+ }
+ return;
+ }
+ mState = STATE_WAITING_FOR_SERVER;
+ mApplicationToken = token;
+ mShareableActivityToken = shareableActivityToken;
+ mComponentName = component;
+
+ if (sVerbose) {
+ Log.v(TAG, "handleStartSession(): token=" + token + ", act="
+ + getDebugState() + ", id=" + mId);
+ }
+
+ try {
+ mSystemServerInterface.startSession(mApplicationToken, mShareableActivityToken,
+ component, mId, flags, mSessionStateReceiver);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e);
+ }
+ }
+ @Override
+ void onDestroy() {
+ clearAndRunOnContentCaptureThread(() -> {
+ try {
+ flush(FLUSH_REASON_SESSION_FINISHED);
+ } finally {
+ destroySession();
+ }
+ }, MSG_FLUSH);
+ }
+
+ /**
+ * Callback from {@code system_server} after call to {@link
+ * IContentCaptureManager#startSession(IBinder, ComponentName, String, int, IResultReceiver)}.
+ *
+ * @param resultCode session state
+ * @param binder handle to {@code IContentCaptureDirectManager}
+ * @hide
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void onSessionStarted(int resultCode, @Nullable IBinder binder) {
+ checkOnContentCaptureThread();
+ if (binder != null) {
+ mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
+ mDirectServiceVulture = () -> {
+ Log.w(TAG, "Keeping session " + mId + " when service died");
+ mState = STATE_SERVICE_DIED;
+ mDisabled.set(true);
+ };
+ try {
+ binder.linkToDeath(mDirectServiceVulture, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to link to death on " + binder + ": " + e);
+ }
+ }
+
+ if (isContentProtectionEnabled()) {
+ mContentProtectionEventProcessor =
+ new ContentProtectionEventProcessor(
+ mManager.getContentProtectionEventBuffer(),
+ mContentCaptureHandler,
+ mSystemServerInterface,
+ mComponentName.getPackageName(),
+ mManager.mOptions.contentProtectionOptions);
+ } else {
+ mContentProtectionEventProcessor = null;
+ }
+
+ if ((resultCode & STATE_DISABLED) != 0) {
+ resetSession(resultCode);
+ } else {
+ mState = resultCode;
+ mDisabled.set(false);
+ // Flush any pending data immediately as buffering forced until now.
+ flushIfNeeded(FLUSH_REASON_SESSION_CONNECTED);
+ }
+ if (sVerbose) {
+ Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode
+ + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
+ + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size()));
+ }
+ }
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void sendEvent(@NonNull ContentCaptureEvent event) {
+ sendEvent(event, /* forceFlush= */ false);
+ }
+
+ private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+ checkOnContentCaptureThread();
+ final int eventType = event.getType();
+ if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
+ if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED
+ && eventType != ContentCaptureEvent.TYPE_CONTEXT_UPDATED) {
+ // TODO(b/120494182): comment when this could happen (dialogs?)
+ if (sVerbose) {
+ Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
+ + ContentCaptureEvent.getTypeAsString(eventType)
+ + "): dropping because session not started yet");
+ }
+ return;
+ }
+ if (mDisabled.get()) {
+ // This happens when the event was queued in the handler before the sesison was ready,
+ // then handleSessionStarted() returned and set it as disabled - we need to drop it,
+ // otherwise it will keep triggering handleScheduleFlush()
+ if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
+ return;
+ }
+
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ if (eventType == TYPE_VIEW_TREE_APPEARING) {
+ Trace.asyncTraceBegin(
+ Trace.TRACE_TAG_VIEW, /* methodName= */ "sendEventAsync", /* cookie= */ 0);
+ }
+ }
+
+ if (isContentProtectionReceiverEnabled()) {
+ sendContentProtectionEvent(event);
+ }
+ if (isContentCaptureReceiverEnabled()) {
+ sendContentCaptureEvent(event, forceFlush);
+ }
+
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ if (eventType == TYPE_VIEW_TREE_APPEARED) {
+ Trace.asyncTraceEnd(
+ Trace.TRACE_TAG_VIEW, /* methodName= */ "sendEventAsync", /* cookie= */ 0);
+ }
+ }
+ }
+
+ private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) {
+ checkOnContentCaptureThread();
+ if (mContentProtectionEventProcessor != null) {
+ mContentProtectionEventProcessor.processEvent(event);
+ }
+ }
+
+ private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+ checkOnContentCaptureThread();
+ final int eventType = event.getType();
+ final int maxBufferSize = mManager.mOptions.maxBufferSize;
+ if (mEvents == null) {
+ if (sVerbose) {
+ Log.v(TAG, "handleSendEvent(): creating buffer for " + maxBufferSize + " events");
+ }
+ mEvents = new ArrayList<>(maxBufferSize);
+ }
+
+ // Some type of events can be merged together
+ boolean addEvent = true;
+
+ if (eventType == TYPE_VIEW_TEXT_CHANGED) {
+ // We determine whether to add or merge the current event by following criteria:
+ // 1. Don't have composing span: always add.
+ // 2. Have composing span:
+ // 2.1 either last or current text is empty: add.
+ // 2.2 last event doesn't have composing span: add.
+ // Otherwise, merge.
+ final CharSequence text = event.getText();
+ final boolean hasComposingSpan = event.hasComposingSpan();
+ if (hasComposingSpan) {
+ ContentCaptureEvent lastEvent = null;
+ for (int index = mEvents.size() - 1; index >= 0; index--) {
+ final ContentCaptureEvent tmpEvent = mEvents.get(index);
+ if (event.getId().equals(tmpEvent.getId())) {
+ lastEvent = tmpEvent;
+ break;
+ }
+ }
+ if (lastEvent != null && lastEvent.hasComposingSpan()) {
+ final CharSequence lastText = lastEvent.getText();
+ final boolean bothNonEmpty = !TextUtils.isEmpty(lastText)
+ && !TextUtils.isEmpty(text);
+ boolean equalContent =
+ TextUtils.equals(lastText, text)
+ && lastEvent.hasSameComposingSpan(event)
+ && lastEvent.hasSameSelectionSpan(event);
+ if (equalContent) {
+ addEvent = false;
+ } else if (bothNonEmpty) {
+ lastEvent.mergeEvent(event);
+ addEvent = false;
+ }
+ if (!addEvent && sVerbose) {
+ Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
+ + getSanitizedString(text));
+ }
+ }
+ }
+ }
+
+ if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) {
+ final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
+ if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED
+ && event.getSessionId() == lastEvent.getSessionId()) {
+ if (sVerbose) {
+ Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session "
+ + lastEvent.getSessionId());
+ }
+ lastEvent.mergeEvent(event);
+ addEvent = false;
+ }
+ }
+
+ if (addEvent) {
+ mEvents.add(event);
+ }
+
+ // TODO: we need to change when the flush happens so that we don't flush while the
+ // composing span hasn't changed. But we might need to keep flushing the events for the
+ // non-editable views and views that don't have the composing state; otherwise some other
+ // Content Capture features may be delayed.
+
+ final int numberEvents = mEvents.size();
+
+ final boolean bufferEvent = numberEvents < maxBufferSize;
+
+ if (bufferEvent && !forceFlush) {
+ final int flushReason;
+ if (eventType == TYPE_VIEW_TEXT_CHANGED) {
+ mNextFlushForTextChanged = true;
+ flushReason = FLUSH_REASON_TEXT_CHANGE_TIMEOUT;
+ } else {
+ if (mNextFlushForTextChanged) {
+ if (sVerbose) {
+ Log.i(TAG, "Not scheduling flush because next flush is for text changed");
+ }
+ return;
+ }
+
+ flushReason = FLUSH_REASON_IDLE_TIMEOUT;
+ }
+ scheduleFlush(flushReason, /* checkExisting= */ true);
+ return;
+ }
+
+ if (mState != STATE_ACTIVE && numberEvents >= maxBufferSize) {
+ // Callback from startSession hasn't been called yet - typically happens on system
+ // apps that are started before the system service
+ // TODO(b/122959591): try to ignore session while system is not ready / boot
+ // not complete instead. Similarly, the manager service should return right away
+ // when the user does not have a service set
+ if (sDebug) {
+ Log.d(TAG, "Closing session for " + getDebugState()
+ + " after " + numberEvents + " delayed events");
+ }
+ resetSession(STATE_DISABLED | STATE_NO_RESPONSE);
+ // TODO(b/111276913): denylist activity / use special flag to indicate that
+ // when it's launched again
+ return;
+ }
+ final int flushReason;
+ switch (eventType) {
+ case ContentCaptureEvent.TYPE_SESSION_STARTED:
+ flushReason = FLUSH_REASON_SESSION_STARTED;
+ break;
+ case ContentCaptureEvent.TYPE_SESSION_FINISHED:
+ flushReason = FLUSH_REASON_SESSION_FINISHED;
+ break;
+ case ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING:
+ flushReason = FLUSH_REASON_VIEW_TREE_APPEARING;
+ break;
+ case ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED:
+ flushReason = FLUSH_REASON_VIEW_TREE_APPEARED;
+ break;
+ default:
+ flushReason = forceFlush ? FLUSH_REASON_FORCE_FLUSH : FLUSH_REASON_FULL;
+ }
+
+ flush(flushReason);
+ }
+
+ private boolean hasStarted() {
+ checkOnContentCaptureThread();
+ return mState != UNKNOWN_STATE;
+ }
+
+ private void scheduleFlush(@FlushReason int reason, boolean checkExisting) {
+ checkOnContentCaptureThread();
+ if (sVerbose) {
+ Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason)
+ + ", checkExisting=" + checkExisting);
+ }
+ if (!hasStarted()) {
+ if (sVerbose) Log.v(TAG, "handleScheduleFlush(): session not started yet");
+ return;
+ }
+
+ if (mDisabled.get()) {
+ // Should not be called on this state, as handleSendEvent checks.
+ // But we rather add one if check and log than re-schedule and keep the session alive...
+ Log.e(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): should not be called "
+ + "when disabled. events=" + (mEvents == null ? null : mEvents.size()));
+ return;
+ }
+ if (checkExisting && mContentCaptureHandler.hasMessages(MSG_FLUSH)) {
+ // "Renew" the flush message by removing the previous one
+ mContentCaptureHandler.removeMessages(MSG_FLUSH);
+ }
+
+ final int flushFrequencyMs;
+ if (reason == FLUSH_REASON_TEXT_CHANGE_TIMEOUT) {
+ flushFrequencyMs = mManager.mOptions.textChangeFlushingFrequencyMs;
+ } else {
+ if (reason != FLUSH_REASON_IDLE_TIMEOUT) {
+ if (sDebug) {
+ Log.d(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): not a timeout "
+ + "reason because mDirectServiceInterface is not ready yet");
+ }
+ }
+ flushFrequencyMs = mManager.mOptions.idleFlushingFrequencyMs;
+ }
+
+ mNextFlush = System.currentTimeMillis() + flushFrequencyMs;
+ if (sVerbose) {
+ Log.v(TAG, "handleScheduleFlush(): scheduled to flush in "
+ + flushFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
+ }
+ // Post using a Runnable directly to trim a few μs from PooledLambda.obtainMessage()
+ mContentCaptureHandler.postDelayed(() ->
+ flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs);
+ }
+
+ private void flushIfNeeded(@FlushReason int reason) {
+ checkOnContentCaptureThread();
+ if (mEvents == null || mEvents.isEmpty()) {
+ if (sVerbose) Log.v(TAG, "Nothing to flush");
+ return;
+ }
+ flush(reason);
+ }
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @Override
+ public void flush(@FlushReason int reason) {
+ runOnContentCaptureThread(() -> flushImpl(reason));
+ }
+
+ private void flushImpl(@FlushReason int reason) {
+ checkOnContentCaptureThread();
+ if (mEvents == null || mEvents.size() == 0) {
+ if (sVerbose) {
+ Log.v(TAG, "Don't flush for empty event buffer.");
+ }
+ return;
+ }
+
+ if (mDisabled.get()) {
+ Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when "
+ + "disabled");
+ return;
+ }
+
+ if (!isContentCaptureReceiverEnabled()) {
+ return;
+ }
+
+ if (mDirectServiceInterface == null) {
+ if (sVerbose) {
+ Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
+ + "client not ready: " + mEvents);
+ }
+ if (!mContentCaptureHandler.hasMessages(MSG_FLUSH)) {
+ scheduleFlush(reason, /* checkExisting= */ false);
+ }
+ return;
+ }
+
+ mNextFlushForTextChanged = false;
+
+ final int numberEvents = mEvents.size();
+ final String reasonString = getFlushReasonAsString(reason);
+
+ if (sVerbose) {
+ ContentCaptureEvent event = mEvents.get(numberEvents - 1);
+ String forceString = (reason == FLUSH_REASON_FORCE_FLUSH) ? ". The force flush event "
+ + ContentCaptureEvent.getTypeAsString(event.getType()) : "";
+ Log.v(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason)
+ + forceString);
+ }
+ if (mFlushHistory != null) {
+ // Logs reason, size, max size, idle timeout
+ final String logRecord = "r=" + reasonString + " s=" + numberEvents
+ + " m=" + mManager.mOptions.maxBufferSize
+ + " i=" + mManager.mOptions.idleFlushingFrequencyMs;
+ mFlushHistory.log(logRecord);
+ }
+ try {
+ mContentCaptureHandler.removeMessages(MSG_FLUSH);
+
+ final ParceledListSlice<ContentCaptureEvent> events = clearEvents();
+ mDirectServiceInterface.sendEvents(events, reason, mManager.mOptions);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState()
+ + ": " + e);
+ }
+ }
+
+ @Override
+ public void updateContentCaptureContext(@Nullable ContentCaptureContext context) {
+ internalNotifyContextUpdated(mId, context);
+ }
+
+ /**
+ * Resets the buffer and return a {@link ParceledListSlice} with the previous events.
+ */
+ @NonNull
+ private ParceledListSlice<ContentCaptureEvent> clearEvents() {
+ checkOnContentCaptureThread();
+ // NOTE: we must save a reference to the current mEvents and then set it to to null,
+ // otherwise clearing it would clear it in the receiving side if the service is also local.
+ if (mEvents == null) {
+ return new ParceledListSlice<>(Collections.EMPTY_LIST);
+ }
+
+ final List<ContentCaptureEvent> events = new ArrayList<>(mEvents);
+ mEvents.clear();
+ return new ParceledListSlice<>(events);
+ }
+
+ /** hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void destroySession() {
+ checkOnContentCaptureThread();
+ if (sDebug) {
+ Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
+ + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
+ + getDebugState());
+ }
+
+ reportWrongThreadMetric();
+ try {
+ mSystemServerInterface.finishSession(mId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error destroying system-service session " + mId + " for "
+ + getDebugState() + ": " + e);
+ }
+
+ if (mDirectServiceInterface != null) {
+ mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
+ }
+ mDirectServiceInterface = null;
+ mContentProtectionEventProcessor = null;
+ mEventProcessQueue.clear();
+ }
+
+ // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
+ // clearings out.
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void resetSession(int newState) {
+ checkOnContentCaptureThread();
+ if (sVerbose) {
+ Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
+ + getStateAsString(mState) + " to " + getStateAsString(newState));
+ }
+ mState = newState;
+ mDisabled.set((newState & STATE_DISABLED) != 0);
+ // TODO(b/122454205): must reset children (which currently is owned by superclass)
+ mApplicationToken = null;
+ mShareableActivityToken = null;
+ mComponentName = null;
+ mEvents = null;
+ if (mDirectServiceInterface != null) {
+ try {
+ mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "IContentCaptureDirectManager does not exist");
+ }
+ }
+ mDirectServiceInterface = null;
+ mContentProtectionEventProcessor = null;
+ mContentCaptureHandler.removeMessages(MSG_FLUSH);
+ }
+
+ @Override
+ void internalNotifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
+ final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
+ .setViewNode(node.mNode);
+ enqueueEvent(event);
+ }
+
+ @Override
+ void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
+ final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED)
+ .setAutofillId(id);
+ enqueueEvent(event);
+ }
+
+ @Override
+ void internalNotifyViewTextChanged(
+ int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
+ // Since the same CharSequence instance may be reused in the TextView, we need to make
+ // a copy of its content so that its value will not be changed by subsequent updates
+ // in the TextView.
+ CharSequence trimmed = TextUtils.trimToParcelableSize(text);
+ final CharSequence eventText = trimmed != null && trimmed == text
+ ? trimmed.toString()
+ : trimmed;
+
+ final int composingStart;
+ final int composingEnd;
+ if (text instanceof Spannable) {
+ composingStart = BaseInputConnection.getComposingSpanStart((Spannable) text);
+ composingEnd = BaseInputConnection.getComposingSpanEnd((Spannable) text);
+ } else {
+ composingStart = ContentCaptureEvent.MAX_INVALID_VALUE;
+ composingEnd = ContentCaptureEvent.MAX_INVALID_VALUE;
+ }
+
+ final int startIndex = Selection.getSelectionStart(text);
+ final int endIndex = Selection.getSelectionEnd(text);
+
+ final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED)
+ .setAutofillId(id).setText(eventText)
+ .setComposingIndex(composingStart, composingEnd)
+ .setSelectionIndex(startIndex, endIndex);
+ enqueueEvent(event);
+ }
+
+ @Override
+ void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
+ final ContentCaptureEvent event =
+ new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
+ .setInsets(viewInsets);
+ enqueueEvent(event);
+ }
+
+ @Override
+ public void internalNotifyViewTreeEvent(int sessionId, boolean started) {
+ final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
+ final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled();
+ final boolean forceFlush = disableFlush ? !started : FORCE_FLUSH;
+
+ final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type);
+ enqueueEvent(event, forceFlush);
+ }
+
+ @Override
+ public void internalNotifySessionResumed() {
+ final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_RESUMED);
+ enqueueEvent(event, FORCE_FLUSH);
+ }
+
+ @Override
+ public void internalNotifySessionPaused() {
+ final ContentCaptureEvent event = new ContentCaptureEvent(mId, TYPE_SESSION_PAUSED);
+ enqueueEvent(event, FORCE_FLUSH);
+ }
+
+ @Override
+ boolean isContentCaptureEnabled() {
+ return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
+ }
+
+ // Called by ContentCaptureManager.isContentCaptureEnabled
+ boolean isDisabled() {
+ return mDisabled.get();
+ }
+
+ /**
+ * Sets the disabled state of content capture.
+ *
+ * @return whether disabled state was changed.
+ */
+ boolean setDisabled(boolean disabled) {
+ return mDisabled.compareAndSet(!disabled, disabled);
+ }
+
+ @Override
+ void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId,
+ @NonNull ContentCaptureContext clientContext) {
+ final ContentCaptureEvent event =
+ new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
+ .setParentSessionId(parentSessionId)
+ .setClientContext(clientContext);
+ enqueueEvent(event, FORCE_FLUSH);
+ }
+
+ @Override
+ void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) {
+ final ContentCaptureEvent event =
+ new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+ .setParentSessionId(parentSessionId);
+ enqueueEvent(event, FORCE_FLUSH);
+ }
+
+ @Override
+ void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
+ final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
+ .setClientContext(context);
+ enqueueEvent(event, FORCE_FLUSH);
+ }
+
+ @Override
+ public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
+ final ContentCaptureEvent event =
+ new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED)
+ .setBounds(bounds);
+ enqueueEvent(event);
+ }
+
+ private List<ContentCaptureEvent> clearBufferEvents() {
+ final ArrayList<ContentCaptureEvent> bufferEvents = new ArrayList<>();
+ ContentCaptureEvent event;
+ while ((event = mEventProcessQueue.poll()) != null) {
+ bufferEvents.add(event);
+ }
+ return bufferEvents;
+ }
+
+ private void enqueueEvent(@NonNull final ContentCaptureEvent event) {
+ enqueueEvent(event, /* forceFlush */ false);
+ }
+
+ /**
+ * Enqueue the event into {@code mEventProcessBuffer} if it is not an urgent request. Otherwise,
+ * clear the buffer events then starting sending out current event.
+ */
+ private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) {
+ if (forceFlush) {
+ // The buffer events are cleared in the same thread first to prevent new events
+ // being added during the time of context switch. This would disrupt the sequence
+ // of events.
+ final List<ContentCaptureEvent> batchEvents = clearBufferEvents();
+ runOnContentCaptureThread(() -> {
+ for (int i = 0; i < batchEvents.size(); i++) {
+ sendEvent(batchEvents.get(i));
+ }
+ sendEvent(event, /* forceFlush= */ true);
+ });
+ } else {
+ mEventProcessQueue.offer(event);
+ }
+ }
+
+ @Override
+ public void notifyContentCaptureEvents(
+ @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
+ runOnUiThread(() -> {
+ prepareViewStructures(contentCaptureEvents);
+ runOnContentCaptureThread(() ->
+ notifyContentCaptureEventsImpl(contentCaptureEvents));
+ });
+ }
+
+ /**
+ * Traverse events and pre-process {@link View} events to {@link ViewStructureSession} events.
+ * If a {@link View} event is invalid, an empty {@link ViewStructureSession} will still be
+ * provided.
+ */
+ private void prepareViewStructures(
+ @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
+ for (int i = 0; i < contentCaptureEvents.size(); i++) {
+ int sessionId = contentCaptureEvents.keyAt(i);
+ ArrayList<Object> events = contentCaptureEvents.valueAt(i);
+ for_each_event: for (int j = 0; j < events.size(); j++) {
+ Object event = events.get(j);
+ if (event instanceof View) {
+ View view = (View) event;
+ ContentCaptureSession session = view.getContentCaptureSession();
+ ViewStructureSession structureSession = new ViewStructureSession();
+
+ // Replace the View event with ViewStructureSession no matter the data is
+ // available or not. This is to ensure the sequence of the events are still
+ // the same. Calls to notifyViewAppeared will check the availability later.
+ events.set(j, structureSession);
+ if (session == null) {
+ Log.w(TAG, "no content capture session on view: " + view);
+ continue for_each_event;
+ }
+ int actualId = session.getId();
+ if (actualId != sessionId) {
+ Log.w(TAG, "content capture session mismatch for view (" + view
+ + "): was " + sessionId + " before, it's " + actualId + " now");
+ continue for_each_event;
+ }
+ ViewStructure structure = session.newViewStructure(view);
+ view.onProvideContentCaptureStructure(structure, /* flags= */ 0);
+
+ structureSession.setSession(session);
+ structureSession.setStructure(structure);
+ }
+ }
+ }
+ }
+
+ private void notifyContentCaptureEventsImpl(
+ @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
+ checkOnContentCaptureThread();
+ try {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents");
+ }
+ for (int i = 0; i < contentCaptureEvents.size(); i++) {
+ int sessionId = contentCaptureEvents.keyAt(i);
+ internalNotifyViewTreeEvent(sessionId, /* started= */ true);
+ ArrayList<Object> events = contentCaptureEvents.valueAt(i);
+ for_each_event: for (int j = 0; j < events.size(); j++) {
+ Object event = events.get(j);
+ if (event instanceof AutofillId) {
+ internalNotifyViewDisappeared(sessionId, (AutofillId) event);
+ } else if (event instanceof ViewStructureSession viewStructureSession) {
+ viewStructureSession.notifyViewAppeared();
+ } else if (event instanceof Insets) {
+ internalNotifyViewInsetsChanged(sessionId, (Insets) event);
+ } else {
+ Log.w(TAG, "invalid content capture event: " + event);
+ }
+ }
+ internalNotifyViewTreeEvent(sessionId, /* started= */ false);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
+ @Override
+ void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+ super.dump(prefix, pw);
+
+ pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
+ pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId());
+ if (mDirectServiceInterface != null) {
+ pw.print(prefix); pw.print("mDirectServiceInterface: ");
+ pw.println(mDirectServiceInterface);
+ }
+ pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
+ pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
+ pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState));
+ if (mApplicationToken != null) {
+ pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
+ }
+ if (mShareableActivityToken != null) {
+ pw.print(prefix); pw.print("sharable activity token: ");
+ pw.println(mShareableActivityToken);
+ }
+ if (mComponentName != null) {
+ pw.print(prefix); pw.print("component name: ");
+ pw.println(mComponentName.flattenToShortString());
+ }
+ if (mEvents != null && !mEvents.isEmpty()) {
+ final int numberEvents = mEvents.size();
+ pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents);
+ pw.print('/'); pw.println(mManager.mOptions.maxBufferSize);
+ if (sVerbose && numberEvents > 0) {
+ final String prefix3 = prefix + " ";
+ for (int i = 0; i < numberEvents; i++) {
+ final ContentCaptureEvent event = mEvents.get(i);
+ pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
+ pw.println();
+ }
+ }
+ pw.print(prefix); pw.print("mNextFlushForTextChanged: ");
+ pw.println(mNextFlushForTextChanged);
+ pw.print(prefix); pw.print("flush frequency: ");
+ if (mNextFlushForTextChanged) {
+ pw.println(mManager.mOptions.textChangeFlushingFrequencyMs);
+ } else {
+ pw.println(mManager.mOptions.idleFlushingFrequencyMs);
+ }
+ pw.print(prefix); pw.print("next flush: ");
+ TimeUtils.formatDuration(mNextFlush - System.currentTimeMillis(), pw);
+ pw.print(" ("); pw.print(TimeUtils.logTimeOfDay(mNextFlush)); pw.println(")");
+ }
+ if (mFlushHistory != null) {
+ pw.print(prefix); pw.println("flush history:");
+ mFlushHistory.reverseDump(/* fd= */ null, pw, /* args= */ null); pw.println();
+ } else {
+ pw.print(prefix); pw.println("not logging flush history");
+ }
+
+ super.dump(prefix, pw);
+ }
+
+ /**
+ * Gets a string that can be used to identify the activity on logging statements.
+ */
+ private String getActivityName() {
+ return mComponentName == null
+ ? "pkg:" + mContext.getPackageName()
+ : "act:" + mComponentName.flattenToShortString();
+ }
+
+ @NonNull
+ private String getDebugState() {
+ return getActivityName() + " [state=" + getStateAsString(mState) + ", disabled="
+ + mDisabled.get() + "]";
+ }
+
+ @NonNull
+ private String getDebugState(@FlushReason int reason) {
+ return getDebugState() + ", reason=" + getFlushReasonAsString(reason);
+ }
+
+ private boolean isContentProtectionReceiverEnabled() {
+ return mManager.mOptions.contentProtectionOptions.enableReceiver;
+ }
+
+ private boolean isContentCaptureReceiverEnabled() {
+ return mManager.mOptions.enableReceiver;
+ }
+
+ 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());
+ }
+
+ /**
+ * Checks that the current work is running on the assigned thread from {@code mHandler} and
+ * count the number of times running on the wrong thread.
+ *
+ * <p>It is not guaranteed that the callers always invoke function from a single thread.
+ * Therefore, accessing internal properties in {@link MainContentCaptureSession} should
+ * always delegate to the assigned thread from {@code mHandler} for synchronization.</p>
+ */
+ private void checkOnContentCaptureThread() {
+ final boolean onContentCaptureThread = mContentCaptureHandler.getLooper().isCurrentThread();
+ if (!onContentCaptureThread) {
+ mWrongThreadCount.incrementAndGet();
+ Log.e(TAG, "MainContentCaptureSession running on " + Thread.currentThread());
+ }
+ }
+
+ /** Reports number of times running on the wrong thread. */
+ private void reportWrongThreadMetric() {
+ Counter.logIncrement(
+ CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID, mWrongThreadCount.getAndSet(0));
+ }
+
+ /**
+ * Ensures that {@code r} will be running on the assigned thread.
+ *
+ * <p>This is to prevent unnecessary delegation to Handler that results in fragmented runnable.
+ * </p>
+ */
+ private void runOnContentCaptureThread(@NonNull Runnable r) {
+ if (!mContentCaptureHandler.getLooper().isCurrentThread()) {
+ mContentCaptureHandler.post(r);
+ } else {
+ r.run();
+ }
+ }
+
+ private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) {
+ if (!mContentCaptureHandler.getLooper().isCurrentThread()) {
+ mContentCaptureHandler.removeMessages(what);
+ mContentCaptureHandler.post(r);
+ } else {
+ r.run();
+ }
+ }
+
+ private void runOnUiThread(@NonNull Runnable r) {
+ if (mUiHandler.getLooper().isCurrentThread()) {
+ r.run();
+ } else {
+ mUiHandler.post(r);
+ }
+ }
+
+ /**
+ * Holds {@link ContentCaptureSession} and related {@link ViewStructure} for processing.
+ */
+ private static final class ViewStructureSession {
+ @Nullable private ContentCaptureSession mSession;
+ @Nullable private ViewStructure mStructure;
+
+ ViewStructureSession() {}
+
+ void setSession(@Nullable ContentCaptureSession session) {
+ this.mSession = session;
+ }
+
+ void setStructure(@Nullable ViewStructure struct) {
+ this.mStructure = struct;
+ }
+
+ /**
+ * Calls {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)} if the session and
+ * the view structure are available.
+ */
+ void notifyViewAppeared() {
+ if (mSession != null && mStructure != null) {
+ mSession.notifyViewAppeared(mStructure);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
index f05d9cbe1c8d..983658a01dc8 100644
--- a/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
+++ b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
@@ -96,8 +96,10 @@ public class PackageInfoCommonUtils {
info.baseRevisionCode = pkg.getBaseRevisionCode();
info.splitRevisionCodes = pkg.getSplitRevisionCodes();
info.versionName = pkg.getVersionName();
- info.sharedUserId = pkg.getSharedUserId();
- info.sharedUserLabel = pkg.getSharedUserLabelResourceId();
+ if (!pkg.isLeavingSharedUser()) {
+ info.sharedUserId = pkg.getSharedUserId();
+ info.sharedUserLabel = pkg.getSharedUserLabelResourceId();
+ }
info.applicationInfo = applicationInfo;
info.installLocation = pkg.getInstallLocation();
if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c6209dd25c47..232a36fb6cb3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -578,6 +578,7 @@
<protected-broadcast android:name="com.android.settings.network.SWITCH_TO_SUBSCRIPTION" />
<protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
+ <protected-broadcast android:name="android.app.action.KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED" />
<protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
<protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" />
<protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java
deleted file mode 100644
index 785a8a1ced60..000000000000
--- a/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.doReturn;
-
-import android.app.Activity;
-import android.app.ClientTransactionHandler;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link ActivityConfigurationChangeItem}.
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:ActivityConfigurationChangeItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class ActivityConfigurationChangeItemTest {
-
- @Mock
- private ClientTransactionHandler mHandler;
- @Mock
- private IBinder mToken;
- @Mock
- private Activity mActivity;
- // Can't mock final class.
- private final Configuration mConfiguration = new Configuration();
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void testGetContextToUpdate() {
- doReturn(mActivity).when(mHandler).getActivity(mToken);
-
- final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem
- .obtain(mToken, mConfiguration);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(mActivity, context);
- }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
new file mode 100644
index 000000000000..b5e8203c51ed
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.app.ClientTransactionHandler;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+import android.util.MergedConfiguration;
+import android.view.IWindow;
+import android.view.InsetsState;
+import android.window.ClientWindowFrames;
+import android.window.WindowContext;
+import android.window.WindowContextInfo;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for subtypes of {@link ClientTransactionItem}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ClientTransactionItemTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class ClientTransactionItemTest {
+
+ @Mock
+ private ClientTransactionHandler mHandler;
+ @Mock
+ private IBinder mActivityToken;
+ @Mock
+ private Activity mActivity;
+ @Mock
+ private PendingTransactionActions mPendingActions;
+ @Mock
+ private IBinder mWindowClientToken;
+ @Mock
+ private WindowContext mWindowContext;
+ @Mock
+ private IWindow mWindow;
+
+ // Can't mock final class.
+ private Configuration mGlobalConfig;
+ private Configuration mConfiguration;
+ private ActivityThread.ActivityClientRecord mActivityClientRecord;
+ private ArrayMap<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed;
+ private InsetsState mInsetsState;
+ private ClientWindowFrames mFrames;
+ private MergedConfiguration mMergedConfiguration;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mGlobalConfig = new Configuration();
+ mConfiguration = new Configuration();
+ mActivitiesToBeDestroyed = new ArrayMap<>();
+ mActivityClientRecord = new ActivityThread.ActivityClientRecord();
+ mInsetsState = new InsetsState();
+ mFrames = new ClientWindowFrames();
+ mMergedConfiguration = new MergedConfiguration(mGlobalConfig, mConfiguration);
+
+ doReturn(mActivity).when(mHandler).getActivity(mActivityToken);
+ doReturn(mActivitiesToBeDestroyed).when(mHandler).getActivitiesToBeDestroyed();
+ }
+
+ @Test
+ public void testActivityConfigurationChangeItem_getContextToUpdate() {
+ final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem
+ .obtain(mActivityToken, mConfiguration);
+ final Context context = item.getContextToUpdate(mHandler);
+
+ assertEquals(mActivity, context);
+ }
+
+ @Test
+ public void testActivityRelaunchItem_getContextToUpdate() {
+ final ActivityRelaunchItem item = ActivityRelaunchItem
+ .obtain(mActivityToken, null /* pendingResults */, null /* pendingNewIntents */,
+ 0 /* configChange */, mMergedConfiguration, false /* preserveWindow */);
+ final Context context = item.getContextToUpdate(mHandler);
+
+ assertEquals(mActivity, context);
+ }
+
+ @Test
+ public void testConfigurationChangeItem_getContextToUpdate() {
+ final ConfigurationChangeItem item = ConfigurationChangeItem
+ .obtain(mConfiguration, DEVICE_ID_DEFAULT);
+ final Context context = item.getContextToUpdate(mHandler);
+
+ assertEquals(ActivityThread.currentApplication(), context);
+ }
+
+ @Test
+ public void testDestroyActivityItem_preExecute() {
+ final DestroyActivityItem item = DestroyActivityItem
+ .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+ item.preExecute(mHandler);
+
+ assertEquals(1, mActivitiesToBeDestroyed.size());
+ assertEquals(item, mActivitiesToBeDestroyed.get(mActivityToken));
+ }
+
+ @Test
+ public void testDestroyActivityItem_postExecute() {
+ final DestroyActivityItem item = DestroyActivityItem
+ .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+ item.preExecute(mHandler);
+ item.postExecute(mHandler, mPendingActions);
+
+ assertTrue(mActivitiesToBeDestroyed.isEmpty());
+ }
+
+ @Test
+ public void testDestroyActivityItem_execute() {
+ final DestroyActivityItem item = DestroyActivityItem
+ .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+ item.execute(mHandler, mActivityClientRecord, mPendingActions);
+
+ verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */,
+ eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any());
+ }
+
+ @Test
+ public void testLaunchActivityItem_getContextToUpdate() {
+ final LaunchActivityItem item = new TestUtils.LaunchActivityItemBuilder(
+ mActivityToken, new Intent(), new ActivityInfo())
+ .build();
+
+ final Context context = item.getContextToUpdate(mHandler);
+
+ assertEquals(ActivityThread.currentApplication(), context);
+ }
+
+ @Test
+ public void testMoveToDisplayItem_getContextToUpdate() {
+ final MoveToDisplayItem item = MoveToDisplayItem
+ .obtain(mActivityToken, DEFAULT_DISPLAY, mConfiguration);
+ final Context context = item.getContextToUpdate(mHandler);
+
+ assertEquals(mActivity, context);
+ }
+
+ @Test
+ public void testWindowContextInfoChangeItem_execute() {
+ final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
+ .obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY);
+ item.execute(mHandler, mPendingActions);
+
+ verify(mHandler).handleWindowContextInfoChanged(mWindowClientToken,
+ new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY));
+ }
+
+ @Test
+ public void testWindowContextInfoChangeItem_getContextToUpdate() {
+ doReturn(mWindowContext).when(mHandler).getWindowContext(mWindowClientToken);
+
+ final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
+ .obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY);
+ final Context context = item.getContextToUpdate(mHandler);
+
+ assertEquals(mWindowContext, context);
+ }
+
+ @Test
+ public void testWindowContextWindowRemovalItem_execute() {
+ final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain(
+ mWindowClientToken);
+ item.execute(mHandler, mPendingActions);
+
+ verify(mHandler).handleWindowContextWindowRemoval(mWindowClientToken);
+ }
+
+ @Test
+ public void testWindowStateResizeItem_execute() throws RemoteException {
+ final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
+ true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */,
+ true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+ true /* dragResizing */);
+ item.execute(mHandler, mPendingActions);
+
+ verify(mWindow).resized(mFrames,
+ true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */,
+ true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+ true /* dragResizing */);
+ }
+
+ @Test
+ public void testWindowStateResizeItem_getContextToUpdate() {
+ final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
+ true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */,
+ true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+ true /* dragResizing */);
+ final Context context = item.getContextToUpdate(mHandler);
+
+ assertEquals(ActivityThread.currentApplication(), context);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java
deleted file mode 100644
index d9f5523c9782..000000000000
--- a/core/tests/coretests/src/android/app/servertransaction/ConfigurationChangeItemTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-import static android.content.Context.DEVICE_ID_DEFAULT;
-
-import static org.junit.Assert.assertEquals;
-
-import android.app.ActivityThread;
-import android.app.ClientTransactionHandler;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link ConfigurationChangeItem}.
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:ConfigurationChangeItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class ConfigurationChangeItemTest {
-
- @Mock
- private ClientTransactionHandler mHandler;
- // Can't mock final class.
- private final Configuration mConfiguration = new Configuration();
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void testGetContextToUpdate() {
- final ConfigurationChangeItem item = ConfigurationChangeItem
- .obtain(mConfiguration, DEVICE_ID_DEFAULT);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(ActivityThread.currentApplication(), context);
- }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java b/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java
deleted file mode 100644
index ecd75a8370ce..000000000000
--- a/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import android.app.ActivityThread.ActivityClientRecord;
-import android.app.ClientTransactionHandler;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArrayMap;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link DestroyActivityItem}.
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:DestroyActivityItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class DestroyActivityItemTest {
-
- @Mock
- private ClientTransactionHandler mHandler;
- @Mock
- private PendingTransactionActions mPendingActions;
- @Mock
- private IBinder mActivityToken;
-
- // Can't mock final class.
- private ActivityClientRecord mActivityClientRecord;
-
- private ArrayMap<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed;
- private DestroyActivityItem mItem;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mItem = DestroyActivityItem.obtain(
- mActivityToken, false /* finished */, 123 /* configChanges */);
- mActivityClientRecord = new ActivityClientRecord();
- mActivitiesToBeDestroyed = new ArrayMap<>();
-
- doReturn(mActivitiesToBeDestroyed).when(mHandler).getActivitiesToBeDestroyed();
- }
-
- @Test
- public void testPreExecute() {
- mItem.preExecute(mHandler);
-
- assertEquals(1, mActivitiesToBeDestroyed.size());
- assertEquals(mItem, mActivitiesToBeDestroyed.get(mActivityToken));
- }
-
- @Test
- public void testPostExecute() {
- mItem.preExecute(mHandler);
- mItem.postExecute(mHandler, mPendingActions);
-
- assertTrue(mActivitiesToBeDestroyed.isEmpty());
- }
-
- @Test
- public void testExecute() {
- mItem.execute(mHandler, mActivityClientRecord, mPendingActions);
-
- verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */,
- eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any());
- }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java
deleted file mode 100644
index a801a76d16c9..000000000000
--- a/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import android.app.ClientTransactionHandler;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-import android.window.WindowContext;
-import android.window.WindowContextInfo;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link WindowContextInfoChangeItem}.
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:WindowContextInfoChangeItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class WindowContextInfoChangeItemTest {
-
- @Mock
- private ClientTransactionHandler mHandler;
- @Mock
- private PendingTransactionActions mPendingActions;
- @Mock
- private IBinder mClientToken;
- @Mock
- private WindowContext mWindowContext;
- // Can't mock final class.
- private final Configuration mConfiguration = new Configuration();
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void testExecute() {
- final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
- .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY);
- item.execute(mHandler, mPendingActions);
-
- verify(mHandler).handleWindowContextInfoChanged(mClientToken,
- new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY));
- }
-
- @Test
- public void testGetContextToUpdate() {
- doReturn(mWindowContext).when(mHandler).getWindowContext(mClientToken);
-
- final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
- .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(mWindowContext, context);
- }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java
deleted file mode 100644
index cf9935f2822f..000000000000
--- a/core/tests/coretests/src/android/app/servertransaction/WindowContextWindowRemovalItemTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-import static org.mockito.Mockito.verify;
-
-import android.app.ClientTransactionHandler;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link WindowContextWindowRemovalItem}.
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:WindowContextWindowRemovalItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class WindowContextWindowRemovalItemTest {
-
- @Mock
- private ClientTransactionHandler mHandler;
- @Mock
- private PendingTransactionActions mPendingActions;
- @Mock
- private IBinder mClientToken;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void testExecute() {
- final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain(
- mClientToken);
- item.execute(mHandler, mPendingActions);
-
- verify(mHandler).handleWindowContextWindowRemoval(mClientToken);
- }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
deleted file mode 100644
index 4d45daf3570c..000000000000
--- a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-import static org.mockito.Mockito.verify;
-
-import android.app.ClientTransactionHandler;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.util.MergedConfiguration;
-import android.view.IWindow;
-import android.view.InsetsState;
-import android.window.ClientWindowFrames;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link WindowStateResizeItem}.
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:WindowStateResizeItemTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class WindowStateResizeItemTest {
-
- @Mock
- private ClientTransactionHandler mHandler;
- @Mock
- private PendingTransactionActions mPendingActions;
- @Mock
- private IWindow mWindow;
-
- private InsetsState mInsetsState;
- private ClientWindowFrames mFrames;
- private MergedConfiguration mConfiguration;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- mInsetsState = new InsetsState();
- mFrames = new ClientWindowFrames();
- mConfiguration = new MergedConfiguration();
- }
-
- @Test
- public void testExecute() throws RemoteException {
- final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
- true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
- true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
- true /* dragResizing */);
- item.execute(mHandler, mPendingActions);
-
- verify(mWindow).resized(mFrames,
- true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
- true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
- true /* dragResizing */);
- }
-}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index 35ddfdb3723b..e52aa1b0fff4 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -153,7 +153,7 @@ public class ContentCaptureManagerTest {
final ContentCaptureManager manager =
new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
// Ensure main session is created.
- final MainContentCaptureSession unused = manager.getMainContentCaptureSession();
+ final ContentCaptureSession unused = manager.getMainContentCaptureSession();
final WindowManager.LayoutParams initialParam = new WindowManager.LayoutParams();
initialParam.flags |= WindowManager.LayoutParams.FLAG_SECURE;
@@ -167,7 +167,7 @@ public class ContentCaptureManagerTest {
final ContentCaptureManager manager =
new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
// Ensure main session is created.
- final MainContentCaptureSession unused = manager.getMainContentCaptureSession();
+ final ContentCaptureSession unused = manager.getMainContentCaptureSession();
final WindowManager.LayoutParams initialParam = new WindowManager.LayoutParams();
initialParam.flags |= WindowManager.LayoutParams.FLAG_SECURE;
// Default param does not have FLAG_SECURE set.
@@ -184,7 +184,7 @@ public class ContentCaptureManagerTest {
final ContentCaptureManager manager =
new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
// Ensure main session is created.
- final MainContentCaptureSession unused = manager.getMainContentCaptureSession();
+ final ContentCaptureSession unused = manager.getMainContentCaptureSession();
// Default param does not have FLAG_SECURE set.
final WindowManager.LayoutParams resetParam = new WindowManager.LayoutParams();
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 23b9b9bdb451..4a4c693d7122 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -21,12 +21,19 @@ import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.assertThrows;
import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentName;
import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.SparseArray;
import android.view.View;
import android.view.ViewStructure;
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.google.common.collect.ImmutableMap;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
@@ -40,6 +47,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.ArrayList;
import java.util.Map;
/**
@@ -195,6 +203,22 @@ public class ContentCaptureSessionTest {
}
@Override
+ void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
+ @NonNull ComponentName component, int flags) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ boolean isDisabled() {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ boolean setDisabled(boolean disabled) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
ContentCaptureSession newChild(ContentCaptureContext context) {
throw new UnsupportedOperationException("should not have been called");
}
@@ -210,20 +234,20 @@ public class ContentCaptureSessionTest {
}
@Override
- void internalNotifyViewAppeared(ViewStructureImpl node) {
+ void internalNotifyViewAppeared(final int sessionId, ViewStructureImpl node) {
throw new UnsupportedOperationException("should not have been called");
}
@Override
- void internalNotifyViewDisappeared(AutofillId id) {}
+ void internalNotifyViewDisappeared(final int sessionId, AutofillId id) {}
@Override
- void internalNotifyViewTextChanged(AutofillId id, CharSequence text) {
+ void internalNotifyViewTextChanged(final int sessionId, AutofillId id, CharSequence text) {
throw new UnsupportedOperationException("should not have been called");
}
@Override
- public void internalNotifyViewTreeEvent(boolean started) {
+ public void internalNotifyViewTreeEvent(final int sessionId, boolean started) {
if (started) {
mInternalNotifyViewTreeEventStartedCount += 1;
} else {
@@ -242,7 +266,34 @@ public class ContentCaptureSessionTest {
}
@Override
- void internalNotifyViewInsetsChanged(Insets viewInsets) {
+ void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId,
+ @NonNull ContentCaptureContext clientContext) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void internalNotifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ public void notifyContentCaptureEvents(
+ @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
+
+ }
+
+ @Override
+ void internalNotifyViewInsetsChanged(final int sessionId, Insets viewInsets) {
throw new UnsupportedOperationException("should not have been called");
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
new file mode 100644
index 000000000000..f0f3a9683353
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
@@ -0,0 +1,530 @@
+/*
+ * 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.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED;
+import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.graphics.Insets;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.contentprotection.ContentProtectionEventProcessor;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Test for {@link MainContentCaptureSessionV2}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentcapture.MainContentCaptureSessionV2Test}
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@TestableLooper.RunWithLooper
+public class MainContentCaptureSessionV2Test {
+
+ private static final int BUFFER_SIZE = 100;
+
+ private static final int REASON = 123;
+
+ private static final ContentCaptureEvent EVENT =
+ new ContentCaptureEvent(/* sessionId= */ 0, TYPE_SESSION_STARTED);
+
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName("com.test.package", "TestClass");
+
+ private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+ private static final ContentCaptureManager.StrippedContext sStrippedContext =
+ new ContentCaptureManager.StrippedContext(sContext);
+
+ private TestableLooper mTestableLooper;
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock private IContentCaptureManager mMockSystemServerInterface;
+
+ @Mock private ContentProtectionEventProcessor mMockContentProtectionEventProcessor;
+
+ @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager;
+
+ @Before
+ public void setup() {
+ mTestableLooper = TestableLooper.get(this);
+ }
+
+ @Test
+ public void onSessionStarted_contentProtectionEnabled_processorCreated() {
+ MainContentCaptureSessionV2 session = createSession();
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+ mTestableLooper.processAllMessages();
+
+ assertThat(session.mContentProtectionEventProcessor).isNotNull();
+ }
+
+ @Test
+ public void onSessionStarted_contentProtectionDisabled_processorNotCreated() {
+ MainContentCaptureSessionV2 session =
+ createSession(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ false);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+ mTestableLooper.processAllMessages();
+
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ }
+
+ @Test
+ public void onSessionStarted_contentProtectionNoBuffer_processorNotCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true,
+ -BUFFER_SIZE,
+ /* requiredGroups= */ List.of(List.of("a")),
+ /* optionalGroups= */ Collections.emptyList(),
+ /* optionalGroupsThreshold= */ 0));
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+ mTestableLooper.processAllMessages();
+
+ 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));
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+ mTestableLooper.processAllMessages();
+
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ }
+
+ @Test
+ public void onSessionStarted_noComponentName_processorNotCreated() {
+ MainContentCaptureSessionV2 session = createSession();
+ session.mComponentName = null;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+ mTestableLooper.processAllMessages();
+
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ }
+
+ @Test
+ public void sendEvent_contentCaptureDisabled_contentProtectionDisabled() {
+ MainContentCaptureSessionV2 session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ false);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+ mTestableLooper.processAllMessages();
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ public void sendEvent_contentCaptureDisabled_contentProtectionEnabled() {
+ MainContentCaptureSessionV2 session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ true);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+ mTestableLooper.processAllMessages();
+
+ verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ public void sendEvent_contentCaptureEnabled_contentProtectionDisabled() {
+ MainContentCaptureSessionV2 session =
+ createSession(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ false);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+ mTestableLooper.processAllMessages();
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNotNull();
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void sendEvent_contentCaptureEnabled_contentProtectionEnabled() {
+ MainContentCaptureSessionV2 session = createSession();
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+ mTestableLooper.processAllMessages();
+
+ verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
+ assertThat(session.mEvents).isNotNull();
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void sendEvent_contentProtectionEnabled_processorNotCreated() {
+ MainContentCaptureSessionV2 session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ true);
+
+ session.sendEvent(EVENT);
+ mTestableLooper.processAllMessages();
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ public void flush_contentCaptureDisabled_contentProtectionDisabled() throws Exception {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ false);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+ mTestableLooper.processAllMessages();
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyZeroInteractions(mMockContentCaptureDirectManager);
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void flush_contentCaptureDisabled_contentProtectionEnabled() {
+ MainContentCaptureSessionV2 session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ true);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+ mTestableLooper.processAllMessages();
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyZeroInteractions(mMockContentCaptureDirectManager);
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void flush_contentCaptureEnabled_contentProtectionDisabled() throws Exception {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ false);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+ mTestableLooper.processAllMessages();
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isEmpty();
+ assertEventFlushedContentCapture(options);
+ }
+
+ @Test
+ public void flush_contentCaptureEnabled_contentProtectionEnabled() throws Exception {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+ mTestableLooper.processAllMessages();
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isEmpty();
+ assertEventFlushedContentCapture(options);
+ }
+
+ @Test
+ public void destroySession() throws Exception {
+ MainContentCaptureSessionV2 session = createSession();
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.destroySession();
+ mTestableLooper.processAllMessages();
+
+ verify(mMockSystemServerInterface).finishSession(anyInt());
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mDirectServiceInterface).isNull();
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ }
+
+ @Test
+ public void resetSession() {
+ MainContentCaptureSessionV2 session = createSession();
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.resetSession(/* newState= */ 0);
+ mTestableLooper.processAllMessages();
+
+ verifyZeroInteractions(mMockSystemServerInterface);
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mDirectServiceInterface).isNull();
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void notifyContentCaptureEvents_notStarted_ContentCaptureDisabled_ProtectionDisabled() {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ false);
+ MainContentCaptureSessionV2 session = createSession(options);
+
+ notifyContentCaptureEvents(session);
+ mTestableLooper.processAllMessages();
+
+ verifyZeroInteractions(mMockContentCaptureDirectManager);
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void notifyContentCaptureEvents_started_ContentCaptureDisabled_ProtectionDisabled() {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ false);
+ MainContentCaptureSessionV2 session = createSession(options);
+
+ session.onSessionStarted(0x2, null);
+ notifyContentCaptureEvents(session);
+ mTestableLooper.processAllMessages();
+
+ verifyZeroInteractions(mMockContentCaptureDirectManager);
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void notifyContentCaptureEvents_notStarted_ContentCaptureEnabled_ProtectionEnabled() {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ notifyContentCaptureEvents(session);
+ mTestableLooper.processAllMessages();
+
+ verifyZeroInteractions(mMockContentCaptureDirectManager);
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ @SuppressWarnings("GuardedBy")
+ public void notifyContentCaptureEvents_started_ContentCaptureEnabled_ProtectionEnabled()
+ throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.onSessionStarted(0x2, null);
+ // Override the processor for interaction verification.
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+ notifyContentCaptureEvents(session);
+ mTestableLooper.processAllMessages();
+
+ // Force flush will happen twice.
+ verify(mMockContentCaptureDirectManager, times(1))
+ .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARING), any());
+ verify(mMockContentCaptureDirectManager, times(1))
+ .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARED), any());
+ // Other than the five view events, there will be two additional tree appearing events.
+ verify(mMockContentProtectionEventProcessor, times(7)).processEvent(any());
+ assertThat(session.mEvents).isEmpty();
+ }
+
+ /** Simulates the regular content capture events sequence. */
+ private void notifyContentCaptureEvents(final MainContentCaptureSessionV2 session) {
+ final ArrayList<Object> events = new ArrayList<>(
+ List.of(
+ prepareView(session),
+ prepareView(session),
+ new AutofillId(0),
+ prepareView(session),
+ Insets.of(0, 0, 0, 0)
+ )
+ );
+
+ final SparseArray<ArrayList<Object>> contentCaptureEvents = new SparseArray<>();
+ contentCaptureEvents.set(session.getId(), events);
+
+ session.notifyContentCaptureEvents(contentCaptureEvents);
+ }
+
+ private View prepareView(final MainContentCaptureSessionV2 session) {
+ final View view = new View(sContext);
+ view.setContentCaptureSession(session);
+ return view;
+ }
+
+ private static ContentCaptureOptions createOptions(
+ boolean enableContentCaptureReceiver,
+ ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
+ return new ContentCaptureOptions(
+ /* loggingLevel= */ 0,
+ BUFFER_SIZE,
+ /* idleFlushingFrequencyMs= */ 0,
+ /* textChangeFlushingFrequencyMs= */ 0,
+ /* logHistorySize= */ 0,
+ /* disableFlushForViewTreeAppearing= */ false,
+ enableContentCaptureReceiver,
+ contentProtectionOptions,
+ /* whitelistedComponents= */ null);
+ }
+
+ private static ContentCaptureOptions createOptions(
+ boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
+ return createOptions(
+ enableContentCaptureReceiver,
+ new ContentCaptureOptions.ContentProtectionOptions(
+ enableContentProtectionReceiver,
+ BUFFER_SIZE,
+ /* requiredGroups= */ List.of(List.of("a")),
+ /* optionalGroups= */ Collections.emptyList(),
+ /* optionalGroupsThreshold= */ 0));
+ }
+
+ private ContentCaptureManager createManager(ContentCaptureOptions options) {
+ return new ContentCaptureManager(sContext, mMockSystemServerInterface, options);
+ }
+
+ private MainContentCaptureSessionV2 createSession(ContentCaptureManager manager) {
+ final Handler testHandler = Handler.createAsync(mTestableLooper.getLooper());
+ MainContentCaptureSessionV2 session =
+ new MainContentCaptureSessionV2(
+ sStrippedContext,
+ manager,
+ testHandler,
+ testHandler,
+ mMockSystemServerInterface);
+ session.mComponentName = COMPONENT_NAME;
+ return session;
+ }
+
+ private MainContentCaptureSessionV2 createSession(ContentCaptureOptions options) {
+ return createSession(createManager(options));
+ }
+
+ private MainContentCaptureSessionV2 createSession(
+ boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
+ return createSession(
+ createOptions(enableContentCaptureReceiver, enableContentProtectionReceiver));
+ }
+
+ private MainContentCaptureSessionV2 createSession() {
+ return createSession(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ }
+
+ private void assertEventFlushedContentCapture(ContentCaptureOptions options) throws Exception {
+ ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
+ verify(mMockContentCaptureDirectManager)
+ .sendEvents(captor.capture(), eq(REASON), eq(options));
+
+ assertThat(captor.getValue()).isNotNull();
+ List<ContentCaptureEvent> actual = captor.getValue().getList();
+ assertThat(actual).isNotNull();
+ assertThat(actual).containsExactly(EVENT);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 15ee4e1d4adf..65597de44255 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -47,6 +47,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.getActivityInt
import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
+import android.annotation.CallbackExecutor;
import android.app.Activity;
import android.app.ActivityClient;
import android.app.ActivityOptions;
@@ -63,6 +64,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.SystemProperties;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -85,6 +87,7 @@ import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.extensions.WindowExtensionsImpl;
import androidx.window.extensions.core.util.function.Consumer;
import androidx.window.extensions.core.util.function.Function;
+import androidx.window.extensions.core.util.function.Predicate;
import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
@@ -158,8 +161,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** Callback to Jetpack to notify about changes to split states. */
@GuardedBy("mLock")
@Nullable
- private Consumer<List<SplitInfo>> mEmbeddingCallback;
+ private Consumer<List<SplitInfo>> mSplitInfoCallback;
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
+
+ /**
+ * Stores callbacks to Jetpack to notify about changes to {@link ActivityStack activityStacks}
+ * and corresponding {@link Executor executors} to dispatch the callback.
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final ArrayMap<Consumer<List<ActivityStack>>, Executor> mActivityStackCallbacks =
+ new ArrayMap<>();
+
+ private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>();
+
private final Handler mHandler;
final Object mLock = new Object();
private final ActivityStartMonitor mActivityStartMonitor;
@@ -283,7 +298,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@Override
- public void unpinTopActivityStack(int taskId){
+ public void unpinTopActivityStack(int taskId) {
synchronized (mLock) {
Log.i(TAG, "Request to unpin top activity stack.");
final TaskContainer task = getTaskContainer(taskId);
@@ -333,6 +348,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
public void setActivityStackAttributesCalculator(
@NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
calculator) {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return;
+ }
synchronized (mLock) {
mActivityStackAttributesCalculator = calculator;
}
@@ -340,6 +358,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void clearActivityStackAttributesCalculator() {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return;
+ }
synchronized (mLock) {
mActivityStackAttributesCalculator = null;
}
@@ -351,7 +372,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return mSplitAttributesCalculator;
}
- @Override
+ // TODO(b/295993745): remove after we migrate to the bundle approach.
@NonNull
public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options,
@NonNull IBinder token) {
@@ -368,6 +389,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Registers the split organizer callback to notify about changes to active splits.
+ *
* @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with
* {@link WindowExtensionsImpl#getVendorApiLevel()} 2.
*/
@@ -381,12 +403,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Registers the split organizer callback to notify about changes to active splits.
+ *
* @since {@link WindowExtensionsImpl#getVendorApiLevel()} 2
*/
+ @Override
public void setSplitInfoCallback(Consumer<List<SplitInfo>> callback) {
synchronized (mLock) {
- mEmbeddingCallback = callback;
- updateCallbackIfNecessary();
+ mSplitInfoCallback = callback;
+ updateSplitInfoCallbackIfNecessary();
}
}
@@ -396,7 +420,35 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void clearSplitInfoCallback() {
synchronized (mLock) {
- mEmbeddingCallback = null;
+ mSplitInfoCallback = null;
+ }
+ }
+
+ /**
+ * Registers the callback for the {@link ActivityStack} state change.
+ *
+ * @param executor The executor to dispatch the callback.
+ * @param callback The callback for this {@link ActivityStack} state change.
+ */
+ @Override
+ public void registerActivityStackCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<List<ActivityStack>> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ synchronized (mLock) {
+ mActivityStackCallbacks.put(callback, executor);
+ updateActivityStackCallbackIfNecessary();
+ }
+ }
+
+ /** @see #registerActivityStackCallback(Executor, Consumer) */
+ @Override
+ public void unregisterActivityStackCallback(@NonNull Consumer<List<ActivityStack>> callback) {
+ Objects.requireNonNull(callback);
+
+ synchronized (mLock) {
+ mActivityStackCallbacks.remove(callback);
}
}
@@ -408,13 +460,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
synchronized (mLock) {
// Translate ActivityStack to TaskFragmentContainer.
final List<TaskFragmentContainer> pendingFinishingContainers =
- activityStackTokens.stream()
- .map(token -> {
+ activityStackTokens.stream().map(token -> {
synchronized (mLock) {
return getContainer(token);
}
- }).filter(Objects::nonNull)
- .toList();
+ }).filter(Objects::nonNull).toList();
if (pendingFinishingContainers.isEmpty()) {
return;
@@ -497,6 +547,68 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
+ @Override
+ public void updateActivityStackAttributes(@NonNull IBinder activityStackToken,
+ @NonNull ActivityStackAttributes attributes) {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return;
+ }
+ Objects.requireNonNull(activityStackToken);
+ Objects.requireNonNull(attributes);
+
+ synchronized (mLock) {
+ final TaskFragmentContainer container = getContainer(activityStackToken);
+ if (container == null) {
+ Log.w(TAG, "Cannot find TaskFragmentContainer for token:" + activityStackToken);
+ return;
+ }
+ if (!container.isOverlay()) {
+ Log.w(TAG, "Updating non-overlay container has not supported yet!");
+ return;
+ }
+
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+ mPresenter.applyActivityStackAttributes(wct, container, attributes);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ }
+ }
+
+ @Override
+ @Nullable
+ public ParentContainerInfo getParentContainerInfo(@NonNull IBinder activityStackToken) {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return null;
+ }
+ Objects.requireNonNull(activityStackToken);
+ synchronized (mLock) {
+ final TaskFragmentContainer container = getContainer(activityStackToken);
+ if (container == null) {
+ return null;
+ }
+ final TaskContainer.TaskProperties properties = container.getTaskContainer()
+ .getTaskProperties();
+ return mPresenter.createParentContainerInfoFromTaskProperties(properties);
+ }
+ }
+
+ @Override
+ @Nullable
+ public IBinder getActivityStackToken(@NonNull String tag) {
+ if (!Flags.activityEmbeddingOverlayPresentationFlag()) {
+ return null;
+ }
+ Objects.requireNonNull(tag);
+ synchronized (mLock) {
+ final TaskFragmentContainer taskFragmentContainer =
+ getContainer(container -> tag.equals(container.getOverlayTag()));
+ if (taskFragmentContainer == null) {
+ return null;
+ }
+ return taskFragmentContainer.getTaskFragmentToken();
+ }
+ }
+
/**
* Called when the transaction is ready so that the organizer can update the TaskFragments based
* on the changes in transaction.
@@ -565,8 +677,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Called when a TaskFragment is created and organized by this organizer.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param taskFragmentInfo Info of the TaskFragment that is created.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if
+ * needed.
+ * @param taskFragmentInfo Info of the TaskFragment that is created.
*/
// Suppress GuardedBy warning because lint ask to mark this method as
// @GuardedBy(container.mController.mLock), which is mLock itself
@@ -574,7 +687,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
- @NonNull TaskFragmentInfo taskFragmentInfo) {
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) {
return;
@@ -594,8 +707,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Called when the status of an organized TaskFragment is changed.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param taskFragmentInfo Info of the TaskFragment that is changed.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if
+ * needed.
+ * @param taskFragmentInfo Info of the TaskFragment that is changed.
*/
// Suppress GuardedBy warning because lint ask to mark this method as
// @GuardedBy(container.mController.mLock), which is mLock itself
@@ -665,8 +779,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Called when an organized TaskFragment is removed.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param taskFragmentInfo Info of the TaskFragment that is removed.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if
+ * needed.
+ * @param taskFragmentInfo Info of the TaskFragment that is removed.
*/
@VisibleForTesting
@GuardedBy("mLock")
@@ -686,14 +801,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Called when the parent leaf Task of organized TaskFragments is changed.
* When the leaf Task is changed, the organizer may want to update the TaskFragments in one
* transaction.
- *
+ * <p>
* For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged}
* with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there
* can be an override bounds.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param taskId Id of the parent Task that is changed.
- * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
+ * @param taskId Id of the parent Task that is changed.
+ * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task.
*/
@VisibleForTesting
@GuardedBy("mLock")
@@ -746,20 +861,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* original Task. In this case, we need to notify the organizer so that it can check if the
* Activity matches any split rule.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param taskId The Task that the activity is reparented to.
- * @param activityIntent The intent that the activity is original launched with.
- * @param activityToken If the activity belongs to the same process as the organizer, this
- * will be the actual activity token; if the activity belongs to a
- * different process, the server will generate a temporary token that
- * the organizer can use to reparent the activity through
- * {@link WindowContainerTransaction} if needed.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if
+ * needed.
+ * @param taskId The Task that the activity is reparented to.
+ * @param activityIntent The intent that the activity is original launched with.
+ * @param activityToken If the activity belongs to the same process as the organizer, this
+ * will be the actual activity token; if the activity belongs to a
+ * different process, the server will generate a temporary token that
+ * the organizer can use to reparent the activity through
+ * {@link WindowContainerTransaction} if needed.
*/
@VisibleForTesting
@GuardedBy("mLock")
void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
- int taskId, @NonNull Intent activityIntent,
- @NonNull IBinder activityToken) {
+ int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {
// If the activity belongs to the current app process, we treat it as a new activity
// launch.
final Activity activity = getActivity(activityToken);
@@ -807,14 +922,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Called when the {@link WindowContainerTransaction} created with
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
*
- * @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
- * @param errorCallbackToken token set in
- * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
- * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no
- * TaskFragment created.
- * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed
- * transaction operation.
- * @param exception exception from the server side.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if
+ * needed.
+ * @param errorCallbackToken token set in
+ * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
+ * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no
+ * TaskFragment created.
+ * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed
+ * transaction operation.
+ * @param exception exception from the server side.
*/
// Suppress GuardedBy warning because lint ask to mark this method as
// @GuardedBy(container.mController.mLock), which is mLock itself
@@ -854,7 +970,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
- /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
+ /**
+ * Called on receiving {@link #onTaskFragmentVanished} for cleanup.
+ */
@GuardedBy("mLock")
private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
@@ -881,11 +999,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Checks if the new added activity should be routed to a particular container. It can create a
* new container for the activity and a new split container if necessary.
- * @param activity the activity that is newly added to the Task.
- * @param isOnReparent whether the activity is reparented to the Task instead of new launched.
- * We only support to split as primary for reparented activity for now.
+ *
+ * @param activity the activity that is newly added to the Task.
+ * @param isOnReparent whether the activity is reparented to the Task instead of new launched.
+ * We only support to split as primary for reparented activity for now.
* @return {@code true} if the activity has been handled, such as placed in a TaskFragment, or
- * in a state that the caller shouldn't handle.
+ * in a state that the caller shouldn't handle.
*/
@VisibleForTesting
@GuardedBy("mLock")
@@ -918,7 +1037,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
if (!isOnReparent && taskContainer != null
&& taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
- != container) {
+ != container) {
// Do not resolve if the launched activity is not the top-most container (excludes
// the pinned and overlay container) in the Task.
return true;
@@ -943,6 +1062,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Resolves the activity to a {@link TaskFragmentContainer} according to the Split-rules.
*/
+ @GuardedBy("mLock")
boolean resolveActivityToContainerByRule(@NonNull WindowContainerTransaction wct,
@NonNull Activity activity, @Nullable TaskFragmentContainer container,
boolean isOnReparent) {
@@ -1027,7 +1147,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
@VisibleForTesting
void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct,
- @NonNull Activity activity) {
+ @NonNull Activity activity) {
if (getContainerWithActivity(activity) != null) {
// The activity has already been put in a TaskFragment. This is likely to be done by
// the server when the activity is started.
@@ -1077,7 +1197,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@GuardedBy("mLock")
private void expandActivity(@NonNull WindowContainerTransaction wct,
- @NonNull Activity activity) {
+ @NonNull Activity activity) {
final TaskFragmentContainer container = getContainerWithActivity(activity);
if (shouldContainerBeExpanded(container)) {
// Make sure that the existing container is expanded.
@@ -1089,7 +1209,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
- /** Whether the given new launched activity is in a split with a rule matched. */
+ /**
+ * Whether the given new launched activity is in a split with a rule matched.
+ */
// Suppress GuardedBy warning because lint asks to mark this method as
// @GuardedBy(mPresenter.mController.mLock), which is mLock itself
@SuppressWarnings("GuardedBy")
@@ -1147,7 +1269,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return getSplitRule(primaryActivity, launchedActivity) != null;
}
- /** Finds the activity below the given activity. */
+ /**
+ * Finds the activity below the given activity.
+ */
@VisibleForTesting
@Nullable
@GuardedBy("mLock")
@@ -1198,8 +1322,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
&& canReuseContainer(splitRule, splitContainer.getSplitRule(),
- taskProperties.getTaskMetrics(),
- calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
+ taskProperties.getTaskMetrics(),
+ calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
@@ -1333,7 +1457,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* prioritize to split the new activity with it if it is not
* {@code null}.
* @return the {@link TaskFragmentContainer} to start the new activity in. {@code null} if there
- * is no embedding rule matched.
+ * is no embedding rule matched.
*/
@VisibleForTesting
@Nullable
@@ -1478,7 +1602,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
final int windowingMode = taskContainer
- .getWindowingModeForSplitTaskFragment(sanitizedBounds);
+ .getWindowingModeForTaskFragment(sanitizedBounds);
mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
sanitizedBounds, windowingMode);
mPresenter.updateAnimationParams(wct, taskFragmentToken,
@@ -1495,7 +1619,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@NonNull
private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
- @NonNull Rect taskBounds) {
+ @NonNull Rect taskBounds) {
if (bounds.isEmpty()) {
// Don't need to check if the bounds follows the task bounds.
return bounds;
@@ -1534,11 +1658,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
getActivityIntentMinDimensionsPair(primaryActivity, intent));
if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
&& (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics,
- calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())
+ calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())
// TODO(b/231845476) we should always respect clearTop.
|| !respectClearTop)
&& mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
- null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) {
+ null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
return splitContainer.getSecondaryContainer();
@@ -1563,29 +1687,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
// Check pending appeared activity first because there can be a delay for the server
// update.
- for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
- .getTaskFragmentContainers();
- for (int j = containers.size() - 1; j >= 0; j--) {
- final TaskFragmentContainer container = containers.get(j);
- if (container.hasPendingAppearedActivity(activityToken)) {
- return container;
- }
- }
+ TaskFragmentContainer taskFragmentContainer =
+ getContainer(container -> container.hasPendingAppearedActivity(activityToken));
+ if (taskFragmentContainer != null) {
+ return taskFragmentContainer;
}
+
// Check appeared activity if there is no such pending appeared activity.
- for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
- .getTaskFragmentContainers();
- for (int j = containers.size() - 1; j >= 0; j--) {
- final TaskFragmentContainer container = containers.get(j);
- if (container.hasAppearedActivity(activityToken)) {
- return container;
- }
- }
- }
- return null;
+ return getContainer(container -> container.hasAppearedActivity(activityToken));
}
@GuardedBy("mLock")
@@ -1611,8 +1721,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
- @NonNull Activity activityInTask, int taskId,
- @NonNull TaskFragmentContainer pairedPrimaryContainer) {
+ @NonNull Activity activityInTask, int taskId,
+ @NonNull TaskFragmentContainer pairedPrimaryContainer) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
activityInTask, taskId, pairedPrimaryContainer, null /* tag */,
null /* launchOptions */);
@@ -1622,18 +1732,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Creates and registers a new organized container with an optional activity that will be
* re-parented to it in a WCT.
*
- * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment.
- * @param pendingAppearedIntent the Intent that will be started in the TaskFragment.
- * @param activityInTask activity in the same Task so that we can get the Task bounds
- * if needed.
- * @param taskId parent Task of the new TaskFragment.
- * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
- * set, the new container will be added right above it.
- * @param overlayTag The tag for the new created overlay container. It must be
- * needed if {@code isOverlay} is {@code true}. Otherwise,
- * it should be {@code null}.
- * @param launchOptions The launch options bundle to create a container. Must be
- * specified for overlay container.
+ * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment.
+ * @param pendingAppearedIntent the Intent that will be started in the TaskFragment.
+ * @param activityInTask activity in the same Task so that we can get the Task bounds
+ * if needed.
+ * @param taskId parent Task of the new TaskFragment.
+ * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
+ * set, the new container will be added right above it.
+ * @param overlayTag The tag for the new created overlay container. It must be
+ * needed if {@code isOverlay} is {@code true}. Otherwise,
+ * it should be {@code null}.
+ * @param launchOptions The launch options bundle to create a container. Must be
+ * specified for overlay container.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@@ -1674,7 +1784,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
primaryContainer.getTaskContainer().addSplitContainer(splitContainer);
}
- /** Cleanups all the dependencies when the TaskFragment is entering PIP. */
+ /**
+ * Cleanups all the dependencies when the TaskFragment is entering PIP.
+ */
@GuardedBy("mLock")
private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
@@ -1833,16 +1945,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@SuppressWarnings("GuardedBy")
@GuardedBy("mLock")
void updateOverlayContainer(@NonNull WindowContainerTransaction wct,
- @NonNull TaskFragmentContainer container) {
+ @NonNull TaskFragmentContainer container) {
final TaskContainer taskContainer = container.getTaskContainer();
// Dismiss the overlay container if it's the only container in the task and there's no
// direct activity in the parent task.
if (taskContainer.getTaskFragmentContainers().size() == 1
&& !taskContainer.hasDirectActivity()) {
container.finish(false /* shouldFinishDependent */, mPresenter, wct, this);
+ return;
}
- // TODO(b/295805054): Add the logic to update overlay container
+ if (mActivityStackAttributesCalculator != null) {
+ final ActivityStackAttributesCalculatorParams params =
+ new ActivityStackAttributesCalculatorParams(
+ mPresenter.createParentContainerInfoFromTaskProperties(
+ taskContainer.getTaskProperties()),
+ container.getOverlayTag(),
+ container.getLaunchOptions());
+ final ActivityStackAttributes attributes = mActivityStackAttributesCalculator
+ .apply(params);
+ mPresenter.applyActivityStackAttributes(wct, container, attributes);
+ }
}
/**
@@ -1851,11 +1974,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* are {@code null}, the {@link SplitAttributes} will be calculated with
* {@link SplitPresenter#computeSplitAttributes}.
*
- * @param splitContainer The {@link SplitContainer} to update
+ * @param splitContainer The {@link SplitContainer} to update
* @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
* Otherwise, use the value calculated by
* {@link SplitPresenter#computeSplitAttributes}
- *
* @return {@code true} if the update succeed. Otherwise, returns {@code false}.
*/
@VisibleForTesting
@@ -1890,7 +2012,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
- /** Whether the given split is the topmost split in the Task. */
+ /**
+ * Whether the given split is the topmost split in the Task.
+ */
private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) {
final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer()
.getTaskContainer().getSplitContainers();
@@ -1997,7 +2121,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
- /** Whether or not to allow activity in this container to launch placeholder. */
+ /**
+ * Whether or not to allow activity in this container to launch placeholder.
+ */
@GuardedBy("mLock")
private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
final TaskFragmentContainer topContainer = container.getTaskContainer()
@@ -2031,8 +2157,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Gets the activity options for starting the placeholder activity. In case the placeholder is
* launched when the Task is in the background, we don't want to bring the Task to the front.
- * @param primaryActivity the primary activity to launch the placeholder from.
- * @param isOnCreated whether this happens during the primary activity onCreated.
+ *
+ * @param primaryActivity the primary activity to launch the placeholder from.
+ * @param isOnCreated whether this happens during the primary activity onCreated.
*/
@VisibleForTesting
@GuardedBy("mLock")
@@ -2104,7 +2231,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@VisibleForTesting
@GuardedBy("mLock")
void updateCallbackIfNecessary() {
- if (mEmbeddingCallback == null || !readyToReportToClient()) {
+ updateSplitInfoCallbackIfNecessary();
+ updateActivityStackCallbackIfNecessary();
+ }
+
+ /**
+ * Notifies callbacks about changes to split states if necessary.
+ */
+ @GuardedBy("mLock")
+ private void updateSplitInfoCallbackIfNecessary() {
+ if (!readyToReportToClient() || mSplitInfoCallback == null) {
return;
}
final List<SplitInfo> currentSplitStates = getActiveSplitStatesIfStable();
@@ -2113,7 +2249,32 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
mLastReportedSplitStates.clear();
mLastReportedSplitStates.addAll(currentSplitStates);
- mEmbeddingCallback.accept(currentSplitStates);
+ mSplitInfoCallback.accept(currentSplitStates);
+ }
+
+ /**
+ * Notifies callbacks about changes to {@link ActivityStack} states if necessary.
+ */
+ @GuardedBy("mLock")
+ private void updateActivityStackCallbackIfNecessary() {
+ if (!readyToReportToClient() || mActivityStackCallbacks.isEmpty()) {
+ return;
+ }
+ final List<ActivityStack> currentActivityStacks = getActivityStacksIfStable();
+ if (currentActivityStacks == null
+ || mLastReportedActivityStacks.equals(currentActivityStacks)) {
+ return;
+ }
+ mLastReportedActivityStacks.clear();
+ mLastReportedActivityStacks.addAll(currentActivityStacks);
+ // Copy the map in case a callback is removed during the for-loop.
+ final ArrayMap<Consumer<List<ActivityStack>>, Executor> callbacks =
+ new ArrayMap<>(mActivityStackCallbacks);
+ for (int i = callbacks.size() - 1; i >= 0; --i) {
+ final Executor executor = callbacks.valueAt(i);
+ final Consumer<List<ActivityStack>> callback = callbacks.keyAt(i);
+ executor.execute(() -> callback.accept(currentActivityStacks));
+ }
}
/**
@@ -2138,6 +2299,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/**
+ * Returns a list of currently active {@link ActivityStack activityStacks}.
+ *
+ * @return a list of {@link ActivityStack activityStacks} if all the containers are in
+ * a stable state, or {@code null} otherwise.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private List<ActivityStack> getActivityStacksIfStable() {
+ final List<ActivityStack> activityStacks = new ArrayList<>();
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<ActivityStack> taskActivityStacks =
+ mTaskContainers.valueAt(i).getActivityStacksIfStable();
+ if (taskActivityStacks == null) {
+ return null;
+ }
+ activityStacks.addAll(taskActivityStacks);
+ }
+ return activityStacks;
+ }
+
+ /**
* Whether we can now report the split states to the client.
*/
@GuardedBy("mLock")
@@ -2207,11 +2389,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Nullable
@GuardedBy("mLock")
TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
+ return getContainer(container -> fragmentToken.equals(container.getTaskFragmentToken()));
+ }
+
+ @Nullable
+ @GuardedBy("mLock")
+ TaskFragmentContainer getContainer(@NonNull Predicate<TaskFragmentContainer> predicate) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
.getTaskFragmentContainers();
- for (TaskFragmentContainer container : containers) {
- if (container.getTaskFragmentToken().equals(fragmentToken)) {
+ for (int j = containers.size() - 1; j >= 0; j--) {
+ final TaskFragmentContainer container = containers.get(j);
+ if (predicate.test(container)) {
return container;
}
}
@@ -2304,6 +2493,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* container. There is a case when primary containers for placeholders should be retained
* despite the rule configuration to finish primary with secondary - if they are marked as
* 'sticky' and the placeholder was finished when fully overlapping the primary container.
+ *
* @return {@code true} if the associated container should be retained (and not be finished).
*/
// Suppress GuardedBy warning because lint ask to mark this method as
@@ -2388,8 +2578,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
// specified by Intent, expand the overlay container to fill the parent task instead.
final ActivityStackAttributesCalculatorParams params =
- new ActivityStackAttributesCalculatorParams(mPresenter.toParentContainerInfo(
- mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
+ new ActivityStackAttributesCalculatorParams(
+ mPresenter.createParentContainerInfoFromTaskProperties(
+ mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
// Fallback to expand the bounds if there's no activityStackAttributes calculator.
final Rect relativeBounds = mActivityStackAttributesCalculator != null
? new Rect(mActivityStackAttributesCalculator.apply(params).getRelativeBounds())
@@ -2407,26 +2598,31 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
&& taskId == overlayContainer.getTaskId()) {
// If there's an overlay container with different tag shown in the same
// task, dismiss the existing overlay container.
- overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
- wct, SplitController.this);
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
}
if (overlayTag.equals(overlayContainer.getOverlayTag())
&& taskId != overlayContainer.getTaskId()) {
// If there's an overlay container with same tag in a different task,
// dismiss the overlay container since the tag must be unique per process.
- overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
- wct, SplitController.this);
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
}
if (overlayTag.equals(overlayContainer.getOverlayTag())
&& taskId == overlayContainer.getTaskId()) {
// If there's an overlay container with the same tag and task ID, we treat
// the OverlayCreateParams as the update to the container.
- final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
- .getTaskMetrics().getBounds();
final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+ final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+ final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics()
+ .getBounds();
final Rect sanitizedBounds = sanitizeBounds(relativeBounds, intent, taskBounds);
+
mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
- mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
+ final int windowingMode = taskContainer
+ .getWindowingModeForTaskFragment(sanitizedBounds);
+ mPresenter.updateWindowingMode(wct, overlayToken, windowingMode);
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayContainer,
!sanitizedBounds.isEmpty());
// We can just return the updated overlay container and don't need to
// check other condition since we only have one OverlayCreateParams, and
@@ -2539,7 +2735,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
- /** Executor that posts on the main application thread. */
+ /**
+ * Executor that posts on the main application thread.
+ */
private static class MainThreadExecutor implements Executor {
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -2687,7 +2885,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
&& calculatedSplitAttributes.equals(containerSplitAttributes);
}
- /** Whether the two rules have the same presentation. */
+ /**
+ * Whether the two rules have the same presentation.
+ */
@VisibleForTesting
static boolean areRulesSamePresentation(@NonNull SplitPairRule rule1,
@NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index acfd8e4314bf..543570c63ad7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -16,6 +16,8 @@
package androidx.window.extensions.embedding;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.PackageManager.MATCH_ALL;
import android.app.Activity;
@@ -187,7 +189,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties,
splitAttributes);
final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(secondaryRelBounds);
+ .getWindowingModeForTaskFragment(secondaryRelBounds);
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
primaryActivity.getActivityToken(), secondaryRelBounds, windowingMode);
updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
@@ -259,7 +261,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
if (container == null || container == containerToAvoid) {
container = mController.newContainer(activity, taskId);
final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(relBounds);
+ .getWindowingModeForTaskFragment(relBounds);
final IBinder reparentActivityToken = activity.getActivityToken();
createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken,
relBounds, windowingMode, reparentActivityToken);
@@ -268,7 +270,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
} else {
resizeTaskFragmentIfRegistered(wct, container, relBounds);
final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(relBounds);
+ .getWindowingModeForTaskFragment(relBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
}
updateAnimationParams(wct, container.getTaskFragmentToken(), splitAttributes);
@@ -310,7 +312,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// Pass in the primary container to make sure it is added right above the primary.
primaryContainer);
final TaskContainer taskContainer = mController.getTaskContainer(taskId);
- final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
+ final int windowingMode = taskContainer.getWindowingModeForTaskFragment(
primaryRelBounds);
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule, splitAttributes);
@@ -347,6 +349,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
&& secondaryContainer.areLastRequestedBoundsEqual(null /* bounds */)
&& !secondaryRelBounds.isEmpty();
+ // TODO(b/243518738): remove usages of XXXIfRegistered.
// If the task fragments are not registered yet, the positions will be updated after they
// are created again.
resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRelBounds);
@@ -357,7 +360,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// When placeholder is shown in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
}
- final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
+ final int windowingMode = taskContainer.getWindowingModeForTaskFragment(
primaryRelBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
@@ -398,13 +401,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
*/
void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
- @NonNull TaskFragmentContainer taskFragmentContainer,
+ @NonNull TaskFragmentContainer container,
boolean isolatedNavigationEnabled) {
- if (taskFragmentContainer.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
+ if (container.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
return;
}
- taskFragmentContainer.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
- setTaskFragmentIsolatedNavigation(wct, taskFragmentContainer.getTaskFragmentToken(),
+ container.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
+ setTaskFragmentIsolatedNavigation(wct, container.getTaskFragmentToken(),
isolatedNavigationEnabled);
}
@@ -566,6 +569,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
super.setCompanionTaskFragment(wct, primary, secondary);
}
+ void applyActivityStackAttributes(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container, @NonNull ActivityStackAttributes attributes) {
+ final Rect bounds = attributes.getRelativeBounds();
+
+ resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
+ updateWindowingMode(wct, container.getTaskFragmentToken(),
+ bounds.isEmpty() ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_MULTI_WINDOW);
+ }
+
/**
* Expands the split container if the current split bounds are smaller than the Activity or
* Intent that is added to the container.
@@ -1086,7 +1098,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@NonNull
- ParentContainerInfo toParentContainerInfo(@NonNull TaskProperties taskProperties) {
+ ParentContainerInfo createParentContainerInfoFromTaskProperties(
+ @NonNull TaskProperties taskProperties) {
final Configuration configuration = taskProperties.getConfiguration();
final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent
.getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 028e75fe010f..64ad4faa421d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -145,7 +145,7 @@ class TaskContainer {
* the pair of TaskFragments are stacked due to the limited space.
*/
@WindowingMode
- int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) {
+ int getWindowingModeForTaskFragment(@Nullable Rect taskFragmentBounds) {
// Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it
// will be set to UNDEFINED which will then inherit the Task windowing mode.
if (taskFragmentBounds == null || taskFragmentBounds.isEmpty() || isInPictureInPicture()) {
@@ -443,6 +443,26 @@ class TaskContainer {
return splitStates;
}
+ // TODO(b/317358445): Makes ActivityStack and SplitInfo callback more stable.
+ /**
+ * Returns a list of currently active {@link ActivityStack activityStacks}.
+ *
+ * @return a list of {@link ActivityStack activityStacks} if all the containers are in
+ * a stable state, or {@code null} otherwise.
+ */
+ @Nullable
+ List<ActivityStack> getActivityStacksIfStable() {
+ final List<ActivityStack> activityStacks = new ArrayList<>();
+ for (TaskFragmentContainer container : mContainers) {
+ final ActivityStack activityStack = container.toActivityStackIfStable();
+ if (activityStack == null) {
+ return null;
+ }
+ activityStacks.add(activityStack);
+ }
+ return activityStacks;
+ }
+
/** A wrapper class which contains the information of {@link TaskContainer} */
static final class TaskProperties {
private final int mDisplayId;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index afd554b6e52b..810bded8a7f0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -107,11 +107,11 @@ class TaskFragmentContainer {
private final String mOverlayTag;
/**
- * The launch options that was used to create this container. Must not be {@code null} for
- * {@link #isOverlay()} container.
+ * The launch options that was used to create this container. Must not {@link Bundle#isEmpty()}
+ * for {@link #isOverlay()} container.
*/
- @Nullable
- private final Bundle mLaunchOptions;
+ @NonNull
+ private final Bundle mLaunchOptions = new Bundle();
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -210,7 +210,9 @@ class TaskFragmentContainer {
if (overlayTag != null) {
Objects.requireNonNull(launchOptions);
}
- mLaunchOptions = launchOptions;
+ if (launchOptions != null) {
+ mLaunchOptions.putAll(launchOptions);
+ }
if (pairedPrimaryContainer != null) {
// The TaskFragment will be positioned right above the paired container.
@@ -925,6 +927,17 @@ class TaskFragmentContainer {
return mOverlayTag;
}
+ /**
+ * Returns the options that was used to launch this {@link TaskFragmentContainer}.
+ * {@link Bundle#isEmpty()} means there's no launch option for this container.
+ * <p>
+ * Note that WM Jetpack owns the logic. The WM Extension library must not modify this object.
+ */
+ @NonNull
+ Bundle getLaunchOptions() {
+ return mLaunchOptions;
+ }
+
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 678bdef3df92..5ef6a5263f96 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -16,6 +16,7 @@
package androidx.window.extensions.embedding;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
@@ -287,10 +288,10 @@ public class OverlayPresentationTest {
createOrUpdateOverlayTaskFragmentIfNeeded("test");
verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ verify(mSplitPresenter).updateWindowingMode(mTransaction, overlayToken,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayContainer,
false);
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
}
@Test
@@ -315,8 +316,10 @@ public class OverlayPresentationTest {
createOrUpdateOverlayTaskFragmentIfNeeded("test");
verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
- false);
+ verify(mSplitPresenter).updateWindowingMode(mTransaction,
+ overlayToken, WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
+ overlayContainer, false);
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
.containsExactly(overlayContainer);
}
@@ -425,6 +428,50 @@ public class OverlayPresentationTest {
.that(taskContainer.getTaskFragmentContainers()).isEmpty();
}
+ @Test
+ public void testUpdateActivityStackAttributes_nullParams_throwException() {
+ assertThrows(NullPointerException.class, () ->
+ mSplitController.updateActivityStackAttributes(null,
+ new ActivityStackAttributes.Builder().build()));
+
+ assertThrows(NullPointerException.class, () ->
+ mSplitController.updateActivityStackAttributes(new Binder(), null));
+
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ }
+
+ @Test
+ public void testUpdateActivityStackAttributes_nullContainer_earlyReturn() {
+ final TaskFragmentContainer container = mSplitController.newContainer(mActivity,
+ mActivity.getTaskId());
+ mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
+ new ActivityStackAttributes.Builder().build());
+
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ }
+
+ @Test
+ public void testUpdateActivityStackAttributes_notOverlay_earlyReturn() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+ mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
+ new ActivityStackAttributes.Builder().build());
+
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ }
+
+ @Test
+ public void testUpdateActivityStackAttributes() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
+ doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any());
+ final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder().build();
+ final IBinder token = container.getTaskFragmentToken();
+
+ mSplitController.updateActivityStackAttributes(token, attrs);
+
+ verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs));
+ }
+
/**
* A simplified version of {@link SplitController.ActivityStartMonitor
* #createOrUpdateOverlayTaskFragmentIfNeeded}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index bab4e9195880..b60943a60076 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -354,7 +354,7 @@ public class SplitControllerTest {
bundle.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
container.getTaskFragmentToken());
monitor.mCurrentIntent = intent;
- doReturn(container).when(mSplitController).getContainer(any());
+ doReturn(container).when(mSplitController).getContainer(any(IBinder.class));
monitor.onStartActivityResult(START_CANCELED, bundle);
assertNull(container.getPendingAppearedIntent());
@@ -1642,7 +1642,7 @@ public class SplitControllerTest {
// We need to set those in case we are not respecting clear top.
// TODO(b/231845476) we should always respect clearTop.
final int windowingMode = mSplitController.getTaskContainer(primaryContainer.getTaskId())
- .getWindowingModeForSplitTaskFragment(TASK_BOUNDS);
+ .getWindowingModeForTaskFragment(TASK_BOUNDS);
primaryContainer.setLastRequestedWindowingMode(windowingMode);
secondaryContainer.setLastRequestedWindowingMode(windowingMode);
primaryContainer.setLastRequestedBounds(getSplitBounds(true /* isPrimary */));
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 7b77235f66f7..a5995a3027ac 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -75,7 +75,7 @@ public class TaskContainerTest {
final Configuration configuration = new Configuration();
assertEquals(WINDOWING_MODE_MULTI_WINDOW,
- taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+ taskContainer.getWindowingModeForTaskFragment(splitBounds));
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
@@ -83,7 +83,7 @@ public class TaskContainerTest {
null /* decorSurface */));
assertEquals(WINDOWING_MODE_MULTI_WINDOW,
- taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+ taskContainer.getWindowingModeForTaskFragment(splitBounds));
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
@@ -91,12 +91,12 @@ public class TaskContainerTest {
null /* decorSurface */));
assertEquals(WINDOWING_MODE_FREEFORM,
- taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
+ taskContainer.getWindowingModeForTaskFragment(splitBounds));
// Empty bounds means the split pair are stacked, so it should be UNDEFINED which will then
// inherit the Task windowing mode
assertEquals(WINDOWING_MODE_UNDEFINED,
- taskContainer.getWindowingModeForSplitTaskFragment(new Rect()));
+ taskContainer.getWindowingModeForTaskFragment(new Rect()));
}
@Test
diff --git a/libs/incident/libincident.map.txt b/libs/incident/libincident.map.txt
index f75cceaf59fa..d8650e129742 100644
--- a/libs/incident/libincident.map.txt
+++ b/libs/incident/libincident.map.txt
@@ -1,15 +1,15 @@
LIBINCIDENT {
global:
- AIncidentReportArgs_init; # systemapi # introduced=30
- AIncidentReportArgs_clone; # systemapi # introduced=30
- AIncidentReportArgs_delete; # systemapi # introduced=30
- AIncidentReportArgs_setAll; # systemapi # introduced=30
- AIncidentReportArgs_setPrivacyPolicy; # systemapi # introduced=30
- AIncidentReportArgs_addSection; # systemapi # introduced=30
- AIncidentReportArgs_setReceiverPackage; # systemapi # introduced=30
- AIncidentReportArgs_setReceiverClass; # systemapi # introduced=30
- AIncidentReportArgs_addHeader; # systemapi # introduced=30
- AIncidentReportArgs_takeReport; # systemapi # introduced=30
+ AIncidentReportArgs_init; # systemapi introduced=30
+ AIncidentReportArgs_clone; # systemapi introduced=30
+ AIncidentReportArgs_delete; # systemapi introduced=30
+ AIncidentReportArgs_setAll; # systemapi introduced=30
+ AIncidentReportArgs_setPrivacyPolicy; # systemapi introduced=30
+ AIncidentReportArgs_addSection; # systemapi introduced=30
+ AIncidentReportArgs_setReceiverPackage; # systemapi introduced=30
+ AIncidentReportArgs_setReceiverClass; # systemapi introduced=30
+ AIncidentReportArgs_addHeader; # systemapi introduced=30
+ AIncidentReportArgs_takeReport; # systemapi introduced=30
local:
*;
};
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 8f5f1f6a4794..4fbe9ee90c4c 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -48,9 +48,7 @@
{"exclude-annotation": "androidx.test.filters.FlakyTest"},
{"exclude-annotation": "org.junit.Ignore"}
]
- }
- ],
- "postsubmit": [
+ },
{
"file_patterns": [
"[^/]*(LoudnessCodec)[^/]*\\.java"
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
index 3b15632d065d..ce1004c4c58c 100644
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
@@ -95,12 +95,17 @@ public class LoudnessCodecConfiguratorTest {
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
public void setAudioTrack_callsAudioServiceStart() throws Exception {
final AudioTrack track = createAudioTrack();
+ final MediaCodec mediaCodec = createAndConfigureMediaCodec();
- mLcc.addMediaCodec(createAndConfigureMediaCodec());
- mLcc.setAudioTrack(track);
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+ mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
- anyList());
+ verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
+ anyList());
+ } finally {
+ mediaCodec.release();
+ }
}
@Test
@@ -108,10 +113,15 @@ public class LoudnessCodecConfiguratorTest {
public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception {
when(mAudioService.getLoudnessParams(anyInt(), any())).thenReturn(new PersistableBundle());
final AudioTrack track = createAudioTrack();
+ final MediaCodec mediaCodec = createAndConfigureMediaCodec();
- mLcc.getLoudnessCodecParams(track, createAndConfigureMediaCodec());
+ try {
+ mLcc.getLoudnessCodecParams(track, mediaCodec);
- verify(mAudioService).getLoudnessParams(eq(track.getPlayerIId()), any());
+ verify(mAudioService).getLoudnessParams(eq(track.getPlayerIId()), any());
+ } finally {
+ mediaCodec.release();
+ }
}
@Test
@@ -120,10 +130,14 @@ public class LoudnessCodecConfiguratorTest {
final AudioTrack track = createAudioTrack();
final MediaCodec mediaCodec = createAndConfigureMediaCodec();
- mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+ mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+ verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+ } finally {
+ mediaCodec.release();
+ }
}
@Test
@@ -132,24 +146,33 @@ public class LoudnessCodecConfiguratorTest {
final AudioTrack track = createAudioTrack();
final MediaCodec mediaCodec = createAndConfigureMediaCodec();
- mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
- mLcc.setAudioTrack(track);
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+ mLcc.setAudioTrack(track);
+ mLcc.setAudioTrack(track);
- verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
- anyList());
+ verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
+ anyList());
+ } finally {
+ mediaCodec.release();
+ }
}
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
public void setTrackNull_stopCodecUpdates() throws Exception {
final AudioTrack track = createAudioTrack();
+ final MediaCodec mediaCodec = createAndConfigureMediaCodec();
- mLcc.addMediaCodec(createAndConfigureMediaCodec());
- mLcc.setAudioTrack(track);
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+ mLcc.setAudioTrack(track);
- mLcc.setAudioTrack(null); // stops updates
- verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+ mLcc.setAudioTrack(null); // stops updates
+ verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+ } finally {
+ mediaCodec.release();
+ }
}
@Test
@@ -157,27 +180,37 @@ public class LoudnessCodecConfiguratorTest {
public void addMediaCodecTwice_triggersIAE() throws Exception {
final MediaCodec mediaCodec = createAndConfigureMediaCodec();
- mLcc.addMediaCodec(mediaCodec);
+ try {
+ mLcc.addMediaCodec(mediaCodec);
- assertThrows(IllegalArgumentException.class, () -> mLcc.addMediaCodec(mediaCodec));
+ assertThrows(IllegalArgumentException.class, () -> mLcc.addMediaCodec(mediaCodec));
+ } finally {
+ mediaCodec.release();
+ }
}
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
public void setClearTrack_removeAllAudioServicePiidCodecs() throws Exception {
final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
-
final AudioTrack track = createAudioTrack();
-
- mLcc.addMediaCodec(createAndConfigureMediaCodec());
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
- argument.capture());
- assertEquals(argument.getValue().size(), 1);
-
- mLcc.addMediaCodec(createAndConfigureMediaCodec());
- mLcc.setAudioTrack(null);
- verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+ final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
+ final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
+
+ try {
+ mLcc.addMediaCodec(mediaCodec1);
+ mLcc.setAudioTrack(track);
+ verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
+ argument.capture());
+ assertEquals(argument.getValue().size(), 1);
+
+ mLcc.addMediaCodec(mediaCodec2);
+ mLcc.setAudioTrack(null);
+ verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+ } finally {
+ mediaCodec1.release();
+ mediaCodec2.release();
+ }
}
@Test
@@ -186,24 +219,35 @@ public class LoudnessCodecConfiguratorTest {
final AudioTrack track = createAudioTrack();
final MediaCodec mediaCodec = createAndConfigureMediaCodec();
- mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
- mLcc.removeMediaCodec(mediaCodec);
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+ mLcc.setAudioTrack(track);
+ mLcc.removeMediaCodec(mediaCodec);
- verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+ verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+ } finally {
+ mediaCodec.release();
+ }
}
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
public void addMediaCodecAfterSetTrack_callsAudioServiceAdd() throws Exception {
final AudioTrack track = createAudioTrack();
-
- mLcc.addMediaCodec(createAndConfigureMediaCodec());
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
-
- mLcc.addMediaCodec(createAndConfigureMediaCodec());
- verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any());
+ final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
+ final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
+
+ try {
+ mLcc.addMediaCodec(mediaCodec1);
+ mLcc.setAudioTrack(track);
+ verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+
+ mLcc.addMediaCodec(mediaCodec2);
+ verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any());
+ } finally {
+ mediaCodec1.release();
+ mediaCodec2.release();
+ }
}
@Test
@@ -212,25 +256,36 @@ public class LoudnessCodecConfiguratorTest {
final AudioTrack track = createAudioTrack();
final MediaCodec mediaCodec = createAndConfigureMediaCodec();
- mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+ try {
+ mLcc.addMediaCodec(mediaCodec);
+ mLcc.setAudioTrack(track);
+ verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
- mLcc.removeMediaCodec(mediaCodec);
- verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+ mLcc.removeMediaCodec(mediaCodec);
+ verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+ } finally {
+ mediaCodec.release();
+ }
}
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
public void removeWrongMediaCodecAfterSetTrack_triggersIAE() throws Exception {
final AudioTrack track = createAudioTrack();
-
- mLcc.addMediaCodec(createAndConfigureMediaCodec());
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
-
- assertThrows(IllegalArgumentException.class,
- () -> mLcc.removeMediaCodec(createAndConfigureMediaCodec()));
+ final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
+ final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
+
+ try {
+ mLcc.addMediaCodec(mediaCodec1);
+ mLcc.setAudioTrack(track);
+ verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mLcc.removeMediaCodec(mediaCodec2));
+ } finally {
+ mediaCodec1.release();
+ mediaCodec2.release();
+ }
}
private static AudioTrack createAudioTrack() {
@@ -250,19 +305,21 @@ public class LoudnessCodecConfiguratorTest {
MediaExtractor extractor;
extractor = new MediaExtractor();
- extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+ try {
+ extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
testFd.getLength());
- testFd.close();
-
- assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
- MediaFormat format = extractor.getTrackFormat(0);
- String mime = format.getString(MediaFormat.KEY_MIME);
- assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX));
- final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime);
-
- Log.v(TAG, "configuring with " + format);
- mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
-
- return mediaCodec;
+ assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
+ MediaFormat format = extractor.getTrackFormat(0);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX));
+ final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime);
+
+ Log.v(TAG, "configuring with " + format);
+ mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+ return mediaCodec;
+ } finally {
+ testFd.close();
+ extractor.release();
+ }
}
}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 9f2a9ac4798d..960510879a4c 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -1,9 +1,9 @@
LIBANDROID {
global:
- AActivityManager_addUidImportanceListener; # systemapi # introduced=31
- AActivityManager_removeUidImportanceListener; # systemapi # introduced=31
- AActivityManager_isUidActive; # systemapi # introduced=31
- AActivityManager_getUidImportance; # systemapi # introduced=31
+ AActivityManager_addUidImportanceListener; # systemapi introduced=31
+ AActivityManager_removeUidImportanceListener; # systemapi introduced=31
+ AActivityManager_isUidActive; # systemapi introduced=31
+ AActivityManager_getUidImportance; # systemapi introduced=31
AAssetDir_close;
AAssetDir_getNextFileName;
AAssetDir_rewind;
diff --git a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml b/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
new file mode 100644
index 000000000000..9d16f32db932
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- Copied from //frameworks/base/core/res/res/drawable/item_background_material.xml -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/autofill_light_colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <color android:color="@android:color/white"/>
+ </item>
+</ripple> \ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
new file mode 100644
index 000000000000..2f0c83b6556a
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
+ android:color="@android:color/transparent">
+ <item
+ android:bottom="1dp"
+ android:shape="rectangle"
+ android:top="1dp">
+ <shape>
+ <corners android:radius="28dp" />
+ <solid android:color="@android:color/system_surface_container_high_light" />
+ </shape>
+ </item>
+</ripple> \ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml
new file mode 100644
index 000000000000..39f49caa7051
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
+ android:color="@android:color/transparent">
+ <item
+ android:bottom="1dp"
+ android:shape="rectangle"
+ android:top="1dp">
+ <shape>
+ <corners android:radius="28dp" />
+ <solid android:color="@android:color/system_surface_container_high_dark" />
+ </shape>
+ </item>
+</ripple> \ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml b/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml
new file mode 100644
index 000000000000..e4e9f7ac85a9
--- /dev/null
+++ b/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml
@@ -0,0 +1,37 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/autofill.Dataset">
+ <ImageView
+ android:id="@android:id/icon1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:background="@null"/>
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@android:id/icon1"
+ style="@style/autofill.TextAppearance"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
new file mode 100644
index 000000000000..63b9f24d9033
--- /dev/null
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -0,0 +1,38 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- Color palette -->
+<resources>
+ <color name="autofill_light_colorPrimary">@color/primary_material_light</color>
+ <color name="autofill_light_colorAccent">@color/accent_material_light</color>
+ <color name="autofill_light_colorControlHighlight">@color/ripple_material_light</color>
+ <color name="autofill_light_colorButtonNormal">@color/button_material_light</color>
+
+ <!-- Text colors -->
+ <color name="autofill_light_textColorPrimary">@color/abc_primary_text_material_light</color>
+ <color name="autofill_light_textColorSecondary">@color/abc_secondary_text_material_light</color>
+ <color name="autofill_light_textColorHint">@color/abc_hint_foreground_material_light</color>
+ <color name="autofill_light_textColorHintInverse">@color/abc_hint_foreground_material_dark
+ </color>
+ <color name="autofill_light_textColorHighlight">@color/highlighted_text_material_light</color>
+ <color name="autofill_light_textColorLink">@color/autofill_light_colorAccent</color>
+
+ <!-- These colors are used for Remote Views. -->
+ <color name="background_dark_mode">#0E0C0B</color>
+ <color name="background">#F1F3F4</color>
+ <color name="text_primary_dark_mode">#DFDEDB</color>
+ <color name="text_primary">#202124</color>
+</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
new file mode 100644
index 000000000000..67003a330974
--- /dev/null
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="autofill_view_padding">16dp</dimen>
+ <dimen name="autofill_icon_size">16dp</dimen>
+</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/res/values/styles.xml b/packages/CredentialManager/res/values/styles.xml
new file mode 100644
index 000000000000..4a5761acdd83
--- /dev/null
+++ b/packages/CredentialManager/res/values/styles.xml
@@ -0,0 +1,38 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <style name="autofill.TextAppearance.Small" parent="@style/autofill.TextAppearance">
+ <item name="android:textSize">12sp</item>
+ </style>
+
+
+ <style name="autofill.Dataset" parent="">
+ <item name="android:background">@drawable/autofill_light_selectable_item_background</item>
+ </style>
+
+ <style name="autofill.TextAppearance" parent="">
+ <item name="android:textColor">@color/autofill_light_textColorPrimary</item>
+ <item name="android:textColorHint">@color/autofill_light_textColorHint</item>
+ <item name="android:textColorHighlight">@color/autofill_light_textColorHighlight</item>
+ <item name="android:textColorLink">@color/autofill_light_textColorLink</item>
+ <item name="android:textSize">14sp</item>
+ </style>
+
+ <style name="autofill.TextAppearance.Primary">
+ <item name="android:textColor">@color/autofill_light_textColorPrimary</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 20d2f09ced8f..0ff1c7fd8953 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager.autofill
+import android.R
import android.app.assist.AssistStructure
import android.content.Context
import android.credentials.CredentialManager
@@ -41,18 +42,19 @@ import android.service.autofill.SaveRequest
import android.service.credentials.CredentialProviderService
import android.util.Log
import android.view.autofill.AutofillId
-import org.json.JSONException
import android.widget.inline.InlinePresentationSpec
import androidx.autofill.inline.v1.InlineSuggestionUi
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
import com.android.credentialmanager.GetFlowUtils
-import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.common.ui.RemoteViewsFactory
import com.android.credentialmanager.getflow.ProviderDisplayInfo
-import com.android.credentialmanager.model.get.ProviderInfo
import com.android.credentialmanager.getflow.toProviderDisplayInfo
import com.android.credentialmanager.ktx.credentialEntry
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.get.ProviderInfo
+import org.json.JSONException
import org.json.JSONObject
import java.util.concurrent.Executors
@@ -127,9 +129,11 @@ class CredentialAutofillService : AutofillService() {
is PasswordCredentialEntry -> {
entryIconMap[entry.key + entry.subkey] = credentialEntry.icon
}
+
is PublicKeyCredentialEntry -> {
entryIconMap[entry.key + entry.subkey] = credentialEntry.icon
}
+
is CustomCredentialEntry -> {
entryIconMap[entry.key + entry.subkey] = credentialEntry.icon
}
@@ -172,11 +176,11 @@ class CredentialAutofillService : AutofillService() {
}
private fun processProvidersForAutofillId(
- filLRequest: FillRequest,
- autofillId: AutofillId,
- providerList: List<ProviderInfo>,
- entryIconMap: Map<String, Icon>,
- fillResponseBuilder: FillResponse.Builder
+ filLRequest: FillRequest,
+ autofillId: AutofillId,
+ providerList: List<ProviderInfo>,
+ entryIconMap: Map<String, Icon>,
+ fillResponseBuilder: FillResponse.Builder
): Boolean {
if (providerList.isEmpty()) {
return false
@@ -197,7 +201,7 @@ class CredentialAutofillService : AutofillService() {
var i = 0
var datasetAdded = false
- providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@ {
+ providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
val primaryEntry = it.sortedCredentialEntryList.first()
val pendingIntent = primaryEntry.pendingIntent
val fillInIntent = primaryEntry.fillInIntent
@@ -206,37 +210,48 @@ class CredentialAutofillService : AutofillService() {
Log.e(TAG, "PendingIntent was missing from the entry.")
return@usernameLoop
}
- if (inlinePresentationSpecs == null || i >= maxItemCount) {
+ if (inlinePresentationSpecs == null) {
+ Log.i(TAG, "Inline presentation spec is null, " +
+ "building dropdown presentation only")
+ }
+ if (i >= maxItemCount) {
Log.e(TAG, "Skipping because reached the max item count.")
return@usernameLoop
}
- // Create inline presentation
- val spec: InlinePresentationSpec
- if (i < inlinePresentationSpecsCount) {
- spec = inlinePresentationSpecs[i]
- } else {
- spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
- }
- val sliceBuilder = InlineSuggestionUi
- .newContentBuilder(pendingIntent)
- .setTitle(primaryEntry.userName)
- val icon: Icon
- if (primaryEntry.icon == null) {
+ val icon: Icon = if (primaryEntry.icon == null) {
// The empty entry icon has non-null icon reference but null drawable reference.
// If the drawable reference is null, then use the default icon.
- icon = getDefaultIcon()
+ getDefaultIcon()
} else {
- icon = entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey]
+ entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey]
?: getDefaultIcon()
}
- sliceBuilder.setStartIcon(icon)
- val inlinePresentation = InlinePresentation(
- sliceBuilder.build().slice, spec, /* pinned= */ false)
+ // Create inline presentation
+ var inlinePresentation: InlinePresentation? = null;
+ if (inlinePresentationSpecs != null) {
+ val spec: InlinePresentationSpec
+ if (i < inlinePresentationSpecsCount) {
+ spec = inlinePresentationSpecs[i]
+ } else {
+ spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+ }
+ val sliceBuilder = InlineSuggestionUi
+ .newContentBuilder(pendingIntent)
+ .setTitle(primaryEntry.userName)
+ sliceBuilder.setStartIcon(icon)
+ inlinePresentation = InlinePresentation(
+ sliceBuilder.build().slice, spec, /* pinned= */ false)
+ }
+ val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
+ this, icon, primaryEntry)
i++
val dataSetBuilder = Dataset.Builder()
val presentationBuilder = Presentations.Builder()
- .setInlinePresentation(inlinePresentation)
+ .setMenuPresentation(dropdownPresentation)
+ if (inlinePresentation != null) {
+ presentationBuilder.setInlinePresentation(inlinePresentation)
+ }
fillResponseBuilder.addDataset(
dataSetBuilder
@@ -305,7 +320,7 @@ class CredentialAutofillService : AutofillService() {
): MutableMap<AutofillId, MutableList<CredentialEntryInfo>> {
val autofillIdToCredentialEntries:
MutableMap<AutofillId, MutableList<CredentialEntryInfo>> = mutableMapOf()
- credentialEntryList.forEach entryLoop@ { credentialEntry ->
+ credentialEntryList.forEach entryLoop@{ credentialEntry ->
val autofillId: AutofillId? = credentialEntry
.fillInIntent
?.getParcelableExtra(
@@ -323,8 +338,8 @@ class CredentialAutofillService : AutofillService() {
}
private fun copyProviderInfo(
- providerInfo: ProviderInfo,
- credentialList: List<CredentialEntryInfo>
+ providerInfo: ProviderInfo,
+ credentialList: List<CredentialEntryInfo>
): ProviderInfo {
return ProviderInfo(
providerInfo.id,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
new file mode 100644
index 000000000000..4dc7f00c1550
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common.ui
+
+import android.content.Context
+import android.content.res.Configuration
+import android.widget.RemoteViews
+import androidx.core.content.ContextCompat
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import android.graphics.drawable.Icon
+
+class RemoteViewsFactory {
+
+ companion object {
+ private const val setAdjustViewBoundsMethodName = "setAdjustViewBounds"
+ private const val setMaxHeightMethodName = "setMaxHeight"
+ private const val setBackgroundResourceMethodName = "setBackgroundResource"
+
+ fun createDropdownPresentation(
+ context: Context,
+ icon: Icon,
+ credentialEntryInfo: CredentialEntryInfo
+ ): RemoteViews {
+ val padding = context.resources.getDimensionPixelSize(com.android
+ .credentialmanager.R.dimen.autofill_view_padding)
+ var layoutId: Int = com.android.credentialmanager.R.layout
+ .autofill_dataset_left_with_item_tag_hint
+ val remoteViews = RemoteViews(context.packageName, layoutId)
+ setRemoteViewsPaddings(remoteViews, padding)
+ val textColorPrimary = getTextColorPrimary(isDarkMode(context), context);
+ remoteViews.setTextColor(android.R.id.text1, textColorPrimary);
+ remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName)
+
+ remoteViews.setImageViewIcon(android.R.id.icon1, icon);
+ remoteViews.setBoolean(
+ android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+ remoteViews.setInt(
+ android.R.id.icon1,
+ setMaxHeightMethodName,
+ context.resources.getDimensionPixelSize(
+ com.android.credentialmanager.R.dimen.autofill_icon_size));
+ val drawableId = if (isDarkMode(context))
+ com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one_dark
+ else com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
+ remoteViews.setInt(
+ android.R.id.content, setBackgroundResourceMethodName, drawableId);
+ return remoteViews
+ }
+
+ private fun setRemoteViewsPaddings(
+ remoteViews: RemoteViews,
+ padding: Int) {
+ val halfPadding = padding / 2
+ remoteViews.setViewPadding(
+ android.R.id.text1,
+ halfPadding,
+ halfPadding,
+ halfPadding,
+ halfPadding)
+ }
+
+ private fun isDarkMode(context: Context): Boolean {
+ val currentNightMode = context.resources.configuration.uiMode and
+ Configuration.UI_MODE_NIGHT_MASK
+ return currentNightMode == Configuration.UI_MODE_NIGHT_YES
+ }
+
+ private fun getTextColorPrimary(darkMode: Boolean, context: Context): Int {
+ return if (darkMode) ContextCompat.getColor(
+ context, com.android.credentialmanager.R.color.text_primary_dark_mode)
+ else ContextCompat.getColor(context, com.android.credentialmanager.R.color.text_primary)
+ }
+ }
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 41d12dca869e..d9286b3463d4 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -212,6 +212,13 @@ flag {
}
flag {
+ name: "revamped_bouncer_messages"
+ namespace: "systemui"
+ description: "Change the bouncer message to be a 2-line more descriptive message"
+ bug: "236891644"
+}
+
+flag {
name: "rest_to_unlock"
namespace: "systemui"
description: "Require prolonged touch for fingerprint authentication"
diff --git a/packages/SystemUI/animation/core/Android.bp b/packages/SystemUI/animation/Android.bp
index 8438051e3430..8438051e3430 100644
--- a/packages/SystemUI/animation/core/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
diff --git a/packages/SystemUI/animation/core/AndroidManifest.xml b/packages/SystemUI/animation/AndroidManifest.xml
index 321cc53142c6..321cc53142c6 100644
--- a/packages/SystemUI/animation/core/AndroidManifest.xml
+++ b/packages/SystemUI/animation/AndroidManifest.xml
diff --git a/packages/SystemUI/animation/core/build.gradle b/packages/SystemUI/animation/build.gradle
index 12637f4071df..939455fa44ac 100644
--- a/packages/SystemUI/animation/core/build.gradle
+++ b/packages/SystemUI/animation/build.gradle
@@ -5,8 +5,8 @@ apply plugin: 'kotlin-android'
android {
sourceSets {
main {
- java.srcDirs = ["${SYS_UI_DIR}/animation/core/src/com/android/systemui/surfaceeffects/"]
- manifest.srcFile "${SYS_UI_DIR}/animation/core/AndroidManifest.xml"
+ java.srcDirs = ["${SYS_UI_DIR}/animation/src/com/android/systemui/surfaceeffects/"]
+ manifest.srcFile "${SYS_UI_DIR}/animation/AndroidManifest.xml"
}
}
diff --git a/packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml b/packages/SystemUI/animation/res/anim/launch_dialog_enter.xml
index c6b87d38f7da..c6b87d38f7da 100644
--- a/packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml
+++ b/packages/SystemUI/animation/res/anim/launch_dialog_enter.xml
diff --git a/packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml b/packages/SystemUI/animation/res/anim/launch_dialog_exit.xml
index a0f441eaeed4..a0f441eaeed4 100644
--- a/packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml
+++ b/packages/SystemUI/animation/res/anim/launch_dialog_exit.xml
diff --git a/packages/SystemUI/animation/core/res/values/ids.xml b/packages/SystemUI/animation/res/values/ids.xml
index 2d82307aca76..2d82307aca76 100644
--- a/packages/SystemUI/animation/core/res/values/ids.xml
+++ b/packages/SystemUI/animation/res/values/ids.xml
diff --git a/packages/SystemUI/animation/core/res/values/styles.xml b/packages/SystemUI/animation/res/values/styles.xml
index 3b3f7f6128fa..3b3f7f6128fa 100644
--- a/packages/SystemUI/animation/core/res/values/styles.xml
+++ b/packages/SystemUI/animation/res/values/styles.xml
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 9c46ebdc5ac8..9c46ebdc5ac8 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt
index 1c9dabbb0e07..1c9dabbb0e07 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
index b879ba0b1ece..b879ba0b1ece 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 168039ed5d3d..168039ed5d3d 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index c49a487c6766..c49a487c6766 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index addabcc0282a..addabcc0282a 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
index 78ae4af258fc..78ae4af258fc 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index b738e2bc972b..b738e2bc972b 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index d6eba2e7064d..d6eba2e7064d 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index ed8e70568b48..ed8e70568b48 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
index d465962d6edf..d465962d6edf 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt
index b89a8b0e0272..b89a8b0e0272 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index dc54240affc3..8dc74951d332 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -42,9 +42,9 @@ interface TypefaceVariantCache {
return baseTypeface
}
- val axes =
- FontVariationAxis.fromFontVariationSettings(fVar)?.toMutableList()
- ?: mutableListOf()
+ val axes = FontVariationAxis.fromFontVariationSettings(fVar)
+ ?.toMutableList()
+ ?: mutableListOf()
axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) }
if (axes.isEmpty()) {
return baseTypeface
@@ -206,14 +206,12 @@ class TextAnimator(
*
* Here is an example of font runs: "fin. 終わり"
*
- * ```
* Characters : f i n . _ 終 わ り
* Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A
* Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf -->
* runStart = 0, runLength = 5 runStart = 5, runLength = 3
* Glyph IDs : 194 48 7 8 4367 1039 1002
* Glyph Index: 0 1 2 3 0 1 2
- * ```
*
* In this example, the "fi" is converted into ligature form, thus the single glyph ID is
* assigned for two characters, f and i.
@@ -245,21 +243,22 @@ class TextAnimator(
/**
* Set text style with animation.
*
- * By passing -1 to weight, the view preserve the current weight. By passing -1 to textSize, the
- * view preserve the current text size. Bu passing -1 to duration, the default text animation,
- * 1000ms, is used. By passing false to animate, the text will be updated without animation.
+ * By passing -1 to weight, the view preserve the current weight.
+ * By passing -1 to textSize, the view preserve the current text size.
+ * Bu passing -1 to duration, the default text animation, 1000ms, is used.
+ * By passing false to animate, the text will be updated without animation.
*
* @param fvar an optional text fontVariationSettings.
* @param textSize an optional font size.
- * @param colors an optional colors array that must be the same size as numLines passed to the
- * TextInterpolator
+ * @param colors an optional colors array that must be the same size as numLines passed to
+ * the TextInterpolator
* @param strokeWidth an optional paint stroke width
* @param animate an optional boolean indicating true for showing style transition as animation,
- * false for immediate style transition. True by default.
+ * false for immediate style transition. True by default.
* @param duration an optional animation duration in milliseconds. This is ignored if animate is
- * false.
+ * false.
* @param interpolator an optional time interpolator. If null is passed, last set interpolator
- * will be used. This is ignored if animate is false.
+ * will be used. This is ignored if animate is false.
*/
fun setTextStyle(
fvar: String? = "",
@@ -325,8 +324,7 @@ class TextAnimator(
}
/**
- * Set text style with animation. Similar as fun setTextStyle( fvar: String? = "", textSize:
- * ```
+ * Set text style with animation. Similar as
* fun setTextStyle(
* fvar: String? = "",
* textSize: Float = -1f,
@@ -338,7 +336,6 @@ class TextAnimator(
* delay: Long = 0,
* onAnimationEnd: Runnable? = null
* )
- * ```
*
* @param weight an optional style value for `wght` in fontVariationSettings.
* @param width an optional style value for `wdth` in fontVariationSettings.
@@ -359,13 +356,12 @@ class TextAnimator(
delay: Long = 0,
onAnimationEnd: Runnable? = null
) {
- val fvar =
- fontVariationUtils.updateFontVariation(
- weight = weight,
- width = width,
- opticalSize = opticalSize,
- roundness = roundness,
- )
+ val fvar = fontVariationUtils.updateFontVariation(
+ weight = weight,
+ width = width,
+ opticalSize = opticalSize,
+ roundness = roundness,
+ )
setTextStyle(
fvar = fvar,
textSize = textSize,
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 02caeeddd774..02caeeddd774 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 1290f0097536..1290f0097536 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index a9c53d190cea..00d905652987 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -196,9 +196,9 @@ class ViewHierarchyAnimator {
*
* @param includeFadeIn true if the animator should also fade in the view and child views.
* @param fadeInInterpolator the interpolator to use when fading in the view. Unused if
- * [includeFadeIn] is false.
- * @param onAnimationEnd an optional runnable that will be run once the animation finishes
- * successfully. Will not be run if the animation is cancelled.
+ * [includeFadeIn] is false.
+ * @param onAnimationEnd an optional runnable that will be run once the animation
+ * finishes successfully. Will not be run if the animation is cancelled.
*/
@JvmOverloads
fun animateAddition(
@@ -241,10 +241,7 @@ class ViewHierarchyAnimator {
// First, fade in the container view
val containerDuration = duration / 6
createAndStartFadeInAnimator(
- rootView,
- containerDuration,
- startDelay = 0,
- interpolator = fadeInInterpolator
+ rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator
)
// Then, fade in the child views
@@ -400,7 +397,7 @@ class ViewHierarchyAnimator {
* included by specifying [includeMargins].
*
* @param onAnimationEnd an optional runnable that will be run once the animation finishes
- * successfully. Will not be run if the animation is cancelled.
+ * successfully. Will not be run if the animation is cancelled.
*/
@JvmOverloads
fun animateRemoval(
@@ -619,7 +616,6 @@ class ViewHierarchyAnimator {
* not newly introduced margins are included.
*
* Base case
- *
* ```
* 1) origin=TOP
* x---------x x---------x x---------x x---------x x---------x
@@ -640,11 +636,9 @@ class ViewHierarchyAnimator {
* x-----x x-------x | |
* x---------x
* ```
- *
* In case the start and end values differ in the direction of the origin, and
* [ignorePreviousValues] is false, the previous values are used and a translation is
* included in addition to the view expansion.
- *
* ```
* origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70)
* x
@@ -783,7 +777,8 @@ class ViewHierarchyAnimator {
includeMargins: Boolean = false,
): Map<Bound, Int> {
val marginAdjustment =
- if (includeMargins && (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
+ if (includeMargins &&
+ (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams
DimenHolder(
left = marginLp.leftMargin,
@@ -791,9 +786,9 @@ class ViewHierarchyAnimator {
right = marginLp.rightMargin,
bottom = marginLp.bottomMargin
)
- } else {
- DimenHolder(0, 0, 0, 0)
- }
+ } else {
+ DimenHolder(0, 0, 0, 0)
+ }
// These are the end values to use *if* this bound is part of the destination.
val endLeft = left - marginAdjustment.left
@@ -810,69 +805,60 @@ class ViewHierarchyAnimator {
// - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight.
return when (destination) {
- Hotspot.TOP ->
- mapOf(
- Bound.TOP to endTop,
- Bound.BOTTOM to endTop,
- Bound.LEFT to left,
- Bound.RIGHT to right,
- )
- Hotspot.TOP_RIGHT ->
- mapOf(
- Bound.TOP to endTop,
- Bound.BOTTOM to endTop,
- Bound.RIGHT to endRight,
- Bound.LEFT to endRight,
- )
- Hotspot.RIGHT ->
- mapOf(
- Bound.RIGHT to endRight,
- Bound.LEFT to endRight,
- Bound.TOP to top,
- Bound.BOTTOM to bottom,
- )
- Hotspot.BOTTOM_RIGHT ->
- mapOf(
- Bound.BOTTOM to endBottom,
- Bound.TOP to endBottom,
- Bound.RIGHT to endRight,
- Bound.LEFT to endRight,
- )
- Hotspot.BOTTOM ->
- mapOf(
- Bound.BOTTOM to endBottom,
- Bound.TOP to endBottom,
- Bound.LEFT to left,
- Bound.RIGHT to right,
- )
- Hotspot.BOTTOM_LEFT ->
- mapOf(
- Bound.BOTTOM to endBottom,
- Bound.TOP to endBottom,
- Bound.LEFT to endLeft,
- Bound.RIGHT to endLeft,
- )
- Hotspot.LEFT ->
- mapOf(
- Bound.LEFT to endLeft,
- Bound.RIGHT to endLeft,
- Bound.TOP to top,
- Bound.BOTTOM to bottom,
- )
- Hotspot.TOP_LEFT ->
- mapOf(
- Bound.TOP to endTop,
- Bound.BOTTOM to endTop,
- Bound.LEFT to endLeft,
- Bound.RIGHT to endLeft,
- )
- Hotspot.CENTER ->
- mapOf(
- Bound.LEFT to (endLeft + endRight) / 2,
- Bound.RIGHT to (endLeft + endRight) / 2,
- Bound.TOP to (endTop + endBottom) / 2,
- Bound.BOTTOM to (endTop + endBottom) / 2,
- )
+ Hotspot.TOP -> mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.LEFT to left,
+ Bound.RIGHT to right,
+ )
+ Hotspot.TOP_RIGHT -> mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ )
+ Hotspot.RIGHT -> mapOf(
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ Bound.TOP to top,
+ Bound.BOTTOM to bottom,
+ )
+ Hotspot.BOTTOM_RIGHT -> mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ )
+ Hotspot.BOTTOM -> mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.LEFT to left,
+ Bound.RIGHT to right,
+ )
+ Hotspot.BOTTOM_LEFT -> mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ )
+ Hotspot.LEFT -> mapOf(
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ Bound.TOP to top,
+ Bound.BOTTOM to bottom,
+ )
+ Hotspot.TOP_LEFT -> mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ )
+ Hotspot.CENTER -> mapOf(
+ Bound.LEFT to (endLeft + endRight) / 2,
+ Bound.RIGHT to (endLeft + endRight) / 2,
+ Bound.TOP to (endTop + endBottom) / 2,
+ Bound.BOTTOM to (endTop + endBottom) / 2,
+ )
}
}
@@ -1097,13 +1083,11 @@ class ViewHierarchyAnimator {
animator.startDelay = startDelay
animator.duration = duration
animator.interpolator = interpolator
- animator.addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- view.setTag(R.id.tag_alpha_animator, null /* tag */)
- }
+ animator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ view.setTag(R.id.tag_alpha_animator, null /* tag */)
}
- )
+ })
(view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel()
view.setTag(R.id.tag_alpha_animator, animator)
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
index e4f6db57f689..e4f6db57f689 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
index dd32851a97cf..dd32851a97cf 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
index c6b70736bc67..c6b70736bc67 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt
index 49d1fb423d2b..49d1fb423d2b 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
index 8740d1467296..8740d1467296 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
index 7538f188fb81..7538f188fb81 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt
index e42b589f05cf..e42b589f05cf 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
index bce262291f87..bce262291f87 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt
index 147669528c5e..147669528c5e 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
index d8e17c9c8204..d8e17c9c8204 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
index 6c175ddf1ea4..6c175ddf1ea4 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index d4372507e2c4..d4372507e2c4 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
index 91c0a5b635c9..91c0a5b635c9 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index 7e56f4b3d2b2..7e56f4b3d2b2 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index b89912709576..b89912709576 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
index c94fad7246fa..c94fad7246fa 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
index df07856e325e..df07856e325e 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
index 78898932249b..78898932249b 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
index 23fcb691ddb4..23fcb691ddb4 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
index 2cd587ffbc45..2cd587ffbc45 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
index b8f4b276dd6a..b8f4b276dd6a 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index d3c57c91405a..d3c57c91405a 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
index 43d6504fce84..43d6504fce84 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt b/packages/SystemUI/animation/src/com/android/systemui/util/AnimatorExtensions.kt
index 35dbb89ad801..35dbb89ad801 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/util/AnimatorExtensions.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
index 0f63548b6f0c..0f63548b6f0c 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
diff --git a/packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt
index 4bc9972dd50f..4bc9972dd50f 100644
--- a/packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 3fbcf6d77f82..15190214e06e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -24,6 +24,7 @@ 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.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
@@ -90,8 +91,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
.thenReturn(mock(ImageView::class.java))
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
val fakeFeatureFlags = FakeFeatureFlags()
- fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
+ mSetFlagsRule.enableFlags(AconfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
keyguardPasswordViewController =
KeyguardPasswordViewController(
keyguardPasswordView,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 74c922561343..e2bdc49d590c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -24,6 +24,7 @@ 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.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
@@ -75,8 +76,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
private lateinit var mKeyguardMessageAreaControllerFactory:
KeyguardMessageAreaController.Factory
- @Mock
- private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock
private lateinit var mKeyguardMessageAreaController:
@@ -95,7 +95,6 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
whenever(mKeyguardMessageAreaControllerFactory.create(any()))
.thenReturn(mKeyguardMessageAreaController)
fakeFeatureFlags = FakeFeatureFlags()
- fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
mKeyguardPatternView =
View.inflate(mContext, R.layout.keyguard_pattern_view, null) as KeyguardPatternView
@@ -166,7 +165,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
@Test
fun withFeatureFlagOn_oldMessage_isHidden() {
- fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
mKeyguardPatternViewController.onViewAttached()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index d41c2497b230..e89316997fb2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -35,7 +35,6 @@ 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;
@@ -103,8 +102,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
when(mPinBasedInputView.getResources()).thenReturn(getContext().getResources());
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
- featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
-
+ mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index f1701356c134..1fc2843b1cb8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -36,6 +36,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
import com.android.systemui.biometrics.SideFpsController
@@ -196,12 +197,12 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
keyguardPasswordViewController =
KeyguardPasswordViewController(
keyguardPasswordView,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 84d735430edd..cbcca55f6a34 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -24,10 +24,10 @@ 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.Flags
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
@@ -81,8 +81,8 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
as KeyguardSimPinView
val fakeFeatureFlags = FakeFeatureFlags()
- fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
underTest =
KeyguardSimPinViewController(
simPinView,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 7b1f302da6e8..45a60199984b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -24,10 +24,10 @@ 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.Flags
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
@@ -75,8 +75,7 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null)
as KeyguardSimPukView
val fakeFeatureFlags = FakeFeatureFlags()
- fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
-
+ mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
underTest =
KeyguardSimPukViewController(
simPukView,
diff --git a/packages/SystemUI/shared/res/values/ids.xml b/packages/SystemUI/shared/res/values/ids.xml
new file mode 100644
index 000000000000..1ff2f0eff215
--- /dev/null
+++ b/packages/SystemUI/shared/res/values/ids.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <!-- ID of the smartspace card view. -->
+ <item type="id" name="bc_smartspace_view" />
+ <!-- ID of the smartspace date view. -->
+ <item type="id" name="date_smartspace_view" />
+ <!-- ID of the smartspace weather view. -->
+ <item type="id" name="weather_smartspace_view" />
+</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index 1ee58deb501c..5e76801aaca3 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -41,6 +41,7 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.settings.SecureSettings
import java.io.PrintWriter
import javax.inject.Inject
+import dagger.Lazy
/**
* Handles active unlock settings changes.
@@ -51,6 +52,7 @@ class ActiveUnlockConfig @Inject constructor(
private val secureSettings: SecureSettings,
private val contentResolver: ContentResolver,
private val selectedUserInteractor: SelectedUserInteractor,
+ private val keyguardUpdateMonitor: Lazy<KeyguardUpdateMonitor>,
dumpManager: DumpManager
) : Dumpable {
@@ -96,7 +98,6 @@ class ActiveUnlockConfig @Inject constructor(
UNDER_DISPLAY_FINGERPRINT(3),
}
- var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null
private var requestActiveUnlockOnWakeup = false
private var requestActiveUnlockOnUnlockIntent = false
private var requestActiveUnlockOnBioFail = false
@@ -316,7 +317,7 @@ class ActiveUnlockConfig @Inject constructor(
return false
}
- keyguardUpdateMonitor?.let {
+ keyguardUpdateMonitor.get().let {
val anyFaceEnrolled = it.isFaceEnabledAndEnrolled
val anyFingerprintEnrolled = it.isUnlockWithFingerprintPossible(
selectedUserInteractor.getSelectedUserId())
@@ -369,13 +370,13 @@ class ActiveUnlockConfig @Inject constructor(
}")
pw.println("Current state:")
- keyguardUpdateMonitor?.let {
+ keyguardUpdateMonitor.get().let {
pw.println(" shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" +
"${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}")
pw.println(" isFaceEnabledAndEnrolled=${it.isFaceEnabledAndEnrolled}")
pw.println(" fpUnlockPossible=${
it.isUnlockWithFingerprintPossible(selectedUserInteractor.getSelectedUserId())}")
pw.println(" udfpsEnrolled=${it.isUdfpsEnrolled}")
- } ?: pw.println(" keyguardUpdateMonitor is uninitialized")
+ }
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 714fe64f1a8f..66f965aea76f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -30,13 +30,13 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.Flags;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.ui.BouncerMessageView;
import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder;
import com.android.systemui.classifier.FalsingCollector;
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;
@@ -108,7 +108,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
private void updateMessageAreaVisibility() {
if (mMessageAreaController == null) return;
- if (mFeatureFlags.isEnabled(Flags.REVAMPED_BOUNCER_MESSAGES)) {
+ if (Flags.revampedBouncerMessages()) {
mMessageAreaController.disable();
} else {
mMessageAreaController.setIsVisible(true);
@@ -182,15 +182,13 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
public void bindMessageView(
@NonNull BouncerMessageInteractor bouncerMessageInteractor,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- BouncerLogger bouncerLogger,
- FeatureFlags featureFlags) {
+ BouncerLogger bouncerLogger) {
BouncerMessageView bouncerMessageView = (BouncerMessageView) mView.getBouncerMessageView();
if (bouncerMessageView != null) {
BouncerMessageViewBinder.bind(bouncerMessageView,
bouncerMessageInteractor,
messageAreaControllerFactory,
- bouncerLogger,
- featureFlags);
+ bouncerLogger);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index cce2018f733f..5e35e7764dd8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -31,7 +31,6 @@ import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPin;
import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPuk;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
-import static com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
@@ -1164,7 +1163,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mLockPatternUtils.reportFailedPasswordAttempt(userId);
if (timeoutMs > 0) {
mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
- if (!mFeatureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) {
+ if (!com.android.systemui.Flags.revampedBouncerMessages()) {
mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils,
mSecurityModel.getSecurityMode(userId));
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9c61a8a3cd8b..f3cd01f908da 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -111,6 +111,7 @@ import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.settingslib.Utils;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.CoreStartable;
import com.android.systemui.Dumpable;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -175,7 +176,7 @@ import javax.inject.Provider;
* to be updated.
*/
@SysUISingleton
-public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable {
+public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable, CoreStartable {
private static final String TAG = "KeyguardUpdateMonitor";
private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
@@ -347,6 +348,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final LatencyTracker mLatencyTracker;
private final StatusBarStateController mStatusBarStateController;
private final Executor mBackgroundExecutor;
+ private final Executor mMainExecutor;
private final SensorPrivacyManager mSensorPrivacyManager;
private final ActiveUnlockConfig mActiveUnlockConfig;
private final IDreamManager mDreamManager;
@@ -354,7 +356,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Nullable
private final FingerprintManager mFpm;
@Nullable
+ private final BiometricManager mBiometricManager;
+ @Nullable
private KeyguardFaceAuthInteractor mFaceAuthInteractor;
+ private final DevicePostureController mDevicePostureController;
private final TaskStackChangeListeners mTaskStackChangeListeners;
private final IActivityTaskManager mActivityTaskManager;
private final SelectedUserInteractor mSelectedUserInteractor;
@@ -370,7 +375,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean mIsDreaming;
private boolean mLogoutEnabled;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
+ private final FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
/**
* Short delay before restarting fingerprint authentication after a successful try. This should
@@ -2105,6 +2110,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
mStrongAuthTracker = new StrongAuthTracker(context);
mBackgroundExecutor = backgroundExecutor;
+ mMainExecutor = mainExecutor;
mBroadcastDispatcher = broadcastDispatcher;
mInteractionJankMonitor = interactionJankMonitor;
mLatencyTracker = latencyTracker;
@@ -2126,7 +2132,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mDevicePolicyManager = devicePolicyManager;
mPackageManager = packageManager;
mFpm = fingerprintManager;
- mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
+ mBiometricManager = biometricManager;
mConfigFaceAuthSupportedPosture = mContext.getResources().getInteger(
R.integer.config_face_auth_supported_posture);
mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig;
@@ -2134,10 +2140,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mContext.getResources().getStringArray(
R.array.config_fingerprint_listen_on_occluding_activity_packages))
.collect(Collectors.toSet());
+ mDevicePostureController = devicePostureController;
mTaskStackChangeListeners = taskStackChangeListeners;
mActivityTaskManager = activityTaskManagerService;
mSelectedUserInteractor = selectedUserInteractor;
+ mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null);
+ mIsSystemUser = mUserManager.isSystemUser();
+
mHandler = new Handler(mainLooper) {
@Override
public void handleMessage(Message msg) {
@@ -2252,6 +2262,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
};
+ mTimeFormatChangeObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mHandler.sendMessage(mHandler.obtainMessage(
+ MSG_TIME_FORMAT_UPDATE,
+ Settings.System.getString(
+ mContext.getContentResolver(),
+ Settings.System.TIME_12_24)));
+ }
+ };
+ }
+
+ @Override
+ public void start() {
// Since device can't be un-provisioned, we only need to register a content observer
// to update mDeviceProvisioned when we are...
if (!mDeviceProvisioned) {
@@ -2297,7 +2321,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mHandler, UserHandle.ALL);
mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
- mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
mTrustManager.registerTrustListener(this);
@@ -2318,8 +2342,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
}
- if (biometricManager != null) {
- biometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
+ if (mBiometricManager != null) {
+ mBiometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
}
// in case authenticators aren't registered yet at this point:
@@ -2327,7 +2351,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onAllAuthenticatorsRegistered(
@BiometricAuthenticator.Modality int modality) {
- mainExecutor.execute(
+ mMainExecutor.execute(
() -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE));
}
@@ -2335,7 +2359,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
mHandler.obtainMessage(MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED, modality, 0)
.sendToTarget();
- mainExecutor.execute(
+ mMainExecutor.execute(
() -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE));
}
@@ -2353,12 +2377,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
});
if (mConfigFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
- devicePostureController.addCallback(mPostureCallback);
+ mDevicePostureController.addCallback(mPostureCallback);
}
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
- mIsSystemUser = mUserManager.isSystemUser();
int user = mSelectedUserInteractor.getSelectedUserId(true);
mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
@@ -2377,22 +2400,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
initializeSimState();
- mTimeFormatChangeObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mHandler.sendMessage(mHandler.obtainMessage(
- MSG_TIME_FORMAT_UPDATE,
- Settings.System.getString(
- mContext.getContentResolver(),
- Settings.System.TIME_12_24)));
- }
- };
-
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.TIME_12_24),
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
-
- mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null);
}
private void initializeSimState() {
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index 7295936ddc7c..4c9782cdf36c 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -33,6 +33,9 @@ import java.io.PrintWriter;
* abstract fun bind(impl: FoobarStartable): CoreStartable
* </pre>
*
+ * If your CoreStartable depends on different CoreStartables starting before it, use a
+ * {@link com.android.systemui.startable.Dependencies} annotation to list out those dependencies.
+ *
* @see SystemUIApplication#startServicesIfNeeded()
*/
public interface CoreStartable extends Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 01f6971de373..8aae20651a10 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -16,6 +16,7 @@
package com.android.systemui;
+import android.annotation.SuppressLint;
import android.app.ActivityThread;
import android.app.Application;
import android.app.Notification;
@@ -26,27 +27,33 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.os.Looper;
import android.os.Process;
-import android.os.SystemProperties;
import android.os.Trace;
-import android.os.UserHandle;
import android.util.Log;
import android.util.TimingsTraceLog;
import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.protolog.common.ProtoLog;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.res.R;
+import com.android.systemui.startable.Dependencies;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.NotificationChannels;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayDeque;
+import java.util.Arrays;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.Map;
+import java.util.StringJoiner;
import java.util.TreeMap;
import javax.inject.Provider;
@@ -78,10 +85,17 @@ public class SystemUIApplication extends Application implements
ProtoLog.REQUIRE_PROTOLOGTOOL = false;
}
+ @VisibleForTesting
+ @Override
+ public void attachBaseContext(Context base) {
+ super.attachBaseContext(base);
+ }
+
protected GlobalRootComponent getRootComponent() {
return mInitializer.getRootComponent();
}
+ @SuppressLint("RegisterReceiverViaContext")
@Override
public void onCreate() {
super.onCreate();
@@ -96,9 +110,11 @@ public class SystemUIApplication extends Application implements
mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
log.traceEnd();
+ GlobalRootComponent rootComponent = mInitializer.getRootComponent();
+
// Enable Looper trace points.
// This allows us to see Handler callbacks on traces.
- Looper.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP);
+ rootComponent.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP);
// Set the application theme that is inherited by all services. Note that setting the
// application theme in the manifest does only work for activities. Keep this in sync with
@@ -106,15 +122,17 @@ public class SystemUIApplication extends Application implements
setTheme(R.style.Theme_SystemUI);
View.setTraceLayoutSteps(
- SystemProperties.getBoolean("persist.debug.trace_layouts", false));
+ rootComponent.getSystemPropertiesHelper()
+ .getBoolean("persist.debug.trace_layouts", false));
View.setTracedRequestLayoutClassClass(
- SystemProperties.get("persist.debug.trace_request_layout_class", null));
+ rootComponent.getSystemPropertiesHelper()
+ .get("persist.debug.trace_request_layout_class", null));
if (Flags.enableLayoutTracing()) {
View.setTraceLayoutSteps(true);
}
- if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
+ if (rootComponent.getProcessWrapper().isSystemUser()) {
IntentFilter bootCompletedFilter = new
IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
@@ -175,8 +193,8 @@ public class SystemUIApplication extends Application implements
}
/**
- * Makes sure that all the SystemUI services are running. If they are already running, this is a
- * no-op. This is needed to conditinally start all the services, as we only need to have it in
+ * Makes sure that all the CoreStartables are running. If they are already running, this is a
+ * no-op. This is needed to conditionally start all the services, as we only need to have it in
* the main process.
* <p>This method must only be called from the main thread.</p>
*/
@@ -221,7 +239,8 @@ public class SystemUIApplication extends Application implements
if (!mBootCompleteCache.isBootComplete()) {
// check to see if maybe it was already completed long before we began
// see ActivityManagerService.finishBooting()
- if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
+ if ("1".equals(getRootComponent().getSystemPropertiesHelper()
+ .get("sys.boot_completed"))) {
mBootCompleteCache.setBootComplete();
if (DEBUG) {
Log.v(TAG, "BOOT_COMPLETED was already sent");
@@ -237,17 +256,78 @@ public class SystemUIApplication extends Application implements
Trace.TRACE_TAG_APP);
log.traceBegin(metricsPrefix);
- int i = 0;
- for (Map.Entry<Class<?>, Provider<CoreStartable>> entry : startables.entrySet()) {
- String clsName = entry.getKey().getName();
- int j = i; // Copied to make lambda happy.
- timeInitialization(
- clsName,
- () -> mServices[j] = startStartable(clsName, entry.getValue()),
- log,
- metricsPrefix);
- i++;
+ HashSet<Class<?>> startedStartables = new HashSet<>();
+
+ // Perform a form of topological sort:
+ // 1) Iterate through a queue of all non-started startables
+ // If the startable has all of its dependencies met
+ // - start it
+ // Else
+ // - enqueue it for the next iteration
+ // 2) If anything was started and the "next" queue is not empty, loop back to 1
+ // 3) If we're done looping and there are any non-started startables left, throw an error.
+ //
+ // This "sort" is not very optimized. We assume that most CoreStartables don't have many
+ // dependencies - zero in fact. We assume two or three iterations of this loop will be
+ // enough. If that ever changes, it may be worth revisiting.
+
+ log.traceBegin("Topologically start Core Startables");
+ boolean startedAny = false;
+ ArrayDeque<Map.Entry<Class<?>, Provider<CoreStartable>>> queue;
+ ArrayDeque<Map.Entry<Class<?>, Provider<CoreStartable>>> nextQueue =
+ new ArrayDeque<>(startables.entrySet());
+ int numIterations = 0;
+
+ int serviceIndex = 0;
+
+ do {
+ queue = nextQueue;
+ nextQueue = new ArrayDeque<>(startables.size());
+
+ while (!queue.isEmpty()) {
+ Map.Entry<Class<?>, Provider<CoreStartable>> entry = queue.removeFirst();
+
+ Class<?> cls = entry.getKey();
+ Dependencies dep = cls.getAnnotation(Dependencies.class);
+ Class<? extends CoreStartable>[] deps = (dep == null ? null : dep.value());
+ if (deps == null || startedStartables.containsAll(Arrays.asList(deps))) {
+ String clsName = cls.getName();
+ int i = serviceIndex; // Copied to make lambda happy.
+ timeInitialization(
+ clsName,
+ () -> mServices[i] = startStartable(clsName, entry.getValue()),
+ log,
+ metricsPrefix);
+ startedStartables.add(cls);
+ startedAny = true;
+ serviceIndex++;
+ } else {
+ nextQueue.add(entry);
+ }
+ }
+ numIterations++;
+ } while (startedAny && !nextQueue.isEmpty()); // if none were started, stop.
+
+ if (!nextQueue.isEmpty()) { // If some startables were left over, throw an error.
+ while (!nextQueue.isEmpty()) {
+ Map.Entry<Class<?>, Provider<CoreStartable>> entry = nextQueue.removeFirst();
+ Class<?> cls = entry.getKey();
+ Dependencies dep = cls.getAnnotation(Dependencies.class);
+ Class<? extends CoreStartable>[] deps = (dep == null ? null : dep.value());
+ StringJoiner stringJoiner = new StringJoiner(", ");
+ for (int i = 0; deps != null && i < deps.length; i++) {
+ if (!startedStartables.contains(deps[i])) {
+ stringJoiner.add(deps[i].getName());
+ }
+ }
+ Log.e(TAG, "Failed to start " + cls.getName()
+ + ". Missing dependencies: [" + stringJoiner + "]");
+ }
+
+ throw new RuntimeException("Failed to start all CoreStartables. Check logcat!");
}
+ Log.i(TAG, "Topological CoreStartables completed in " + numIterations + " iterations");
+ log.traceEnd();
if (vendorComponent != null) {
timeInitialization(
@@ -258,8 +338,8 @@ public class SystemUIApplication extends Application implements
metricsPrefix);
}
- for (i = 0; i < mServices.length; i++) {
- final CoreStartable service = mServices[i];
+ for (serviceIndex = 0; serviceIndex < mServices.length; serviceIndex++) {
+ final CoreStartable service = mServices[serviceIndex];
if (mBootCompleteCache.isBootComplete()) {
notifyBootCompleted(service);
}
@@ -308,10 +388,14 @@ public class SystemUIApplication extends Application implements
Trace.TRACE_TAG_APP, clsName + ".newInstance()");
}
try {
- startable = (CoreStartable) Class.forName(clsName).newInstance();
+ startable = (CoreStartable) Class.forName(clsName)
+ .getDeclaredConstructor()
+ .newInstance();
} catch (ClassNotFoundException
- | IllegalAccessException
- | InstantiationException ex) {
+ | IllegalAccessException
+ | InstantiationException
+ | NoSuchMethodException
+ | InvocationTargetException ex) {
throw new RuntimeException(ex);
} finally {
Trace.endSection();
@@ -344,7 +428,7 @@ public class SystemUIApplication extends Application implements
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
if (mServicesStarted) {
ConfigurationController configController = mSysUIComponent.getConfigurationController();
if (Trace.isEnabled()) {
@@ -363,7 +447,7 @@ public class SystemUIApplication extends Application implements
@Override
public void setContextAvailableCallback(
- SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) {
+ @NonNull SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) {
mContextAvailableCallback = callback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 83d415fa936e..ab23564a1df4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -403,13 +403,6 @@ public class AuthContainerView extends LinearLayout
final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
R.layout.biometric_prompt_layout, null, false);
- /**
- * View is only set visible in BiometricViewSizeBinder once PromptSize is determined
- * that accounts for iconView size, to prevent prompt resizing being visible to the
- * user.
- * TODO(b/288175072): May be able to remove this once constraint layout is implemented
- */
- view.setVisibility(View.INVISIBLE);
mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
// TODO(b/201510778): This uses the wrong timeout in some cases
getJankListener(view, TRANSIT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index a7fb6f72a41e..90e4a3821634 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -97,13 +97,7 @@ object BiometricViewBinder {
val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
- /**
- * View is only set visible in BiometricViewSizeBinder once PromptSize is determined that
- * accounts for iconView size, to prevent prompt resizing being visible to the user.
- *
- * TODO(b/288175072): May be able to remove this once constraint layout is implemented
- */
- iconView.addLottieOnCompositionLoadedListener { viewModel.setIsIconViewLoaded(true) }
+
PromptIconViewBinder.bind(
iconView,
iconOverlayView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index f340bd81f951..7e16d1e1d668 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -30,6 +30,7 @@ import androidx.core.animation.addListener
import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
+import com.android.systemui.res.R
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.ui.BiometricPromptLayout
@@ -40,8 +41,6 @@ import com.android.systemui.biometrics.ui.viewmodel.isMedium
import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
import com.android.systemui.biometrics.ui.viewmodel.isSmall
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.res.R
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -93,22 +92,8 @@ object BiometricViewSizeBinder {
// TODO(b/251476085): migrate the legacy panel controller and simplify this
view.repeatWhenAttached {
var currentSize: PromptSize? = null
-
lifecycleScope.launch {
- /**
- * View is only set visible in BiometricViewSizeBinder once PromptSize is
- * determined that accounts for iconView size, to prevent prompt resizing being
- * visible to the user.
- *
- * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
- * layout is implemented
- */
- combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
- (isIconViewLoaded, size) ->
- if (!isIconViewLoaded) {
- return@collect
- }
-
+ viewModel.size.collect { size ->
// prepare for animated size transitions
for (v in viewsToHideWhenSmall) {
v.showTextOrHide(forceHide = size.isSmall)
@@ -211,9 +196,8 @@ object BiometricViewSizeBinder {
}
}
}
+
currentSize = size
- view.visibility = View.VISIBLE
- viewModel.setIsIconViewLoaded(false)
notifyAccessibilityChanged()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index d899827ebb2e..6d0a58e202bd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -192,28 +192,6 @@ constructor(
val iconViewModel: PromptIconViewModel =
PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
- private val _isIconViewLoaded = MutableStateFlow(false)
-
- /**
- * For prompts with an iconView, false until the prompt's iconView animation has been loaded in
- * the view, otherwise true by default. Used for BiometricViewSizeBinder to wait for the icon
- * asset to be loaded before determining the prompt size.
- */
- val isIconViewLoaded: Flow<Boolean> =
- combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
- ->
- if (credentialKind is PromptKind.Biometric) {
- isIconViewLoaded
- } else {
- true
- }
- }
-
- // Sets whether the prompt's iconView animation has been loaded in the view yet.
- fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
- _isIconViewLoaded.value = iconViewLoaded
- }
-
/** Padding for prompt UI elements */
val promptPadding: Flow<Rect> =
combine(size, displayStateInteractor.currentRotation) { size, rotation ->
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index f612f9afed77..b587ed846a38 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -22,6 +22,7 @@ import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
@@ -29,8 +30,6 @@ import com.android.systemui.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.bouncer.shared.model.Message
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.REVAMPED_BOUNCER_MESSAGES
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
@@ -100,7 +99,6 @@ constructor(
private val repository: BouncerMessageRepository,
private val userRepository: UserRepository,
private val countDownTimerUtil: CountDownTimerUtil,
- private val featureFlags: FeatureFlags,
private val updateMonitor: KeyguardUpdateMonitor,
trustRepository: TrustRepository,
biometricSettingsRepository: BiometricSettingsRepository,
@@ -229,7 +227,7 @@ constructor(
}
fun onPrimaryAuthLockedOut(secondsBeforeLockoutReset: Long) {
- if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+ if (!Flags.revampedBouncerMessages()) return
val callback =
object : CountDownTimerCallback {
@@ -250,7 +248,7 @@ constructor(
}
fun onPrimaryAuthIncorrectAttempt() {
- if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+ if (!Flags.revampedBouncerMessages()) return
repository.setMessage(
incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
@@ -258,21 +256,21 @@ constructor(
}
fun setFingerprintAcquisitionMessage(value: String?) {
- if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+ if (!Flags.revampedBouncerMessages()) return
repository.setMessage(
defaultMessage(currentSecurityMode, value, isFingerprintAuthCurrentlyAllowed.value)
)
}
fun setFaceAcquisitionMessage(value: String?) {
- if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+ if (!Flags.revampedBouncerMessages()) return
repository.setMessage(
defaultMessage(currentSecurityMode, value, isFingerprintAuthCurrentlyAllowed.value)
)
}
fun setCustomMessage(value: String?) {
- if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+ if (!Flags.revampedBouncerMessages()) return
repository.setMessage(
defaultMessage(currentSecurityMode, value, isFingerprintAuthCurrentlyAllowed.value)
@@ -283,7 +281,7 @@ constructor(
get() = defaultMessage(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
fun onPrimaryBouncerUserInput() {
- if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+ if (!Flags.revampedBouncerMessages()) return
repository.setMessage(defaultMessage)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt
index 5a59012a74b8..b8e6ad6956fe 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt
@@ -23,11 +23,10 @@ import androidx.lifecycle.repeatOnLifecycle
import com.android.keyguard.BouncerKeyguardMessageArea
import com.android.keyguard.KeyguardMessageArea
import com.android.keyguard.KeyguardMessageAreaController
+import com.android.systemui.Flags
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
import com.android.systemui.bouncer.shared.model.Message
import com.android.systemui.bouncer.ui.BouncerMessageView
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.BouncerLogger
import kotlinx.coroutines.launch
@@ -39,10 +38,9 @@ object BouncerMessageViewBinder {
interactor: BouncerMessageInteractor,
factory: KeyguardMessageAreaController.Factory,
bouncerLogger: BouncerLogger,
- featureFlags: FeatureFlags
) {
view.repeatWhenAttached {
- if (!featureFlags.isEnabled(Flags.REVAMPED_BOUNCER_MESSAGES)) {
+ if (!Flags.revampedBouncerMessages()) {
view.primaryMessageView?.disable()
view.secondaryMessageView?.disable()
return@repeatWhenAttached
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 5dcd6615509d..cc387e98aa23 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
@@ -32,7 +32,6 @@ import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
import com.android.systemui.bouncer.ui.BouncerViewDelegate
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.BouncerLogger
@@ -53,7 +52,6 @@ object KeyguardBouncerViewBinder {
messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
bouncerMessageInteractor: BouncerMessageInteractor,
bouncerLogger: BouncerLogger,
- featureFlags: FeatureFlags,
selectedUserInteractor: SelectedUserInteractor,
) {
// Builds the KeyguardSecurityContainerController from bouncer view group.
@@ -141,8 +139,7 @@ object KeyguardBouncerViewBinder {
it.bindMessageView(
bouncerMessageInteractor,
messageAreaControllerFactory,
- bouncerLogger,
- featureFlags
+ bouncerLogger
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index 9e8c0ec7423e..e78ce3bbb0d1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -17,8 +17,12 @@
package com.android.systemui.dagger;
import android.content.Context;
+import android.os.Looper;
import com.android.systemui.dagger.qualifiers.InstrumentationTest;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.SystemPropertiesHelper;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.util.InitializationChecker;
import dagger.BindsInstance;
@@ -58,4 +62,20 @@ public interface GlobalRootComponent {
* Returns an {@link InitializationChecker}.
*/
InitializationChecker getInitializationChecker();
+
+ /**
+ * Returns the main looper for this process.
+ */
+ @Main
+ Looper getMainLooper();
+
+ /**
+ * Returns a {@link SystemPropertiesHelper}.
+ */
+ SystemPropertiesHelper getSystemPropertiesHelper();
+
+ /**
+ * Returns a {@link ProcessWrapper}
+ */
+ ProcessWrapper getProcessWrapper();
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 1b350055e8de..bb0c2733e511 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -208,10 +208,6 @@ object Flags {
val WALLPAPER_PICKER_GRID_APPLY_BUTTON =
unreleasedFlag("wallpaper_picker_grid_apply_button")
- /** Provide new auth messages on the bouncer. */
- // TODO(b/277961132): Tracking bug.
- @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag("revamped_bouncer_messages")
-
/** Keyguard Migration */
// TODO(b/297037052): Tracking bug.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 6c160975587b..6fa20de1fb7f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -17,19 +17,22 @@
package com.android.systemui.flags
import android.os.SystemProperties
-import com.android.systemui.dagger.SysUISingleton
-
import javax.inject.Inject
+import javax.inject.Singleton
/**
* Proxy to make {@link SystemProperties} easily testable.
*/
-@SysUISingleton
+@Singleton
open class SystemPropertiesHelper @Inject constructor() {
fun get(name: String): String {
return SystemProperties.get(name)
}
+ fun get(name: String, def: String?): String {
+ return SystemProperties.get(name, def)
+ }
+
fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
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 3925dd1620b4..13e38358477a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -34,6 +34,7 @@ import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.keyguard.mediator.ScreenOnCoordinator;
+import com.android.systemui.CoreStartable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
@@ -79,9 +80,12 @@ import com.android.systemui.util.time.SystemClock;
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
+import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
import java.util.concurrent.Executor;
@@ -105,7 +109,7 @@ import kotlinx.coroutines.CoroutineDispatcher;
StartKeyguardTransitionModule.class,
ResourceTrimmerModule.class,
})
-public class KeyguardModule {
+public interface KeyguardModule {
/**
* Provides our instance of KeyguardViewMediator which is considered optional.
*/
@@ -207,13 +211,19 @@ public class KeyguardModule {
/** */
@Provides
- public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) {
+ static ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) {
return viewMediator.getViewMediatorCallback();
}
/** */
@Provides
- public KeyguardQuickAffordancesMetricsLogger providesKeyguardQuickAffordancesMetricsLogger() {
+ static KeyguardQuickAffordancesMetricsLogger providesKeyguardQuickAffordancesMetricsLogger() {
return new KeyguardQuickAffordancesMetricsLoggerImpl();
}
+
+ /** Binds {@link KeyguardUpdateMonitor} as a {@link CoreStartable}. */
+ @Binds
+ @IntoMap
+ @ClassKey(KeyguardUpdateMonitor.class)
+ CoreStartable bindsKeyguardUpdateMonitor(KeyguardUpdateMonitor keyguardUpdateMonitor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index e36819b54524..92270ad31664 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -28,6 +28,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
import kotlinx.coroutines.launch
object KeyguardSmartspaceViewBinder {
@@ -69,9 +70,10 @@ object KeyguardSmartspaceViewBinder {
smartspaceViewModel.isSmartspaceEnabled &&
smartspaceViewModel.isDateWeatherDecoupled
) {
- val dateView = constraintLayout.requireViewById<View>(smartspaceViewModel.dateId)
+ val dateView =
+ constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
val weatherView =
- constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId)
+ constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view)
addView(weatherView)
addView(dateView)
}
@@ -88,8 +90,10 @@ object KeyguardSmartspaceViewBinder {
smartspaceViewModel.isSmartspaceEnabled &&
smartspaceViewModel.isDateWeatherDecoupled
) {
- val dateView = smartspaceViewModel.dateView
- val weatherView = smartspaceViewModel.weatherView
+ val dateView =
+ constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
+ val weatherView =
+ constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view)
removeView(weatherView)
removeView(dateView)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index d89e1e41d1bc..1ccc6ccf2cec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -28,6 +28,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
import javax.inject.Inject
/** Adds a layer to group elements for translation for burn-in preventation */
@@ -87,7 +88,7 @@ constructor(
burnInLayer.apply {
if (smartspaceViewModel.isSmartspaceEnabled) {
val smartspaceView =
- constraintLayout.requireViewById<View>(smartspaceViewModel.smartspaceViewId)
+ constraintLayout.requireViewById<View>(sharedR.id.bc_smartspace_view)
addView(smartspaceView)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index b429ab4fac0a..f560b5f068c9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -32,6 +32,7 @@ import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -117,7 +118,7 @@ constructor(
}
constraintSet.apply {
if (migrateClocksToBlueprint()) {
- connect(nicId, TOP, smartspaceViewModel.smartspaceViewId, BOTTOM, bottomMargin)
+ connect(nicId, TOP, sharedR.id.bc_smartspace_view, BOTTOM, bottomMargin)
setGoneMargin(nicId, BOTTOM, bottomMargin)
} else {
connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 656c75c7bfec..b5f32c8a5608 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -32,7 +32,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.res.R
@@ -55,7 +54,6 @@ open class ClockSection
constructor(
private val clockInteractor: KeyguardClockInteractor,
protected val keyguardClockViewModel: KeyguardClockViewModel,
- private val smartspaceViewModel: KeyguardSmartspaceViewModel,
private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
) : KeyguardSection() {
@@ -119,8 +117,8 @@ constructor(
com.android.systemui.customization.R.dimen.small_clock_padding_top
) +
context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
- largeClockTopMargin += smartspaceViewModel.getDimen(DATE_WEATHER_VIEW_HEIGHT)
- largeClockTopMargin += smartspaceViewModel.getDimen(ENHANCED_SMARTSPACE_HEIGHT)
+ largeClockTopMargin += getDimen(DATE_WEATHER_VIEW_HEIGHT)
+ largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT)
if (!keyguardClockViewModel.useLargeClock) {
largeClockTopMargin -=
context.resources.getDimensionPixelSize(
@@ -163,6 +161,12 @@ constructor(
}
}
+ private fun getDimen(name: String): Int {
+ val res = context.packageManager.getResourcesForApplication(context.packageName)
+ val id = res.getIdentifier(name, "dimen", context.packageName)
+ return res.getDimensionPixelSize(id)
+ }
+
companion object {
private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index e7b6e44450bd..b0eee0a68b1f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -31,6 +31,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -80,7 +81,7 @@ constructor(
connect(
R.id.nssl_placeholder,
TOP,
- smartspaceViewModel.smartspaceViewId,
+ sharedR.id.bc_smartspace_view,
BOTTOM,
bottomMargin
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 8cd51cd3b1e3..eacd466bc473 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
+import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -44,13 +45,9 @@ constructor(
val smartspaceController: LockscreenSmartspaceController,
val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
) : KeyguardSection() {
- var smartspaceView by keyguardSmartspaceViewModel::smartspaceView
- var weatherView by keyguardSmartspaceViewModel::weatherView
- var dateView by keyguardSmartspaceViewModel::dateView
-
- val smartspaceViewId = keyguardSmartspaceViewModel.smartspaceViewId
- val weatherViewId = keyguardSmartspaceViewModel.weatherId
- val dateViewId = keyguardSmartspaceViewModel.dateId
+ private var smartspaceView: View? = null
+ private var weatherView: View? = null
+ private var dateView: View? = null
override fun addViews(constraintLayout: ConstraintLayout) {
if (!migrateClocksToBlueprint()) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
index 1302bfa6fc31..19ba1aa4763a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
@@ -20,7 +20,6 @@ import android.content.Context
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
@@ -30,17 +29,9 @@ class SplitShadeClockSection
constructor(
clockInteractor: KeyguardClockInteractor,
keyguardClockViewModel: KeyguardClockViewModel,
- smartspaceViewModel: KeyguardSmartspaceViewModel,
context: Context,
splitShadeStateController: SplitShadeStateController,
-) :
- ClockSection(
- clockInteractor,
- keyguardClockViewModel,
- smartspaceViewModel,
- context,
- splitShadeStateController
- ) {
+) : ClockSection(clockInteractor, keyguardClockViewModel, context, splitShadeStateController) {
override fun applyDefaultConstraints(constraints: ConstraintSet) {
super.applyDefaultConstraints(constraints)
val largeClockEndGuideline =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index 5afdbaa47d95..f20ab06bcda9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -35,6 +35,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.media.controls.ui.KeyguardMediaController
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shared.R as sharedR
import javax.inject.Inject
/** Aligns media on left side for split shade, below smartspace, date, and weather. */
@@ -90,9 +91,9 @@ constructor(
Barrier.BOTTOM,
0,
*intArrayOf(
- keyguardSmartspaceViewModel.smartspaceViewId,
- keyguardSmartspaceViewModel.dateId,
- keyguardSmartspaceViewModel.weatherId,
+ sharedR.id.bc_smartspace_view,
+ sharedR.id.date_smartspace_view,
+ sharedR.id.weather_smartspace_view,
)
)
connect(mediaContainerId, TOP, smartSpaceBarrier, BOTTOM)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index 26e7ee8f561b..a1dd720a82f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -16,47 +16,66 @@
package com.android.systemui.keyguard.ui.viewmodel
-import android.content.Context
-import android.util.Log
-import android.view.View
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
@SysUISingleton
class KeyguardSmartspaceViewModel
@Inject
-constructor(val context: Context, val smartspaceController: LockscreenSmartspaceController) {
+constructor(
+ @Application applicationScope: CoroutineScope,
+ smartspaceController: LockscreenSmartspaceController,
+ keyguardClockViewModel: KeyguardClockViewModel,
+) {
+ /** Whether the smartspace section is available in the build. */
val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
+ /** Whether the weather area is available in the build. */
+ // TODO(b/317891876): this should be a Flow as the value can change over time.
val isWeatherEnabled: Boolean = smartspaceController.isWeatherEnabled()
+ /** Whether the data and weather areas are decoupled in the build. */
val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled()
- val smartspaceViewId: Int
- get() = getId("bc_smartspace_view")
- val dateId: Int
- get() = getId("date_smartspace_view")
+ /** Whether the date area should be visible. */
+ val isDateVisible: StateFlow<Boolean> =
+ keyguardClockViewModel.hasCustomWeatherDataDisplay
+ .map { !it }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = !keyguardClockViewModel.hasCustomWeatherDataDisplay.value,
+ )
- val weatherId: Int
- get() = getId("weather_smartspace_view")
-
- var smartspaceView: View? = null
- var weatherView: View? = null
- var dateView: View? = null
-
- private fun getId(name: String): Int {
- return context.resources.getIdentifier(name, "id", context.packageName).also {
- if (it == 0) {
- Log.d(TAG, "Cannot resolve id $name")
+ /** Whether the weather area should be visible. */
+ val isWeatherVisible: StateFlow<Boolean> =
+ keyguardClockViewModel.hasCustomWeatherDataDisplay
+ .map { clockIncludesCustomWeatherDisplay ->
+ isWeatherVisible(
+ clockIncludesCustomWeatherDisplay = clockIncludesCustomWeatherDisplay,
+ isWeatherEnabled = isWeatherEnabled,
+ )
}
- }
- }
- fun getDimen(name: String): Int {
- val res = context.packageManager.getResourcesForApplication(context.packageName)
- val id = res.getIdentifier(name, "dimen", context.packageName)
- return res.getDimensionPixelSize(id)
- }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ isWeatherVisible(
+ clockIncludesCustomWeatherDisplay =
+ keyguardClockViewModel.hasCustomWeatherDataDisplay.value,
+ isWeatherEnabled = isWeatherEnabled,
+ )
+ )
- companion object {
- private val TAG = KeyguardSmartspaceViewModel::class.java.simpleName
+ private fun isWeatherVisible(
+ clockIncludesCustomWeatherDisplay: Boolean,
+ isWeatherEnabled: Boolean,
+ ): Boolean {
+ return !clockIncludesCustomWeatherDisplay && isWeatherEnabled
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 3cf468f302b2..8162d587f4f7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -250,7 +250,6 @@ public class NotificationShadeWindowViewController implements Dumpable {
messageAreaControllerFactory,
bouncerMessageInteractor,
bouncerLogger,
- featureFlagsClassic,
selectedUserInteractor);
if (DeviceEntryUdfpsRefactor.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt b/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt
new file mode 100644
index 000000000000..5e57f1d1a11f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt
@@ -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 com.android.systemui.startable
+
+import com.android.systemui.CoreStartable
+import kotlin.reflect.KClass
+
+/**
+ * Allows a [CoreStartable] to declare that it must be started after its dependencies.
+ *
+ * This creates a partial, topological ordering. See [com.android.systemui.SystemUIApplication] for
+ * how this ordering is enforced at runtime.
+ */
+@MustBeDocumented
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class Dependencies(vararg val value: KClass<out CoreStartable> = [])
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 633510d9c438..c9df317508f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar;
+import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
+import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
@@ -22,6 +24,7 @@ import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_NULL;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+import static android.app.Flags.keyguardPrivateNotifications;
import static android.os.Flags.allowPrivateProfile;
import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -47,7 +50,6 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -149,6 +151,25 @@ public class NotificationLockscreenUserManagerImpl implements
new ListenerSet<>();
private final Collection<Uri> mLockScreenUris = new ArrayList<>();
+ protected final BroadcastReceiver mKeyguardReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+
+ if (ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED.equals(action)) {
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ mKeyguardAllowingNotifications =
+ intent.getBooleanExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false);
+ if (mCurrentUserId == getSendingUserId()) {
+ boolean changed = updateLockscreenNotificationSetting();
+ if (changed) {
+ notifyNotificationStateChanged();
+ }
+ }
+ }
+ }
+ }
+ };
protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
@Override
@@ -321,11 +342,21 @@ public class NotificationLockscreenUserManagerImpl implements
mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
dumpManager.registerDumpable(this);
+
+ if (keyguardPrivateNotifications()) {
+ init();
+ }
}
public void setUpWithPresenter(NotificationPresenter presenter) {
mPresenter = presenter;
+ if (!keyguardPrivateNotifications()) {
+ init();
+ }
+ }
+
+ private void init() {
mLockscreenSettingsObserver = new ContentObserver(
mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
? mBackgroundHandler
@@ -408,6 +439,11 @@ public class NotificationLockscreenUserManagerImpl implements
new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
? mBackgroundExecutor : null, UserHandle.ALL);
+ if (keyguardPrivateNotifications()) {
+ mBroadcastDispatcher.registerReceiver(mKeyguardReceiver,
+ new IntentFilter(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED),
+ mBackgroundExecutor, UserHandle.ALL);
+ }
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_ADDED);
@@ -449,6 +485,10 @@ public class NotificationLockscreenUserManagerImpl implements
mLockscreenSettingsObserver.onChange(
false, mLockScreenUris, 0, UserHandle.of(userId));
updateDpcSettings(userId);
+
+ if (keyguardPrivateNotifications()) {
+ updateGlobalKeyguardSettings();
+ }
}
public boolean shouldShowLockscreenNotifications() {
@@ -477,8 +517,12 @@ public class NotificationLockscreenUserManagerImpl implements
boolean allowedByDpm;
if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
- && mKeyguardAllowingNotifications;
+ if (keyguardPrivateNotifications()) {
+ show = mUsersUsersAllowingNotifications.get(mCurrentUserId);
+ } else {
+ show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
+ && mKeyguardAllowingNotifications;
+ }
// If DPC never notified us about a user, that means they have no policy for the user,
// and they allow the behavior
allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true);
@@ -521,8 +565,13 @@ public class NotificationLockscreenUserManagerImpl implements
1,
userId) != 0;
mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen);
- boolean keyguardChanged = updateGlobalKeyguardSettings();
- return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged;
+
+ if (keyguardPrivateNotifications()) {
+ return (newAllowLockscreen != originalAllowLockscreen);
+ } else {
+ boolean keyguardChanged = updateGlobalKeyguardSettings();
+ return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged;
+ }
}
@WorkerThread
@@ -560,8 +609,14 @@ public class NotificationLockscreenUserManagerImpl implements
Log.i(TAG, "Asking for redact notifs dpm override too early", new Throwable());
return false;
}
- return mUsersUsersAllowingPrivateNotifications.get(userHandle)
- && mUsersDpcAllowingPrivateNotifications.get(userHandle);
+ if (keyguardPrivateNotifications()) {
+ return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+ && mUsersDpcAllowingPrivateNotifications.get(userHandle)
+ && mKeyguardAllowingNotifications;
+ } else {
+ return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+ && mUsersDpcAllowingPrivateNotifications.get(userHandle);
+ }
} else {
if (userHandle == USER_ALL) {
return true;
@@ -648,9 +703,14 @@ public class NotificationLockscreenUserManagerImpl implements
Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable());
updateDpcSettings(userHandle);
}
- return mUsersUsersAllowingNotifications.get(userHandle)
- && mUsersDpcAllowingNotifications.get(userHandle)
- && mKeyguardAllowingNotifications;
+ if (keyguardPrivateNotifications()) {
+ return mUsersUsersAllowingNotifications.get(userHandle)
+ && mUsersDpcAllowingNotifications.get(userHandle);
+ } else {
+ return mUsersUsersAllowingNotifications.get(userHandle)
+ && mUsersDpcAllowingNotifications.get(userHandle)
+ && mKeyguardAllowingNotifications;
+ }
} else {
if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
return true;
@@ -689,7 +749,12 @@ public class NotificationLockscreenUserManagerImpl implements
ent.getSbn().getNotification().visibility == Notification.VISIBILITY_PRIVATE;
boolean userForcesRedaction = packageHasVisibilityOverride(ent.getSbn().getKey());
- return userForcesRedaction || notificationRequestsRedaction && isNotifRedacted;
+ if (keyguardPrivateNotifications()) {
+ return !mKeyguardAllowingNotifications
+ || userForcesRedaction || notificationRequestsRedaction && isNotifRedacted;
+ } else {
+ return userForcesRedaction || notificationRequestsRedaction && isNotifRedacted;
+ }
}
private boolean packageHasVisibilityOverride(String key) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 3f76d303898e..8858d132c4be 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -51,6 +51,7 @@ import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import dagger.Lazy
@SmallTest
class ActiveUnlockConfigTest : SysuiTestCase() {
@@ -60,6 +61,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() {
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var lazyKeyguardUpdateMonitor: Lazy<KeyguardUpdateMonitor>
@Mock private lateinit var mockPrintWriter: PrintWriter
@Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
@@ -72,6 +74,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUser)
+ whenever(lazyKeyguardUpdateMonitor.get()).thenReturn(keyguardUpdateMonitor)
secureSettings = FakeSettings()
activeUnlockConfig =
ActiveUnlockConfig(
@@ -79,6 +82,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() {
secureSettings,
contentResolver,
selectedUserInteractor,
+ lazyKeyguardUpdateMonitor,
dumpManager
)
}
@@ -260,7 +264,6 @@ class ActiveUnlockConfigTest : SysuiTestCase() {
updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// GIVEN fingerprint and face are NOT enrolled
- activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
`when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(false)
`when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(false)
@@ -290,7 +293,6 @@ class ActiveUnlockConfigTest : SysuiTestCase() {
updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// GIVEN fingerprint and face are both enrolled
- activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
`when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true)
`when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 7f20d9aa9451..51ceda339778 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -37,11 +37,11 @@ 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.Flags;
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;
@@ -98,7 +98,6 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
.thenReturn(mKeyguardMessageArea);
when(mAbsKeyInputView.getResources()).thenReturn(getContext().getResources());
mFeatureFlags = new FakeFeatureFlags();
- mFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false);
mKeyguardAbsKeyInputViewController = createTestObject();
mKeyguardAbsKeyInputViewController.init();
reset(mKeyguardMessageAreaController); // Clear out implicit call to init.
@@ -127,7 +126,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
@Test
public void withFeatureFlagOn_oldMessage_isHidden() {
- mFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
KeyguardAbsKeyInputViewController underTest = createTestObject();
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 1ab634c46de9..d03a898cf6d1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2283,6 +2283,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
Optional.of(mInteractiveToAuthProvider),
mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
+ start();
}
public boolean hasSimStateJustChanged() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
new file mode 100644
index 000000000000..202d9ce27a34
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.os.Looper
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.dagger.GlobalRootComponent
+import com.android.systemui.dagger.SysUIComponent
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.flags.systemPropertiesHelper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.process.processWrapper
+import com.android.systemui.startable.Dependencies
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@RunWithLooper
+class SystemUIApplicationTest : SysuiTestCase() {
+
+ private val app: SystemUIApplication = SystemUIApplication()
+ private lateinit var contextAvailableCallback:
+ SystemUIAppComponentFactoryBase.ContextAvailableCallback
+
+ @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
+ val kosmos = Kosmos()
+ @Mock private lateinit var initializer: SystemUIInitializer
+ @Mock private lateinit var rootComponent: GlobalRootComponent
+ @Mock private lateinit var sysuiComponent: SysUIComponent
+ @Mock private lateinit var bootCompleteCache: BootCompleteCacheImpl
+ @Mock private lateinit var initController: InitController
+
+ private val startableA = StartableA()
+ private val startableB = StartableB()
+ private val startableC = StartableC()
+ private val startableD = StartableD()
+ private val startableE = StartableE()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ app.attachBaseContext(context)
+ contextAvailableCallback =
+ SystemUIAppComponentFactoryBase.ContextAvailableCallback { initializer }
+ whenever(initializer.rootComponent).thenReturn(rootComponent)
+ whenever(initializer.sysUIComponent).thenReturn(sysuiComponent)
+ whenever(rootComponent.mainLooper).thenReturn(Looper.myLooper())
+ whenever(rootComponent.systemPropertiesHelper).thenReturn(kosmos.systemPropertiesHelper)
+ whenever(rootComponent.processWrapper).thenReturn(kosmos.processWrapper)
+ whenever(sysuiComponent.provideBootCacheImpl()).thenReturn(bootCompleteCache)
+ whenever(sysuiComponent.createDumpManager()).thenReturn(kosmos.dumpManager)
+ whenever(sysuiComponent.initController).thenReturn(initController)
+ kosmos.processWrapper.systemUser = true
+
+ app.setContextAvailableCallback(contextAvailableCallback)
+ }
+
+ @Test
+ fun testAppOnCreate() {
+ app.onCreate()
+ }
+
+ @Test
+ fun testStartServices_singleService() {
+ whenever(sysuiComponent.startables)
+ .thenReturn(mutableMapOf(StartableA::class.java to Provider { startableA }))
+ app.onCreate()
+ app.startServicesIfNeeded()
+ assertThat(startableA.started).isTrue()
+ }
+
+ @Test
+ fun testStartServices_twoServices() {
+ whenever(sysuiComponent.startables)
+ .thenReturn(
+ mutableMapOf(
+ StartableA::class.java to Provider { startableA },
+ StartableB::class.java to Provider { startableB }
+ )
+ )
+ app.onCreate()
+ app.startServicesIfNeeded()
+ assertThat(startableA.started).isTrue()
+ assertThat(startableB.started).isTrue()
+ }
+
+ @Test
+ fun testStartServices_simpleDependency() {
+ whenever(sysuiComponent.startables)
+ .thenReturn(
+ mutableMapOf(
+ StartableC::class.java to Provider { startableC },
+ StartableA::class.java to Provider { startableA },
+ StartableB::class.java to Provider { startableB }
+ )
+ )
+ app.onCreate()
+ app.startServicesIfNeeded()
+ assertThat(startableA.started).isTrue()
+ assertThat(startableB.started).isTrue()
+ assertThat(startableC.started).isTrue()
+ assertThat(startableC.order).isGreaterThan(startableA.order)
+ }
+
+ @Test
+ fun testStartServices_complexDependency() {
+ whenever(sysuiComponent.startables)
+ .thenReturn(
+ mutableMapOf(
+ StartableE::class.java to Provider { startableE },
+ StartableC::class.java to Provider { startableC },
+ StartableD::class.java to Provider { startableD },
+ StartableA::class.java to Provider { startableA },
+ StartableB::class.java to Provider { startableB }
+ )
+ )
+ app.onCreate()
+ app.startServicesIfNeeded()
+ assertThat(startableA.started).isTrue()
+ assertThat(startableB.started).isTrue()
+ assertThat(startableC.started).isTrue()
+ assertThat(startableD.started).isTrue()
+ assertThat(startableE.started).isTrue()
+ assertThat(startableC.order).isGreaterThan(startableA.order)
+ assertThat(startableD.order).isGreaterThan(startableA.order)
+ assertThat(startableD.order).isGreaterThan(startableB.order)
+ assertThat(startableE.order).isGreaterThan(startableB.order)
+ assertThat(startableE.order).isGreaterThan(startableD.order)
+ }
+
+ open class TestableStartable : CoreStartable {
+ companion object {
+ var startOrder = 0
+ }
+
+ var started = false
+ var order = -1
+
+ override fun start() {
+ started = true
+ order = startOrder
+ startOrder++
+ }
+ }
+
+ class StartableA : TestableStartable()
+ class StartableB : TestableStartable()
+
+ @Dependencies(StartableA::class) class StartableC : TestableStartable()
+
+ @Dependencies(StartableA::class, StartableB::class) class StartableD : TestableStartable()
+
+ @Dependencies(StartableD::class, StartableB::class) class StartableE : TestableStartable()
+}
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 aa0d7b621793..45a426e1cb84 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
@@ -25,6 +25,7 @@ import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -35,8 +36,6 @@ import com.android.systemui.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
@@ -107,8 +106,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
suspend fun TestScope.init() {
userRepository.setSelectedUserInfo(PRIMARY_USER)
- val featureFlags =
- FakeFeatureFlagsClassic().apply { set(Flags.REVAMPED_BOUNCER_MESSAGES, true) }
+ mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
primaryBouncerInteractor =
PrimaryBouncerInteractor(
bouncerRepository,
@@ -131,7 +129,6 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
repository = repository,
userRepository = userRepository,
countDownTimerUtil = countDownTimerUtil,
- featureFlags = featureFlags,
updateMonitor = updateMonitor,
biometricSettingsRepository = biometricSettingsRepository,
applicationScope = this.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index dc0d9c5f987e..070a0ccd1232 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -17,21 +17,26 @@
package com.android.systemui.keyguard.ui.view.layout.sections
+import android.content.pm.PackageManager
+import android.content.res.Resources
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Utils
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@@ -40,17 +45,10 @@ import org.mockito.MockitoAnnotations
class ClockSectionTest : SysuiTestCase() {
@Mock private lateinit var keyguardClockInteractor: KeyguardClockInteractor
@Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
- @Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
private lateinit var underTest: ClockSection
- // smartspaceViewModel.getDimen("date_weather_view_height")
- private val SMART_SPACE_DATE_WEATHER_HEIGHT = 10
-
- // smartspaceViewModel.getDimen("enhanced_smartspace_height")
- private val ENHANCED_SMART_SPACE_HEIGHT = 11
-
private val SMALL_CLOCK_TOP_SPLIT_SHADE =
context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
@@ -75,15 +73,41 @@ class ClockSectionTest : SysuiTestCase() {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- whenever(smartspaceViewModel.getDimen("date_weather_view_height"))
- .thenReturn(SMART_SPACE_DATE_WEATHER_HEIGHT)
- whenever(smartspaceViewModel.getDimen("enhanced_smartspace_height"))
- .thenReturn(ENHANCED_SMART_SPACE_HEIGHT)
+ val remoteResources = mock<Resources>()
+ whenever(
+ remoteResources.getIdentifier(
+ anyString(),
+ eq("dimen"),
+ anyString(),
+ )
+ )
+ .then { invocation ->
+ val name = invocation.arguments[0] as String
+ val index = DIMENSION_BY_IDENTIFIER_NAME.indexOfFirst { (key, _) -> key == name }
+ if (index == -1) {
+ error(
+ "No entry for a dimension named \"$name\", please add it to the list above."
+ )
+ }
+ index
+ }
+ whenever(
+ remoteResources.getDimensionPixelSize(
+ anyInt(),
+ )
+ )
+ .then { invocation ->
+ val id = invocation.arguments[0] as Int
+ DIMENSION_BY_IDENTIFIER_NAME[id].second
+ }
+ val packageManager = mock<PackageManager>()
+ whenever(packageManager.getResourcesForApplication(anyString())).thenReturn(remoteResources)
+ mContext.setMockPackageManager(packageManager)
+
underTest =
ClockSection(
keyguardClockInteractor,
keyguardClockViewModel,
- smartspaceViewModel,
mContext,
splitShadeStateController,
)
@@ -164,4 +188,14 @@ class ClockSectionTest : SysuiTestCase() {
assertThat(smallClockConstraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(smallClockConstraint.layout.topMargin).isEqualTo(expectedSmallClockTopMargin)
}
+
+ companion object {
+ private val SMART_SPACE_DATE_WEATHER_HEIGHT = 10
+ private val ENHANCED_SMART_SPACE_HEIGHT = 11
+ private val DIMENSION_BY_IDENTIFIER_NAME =
+ listOf(
+ "date_weather_view_height" to SMART_SPACE_DATE_WEATHER_HEIGHT,
+ "enhanced_smartspace_height" to ENHANCED_SMART_SPACE_HEIGHT,
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index 740110b4fee0..28da957d31b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -29,7 +29,9 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.StateFlow
@@ -50,9 +52,9 @@ class SmartspaceSectionTest : SysuiTestCase() {
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var hasCustomWeatherDataDisplay: StateFlow<Boolean>
- private val smartspaceView = View(mContext).also { it.id = View.generateViewId() }
- private val weatherView = View(mContext).also { it.id = View.generateViewId() }
- private val dateView = View(mContext).also { it.id = View.generateViewId() }
+ private val smartspaceView = View(mContext).also { it.id = sharedR.id.bc_smartspace_view }
+ private val weatherView = View(mContext).also { it.id = sharedR.id.weather_smartspace_view }
+ private val dateView = View(mContext).also { it.id = sharedR.id.date_smartspace_view }
private lateinit var constraintLayout: ConstraintLayout
private lateinit var constraintSet: ConstraintSet
@@ -69,13 +71,13 @@ class SmartspaceSectionTest : SysuiTestCase() {
keyguardUnlockAnimationController,
)
constraintLayout = ConstraintLayout(mContext)
- whenever(keyguardSmartspaceViewModel.smartspaceView).thenReturn(smartspaceView)
- whenever(keyguardSmartspaceViewModel.weatherView).thenReturn(weatherView)
- whenever(keyguardSmartspaceViewModel.dateView).thenReturn(dateView)
+ whenever(lockscreenSmartspaceController.buildAndConnectView(any()))
+ .thenReturn(smartspaceView)
+ whenever(lockscreenSmartspaceController.buildAndConnectWeatherView(any()))
+ .thenReturn(weatherView)
+ whenever(lockscreenSmartspaceController.buildAndConnectDateView(any())).thenReturn(dateView)
whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay)
.thenReturn(hasCustomWeatherDataDisplay)
- whenever(keyguardSmartspaceViewModel.smartspaceController)
- .thenReturn(lockscreenSmartspaceController)
constraintSet = ConstraintSet()
}
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 86d8d54684ac..1e1e34421a57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -51,7 +51,6 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
-import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
@@ -197,9 +196,9 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true)
featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false)
featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
- featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true)
featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
testScope = TestScope()
fakeClock = FakeSystemClock()
@@ -242,7 +241,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
repository = BouncerMessageRepositoryImpl(),
userRepository = FakeUserRepository(),
countDownTimerUtil = mock(CountDownTimerUtil::class.java),
- featureFlags = featureFlagsClassic,
updateMonitor = mock(KeyguardUpdateMonitor::class.java),
biometricSettingsRepository = FakeBiometricSettingsRepository(),
applicationScope = testScope.backgroundScope,
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 d9ff892145c7..dd4ac9d28a9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
@@ -189,9 +190,9 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
- featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
testScope = TestScope()
controller =
NotificationShadeWindowViewController(
@@ -232,7 +233,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
repository = BouncerMessageRepositoryImpl(),
userRepository = FakeUserRepository(),
countDownTimerUtil = Mockito.mock(CountDownTimerUtil::class.java),
- featureFlags = featureFlags,
updateMonitor = Mockito.mock(KeyguardUpdateMonitor::class.java),
biometricSettingsRepository = FakeBiometricSettingsRepository(),
applicationScope = testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 42c7375bfb2e..0c6f456b1e80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -19,9 +19,12 @@ package com.android.systemui.statusbar;
import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
+import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
+import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.os.UserHandle.USER_ALL;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
@@ -111,7 +114,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return FlagsParameterization.allCombinationsOf(FLAG_ALLOW_PRIVATE_PROFILE);
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_ALLOW_PRIVATE_PROFILE,
+ FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS);
}
public NotificationLockscreenUserManagerTest(FlagsParameterization flags) {
@@ -245,6 +250,19 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+ public void testInit() {
+ when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+ mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+ mLockscreenUserManager.setUpWithPresenter(mPresenter);
+
+ mBackgroundExecutor.runAllReady();
+
+ assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ }
+
+ @Test
public void testGetCurrentProfiles() {
final SparseArray<UserInfo> expectedCurProfiles = new SparseArray<>();
expectedCurProfiles.put(mCurrentUser.id, mCurrentUser);
@@ -579,6 +597,29 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+ public void testKeyguardManager_noPrivateNotifications() {
+ Mockito.clearInvocations(mDevicePolicyManager);
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
+ .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, true));
+
+ assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ // it's a global field, confirm secondary too
+ assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+ assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+ mSecondaryUser.id));
+ }
+
+ @Test
public void testDevicePolicy_noPrivateNotifications() {
Mockito.clearInvocations(mDevicePolicyManager);
// User allows notifications
@@ -699,6 +740,29 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+ public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications_show() {
+ // KeyguardManager does not allow notifications
+ when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ // DevicePolicy allows notifications
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(0);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+ mLockscreenUserManager.mKeyguardReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mKeyguardReceiver.onReceive(mContext,
+ new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
+ .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false));
+
+ assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
+ assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ @DisableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() {
// KeyguardManager does not allow notifications
when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
@@ -718,6 +782,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
// DevicePolicy allows notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessKosmos.kt
new file mode 100644
index 000000000000..79167f840f60
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.process
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.processWrapper: ProcessWrapperFake by Kosmos.Fixture { ProcessWrapperFake() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt
new file mode 100644
index 000000000000..9841778f835b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.process
+
+class ProcessWrapperFake : ProcessWrapper() {
+
+ var systemUser: Boolean = false
+
+ override fun isSystemUser(): Boolean {
+ return systemUser
+ }
+}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
index 3a90a959b877..73ed97ff7b11 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
@@ -35,8 +35,8 @@ import android.service.contentcapture.SnapshotData;
import android.util.LocalLog;
import android.util.Slog;
import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.ContentCaptureSession;
import android.view.contentcapture.ContentCaptureSessionId;
-import android.view.contentcapture.MainContentCaptureSession;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
@@ -123,7 +123,7 @@ final class ContentCaptureServerSession {
public void setContentCaptureEnabledLocked(boolean enabled) {
try {
final Bundle extras = new Bundle();
- extras.putBoolean(MainContentCaptureSession.EXTRA_ENABLED_STATE, true);
+ extras.putBoolean(ContentCaptureSession.EXTRA_ENABLED_STATE, true);
mSessionStateReceiver.send(enabled ? RESULT_CODE_TRUE : RESULT_CODE_FALSE, extras);
} catch (RemoteException e) {
Slog.w(TAG, "Error async reporting result to client: " + e);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 96b1650d9575..02f4485d5b40 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2118,11 +2118,6 @@ public final class ActiveServices {
// anyway, so we just remove the SHORT_SERVICE type.
foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
}
- if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) {
- throw new ForegroundServiceStartNotAllowedException("FGS type "
- + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType)
- + " not allowed to start from BOOT_COMPLETED!");
- }
boolean alreadyStartedOp = false;
boolean stopProcStatsOp = false;
@@ -2137,6 +2132,12 @@ public final class ActiveServices {
mServiceFGAnrTimer.cancel(r);
}
+ if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) {
+ throw new ForegroundServiceStartNotAllowedException("FGS type "
+ + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType)
+ + " not allowed to start from BOOT_COMPLETED!");
+ }
+
final ProcessServiceRecord psr = r.app.mServices;
try {
boolean ignoreForeground = false;
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index 4df25811cc99..5d609bca334c 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -54,8 +54,8 @@ public class AuthenticationStatsCollector {
@NonNull private final Context mContext;
@NonNull private final PackageManager mPackageManager;
- @NonNull private final FaceManager mFaceManager;
- @NonNull private final FingerprintManager mFingerprintManager;
+ @Nullable private final FaceManager mFaceManager;
+ @Nullable private final FingerprintManager mFingerprintManager;
private final boolean mEnabled;
private final float mThreshold;
@@ -197,11 +197,11 @@ public class AuthenticationStatsCollector {
}
private boolean hasEnrolledFace(int userId) {
- return mFaceManager.hasEnrolledTemplates(userId);
+ return mFaceManager != null && mFaceManager.hasEnrolledTemplates(userId);
}
private boolean hasEnrolledFingerprint(int userId) {
- return mFingerprintManager.hasEnrolledTemplates(userId);
+ return mFingerprintManager != null && mFingerprintManager.hasEnrolledTemplates(userId);
}
/**
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
index 0eb9166371dc..5c9e61a2940b 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java
@@ -58,11 +58,9 @@ import java.util.Objects;
* <p>This implementation obtains and manages all routes via {@link AudioManager}, with the
* exception of {@link AudioManager#handleBluetoothActiveDeviceChanged inactive bluetooth} routes
* which are managed by {@link AudioPoliciesBluetoothRouteController}, which depends on the
- * bluetooth stack (for example {@link BluetoothAdapter}.
+ * bluetooth stack ({@link BluetoothAdapter} and related classes).
*/
-// TODO: b/305199571 - Rename this class to avoid the AudioPolicies prefix, which has been flagged
-// by the audio team as a confusing name.
-/* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController {
+/* package */ final class AudioManagerRouteController implements DeviceRouteController {
private static final String TAG = SystemMediaRoute2Provider.TAG;
@NonNull
@@ -103,7 +101,7 @@ import java.util.Objects;
Manifest.permission.MODIFY_AUDIO_ROUTING,
Manifest.permission.QUERY_AUDIO_STATE
})
- /* package */ AudioPoliciesDeviceRouteController(
+ /* package */ AudioManagerRouteController(
@NonNull Context context,
@NonNull AudioManager audioManager,
@NonNull Looper looper,
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 8b62cc974862..dff0adfca370 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -65,7 +65,7 @@ import java.util.List;
if (strategyForMedia != null
&& btAdapter != null
&& Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
- return new AudioPoliciesDeviceRouteController(
+ return new AudioManagerRouteController(
context,
audioManager,
looper,
diff --git a/services/core/java/com/android/server/media/TEST_MAPPING b/services/core/java/com/android/server/media/TEST_MAPPING
index 1b49093a66d8..b3e5b9e96889 100644
--- a/services/core/java/com/android/server/media/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/TEST_MAPPING
@@ -3,5 +3,10 @@
{
"name": "CtsMediaBetterTogetherTestCases"
}
+ ],
+ "postsubmit": [
+ {
+ "name": "MediaRouterServiceTests"
+ }
]
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 85731651dd59..4d19eade5a05 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -42,6 +42,7 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.Keep;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.am.ProcessList;
@@ -693,6 +694,7 @@ public class NetworkPolicyLogger {
* Note: This class needs to be public for RingBuffer class to be able to create
* new instances of this.
*/
+ @Keep
public static final class Data {
public int type;
public long timeStamp;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ff415c155b35..d9e6692e95b3 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -16,7 +16,9 @@
package com.android.server.notification;
+import static android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS;
import static android.Manifest.permission.RECEIVE_SENSITIVE_NOTIFICATIONS;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -67,6 +69,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OF
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static android.app.Flags.lifetimeExtensionRefactor;
+import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
+import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
@@ -210,6 +214,7 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -3369,9 +3374,7 @@ public class NotificationManagerService extends SystemService {
.setChannelName(r.getChannel().getName().toString())
.setPostedTimeMs(System.currentTimeMillis())
.setTitle(getHistoryTitle(r.getNotification()))
- .setText(getHistoryText(
- r.getSbn().getPackageContext(getContext()),
- r.getNotification()))
+ .setText(getHistoryText(r.getNotification()))
.setIcon(r.getNotification().getSmallIcon())
.build());
}
@@ -3414,12 +3417,11 @@ public class NotificationManagerService extends SystemService {
/**
* Returns the appropriate substring for this notification based on the style of notification.
*/
- private String getHistoryText(Context appContext, Notification n) {
+ private String getHistoryText(Notification n) {
CharSequence text = null;
if (n.extras != null) {
text = n.extras.getCharSequence(EXTRA_TEXT);
-
- Notification.Builder nb = Notification.Builder.recoverBuilder(appContext, n);
+ Notification.Builder nb = Notification.Builder.recoverBuilder(getContext(), n);
if (nb.getStyle() instanceof Notification.BigTextStyle) {
text = ((Notification.BigTextStyle) nb.getStyle()).getBigText();
@@ -5126,8 +5128,10 @@ public class NotificationManagerService extends SystemService {
for (int userId : mUm.getProfileIds(info.userid, false)) {
try {
int uid = getUidForPackageAndUser(pkg, UserHandle.of(userId));
- VersionedPackage vp = new VersionedPackage(pkg, uid);
- nlf.addPackage(vp);
+ if (uid != INVALID_UID) {
+ VersionedPackage vp = new VersionedPackage(pkg, uid);
+ nlf.addPackage(vp);
+ }
} catch (Exception e) {
// pkg doesn't exist on that user; skip
}
@@ -5569,7 +5573,7 @@ public class NotificationManagerService extends SystemService {
private void enforceSystemOrSystemUI(String message) {
if (isCallerSystemOrPhone()) return;
- getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+ getContext().enforceCallingPermission(STATUS_BAR_SERVICE,
message);
}
@@ -5578,7 +5582,7 @@ public class NotificationManagerService extends SystemService {
checkCallerIsSystemOrSameApp(pkg);
} catch (SecurityException e) {
getContext().enforceCallingPermission(
- android.Manifest.permission.STATUS_BAR_SERVICE,
+ STATUS_BAR_SERVICE,
message);
}
}
@@ -6183,13 +6187,20 @@ public class NotificationManagerService extends SystemService {
@Override
public void setPrivateNotificationsAllowed(boolean allow) {
if (PackageManager.PERMISSION_GRANTED
- != getContext().checkCallingPermission(
- permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
+ != getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
throw new SecurityException(
"Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
}
if (allow != mLockScreenAllowSecureNotifications) {
mLockScreenAllowSecureNotifications = allow;
+ if (android.app.Flags.keyguardPrivateNotifications()) {
+ getContext().sendBroadcast(
+ new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
+ .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED,
+ mLockScreenAllowSecureNotifications),
+ STATUS_BAR_SERVICE);
+ }
+
handleSavePolicyFile();
}
}
@@ -6197,8 +6208,7 @@ public class NotificationManagerService extends SystemService {
@Override
public boolean getPrivateNotificationsAllowed() {
if (PackageManager.PERMISSION_GRANTED
- != getContext().checkCallingPermission(
- permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
+ != getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
throw new SecurityException(
"Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
}
@@ -8369,6 +8379,8 @@ public class NotificationManagerService extends SystemService {
boolean posted = false;
try {
posted = postNotification();
+ } catch (Exception e) {
+ Slog.e(TAG, "Error posting", e);
} finally {
if (!posted) {
mTracker.cancel();
@@ -9557,12 +9569,16 @@ public class NotificationManagerService extends SystemService {
}
private void scheduleListenerHintsChanged(int state) {
- mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED);
+ if (!Flags.notificationReduceMessagequeueUsage()) {
+ mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED);
+ }
mHandler.obtainMessage(MESSAGE_LISTENER_HINTS_CHANGED, state, 0).sendToTarget();
}
private void scheduleInterruptionFilterChanged(int listenerInterruptionFilter) {
- mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED);
+ if (!Flags.notificationReduceMessagequeueUsage()) {
+ mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED);
+ }
mHandler.obtainMessage(
MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED,
listenerInterruptionFilter,
@@ -9642,15 +9658,24 @@ public class NotificationManagerService extends SystemService {
}
protected void scheduleSendRankingUpdate() {
- if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
+ if (Flags.notificationReduceMessagequeueUsage()) {
Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE);
sendMessage(m);
+ } else {
+ if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
+ Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE);
+ sendMessage(m);
+ }
}
}
protected void scheduleCancelNotification(CancelNotificationRunnable cancelRunnable) {
- if (!hasCallbacks(cancelRunnable)) {
+ if (Flags.notificationReduceMessagequeueUsage()) {
sendMessage(Message.obtain(this, cancelRunnable));
+ } else {
+ if (!hasCallbacks(cancelRunnable)) {
+ sendMessage(Message.obtain(this, cancelRunnable));
+ }
}
}
@@ -9684,7 +9709,9 @@ public class NotificationManagerService extends SystemService {
}
public void requestSort() {
- removeMessages(MESSAGE_RANKING_SORT);
+ if (!Flags.notificationReduceMessagequeueUsage()) {
+ removeMessages(MESSAGE_RANKING_SORT);
+ }
Message msg = Message.obtain();
msg.what = MESSAGE_RANKING_SORT;
sendMessage(msg);
@@ -10589,7 +10616,7 @@ public class NotificationManagerService extends SystemService {
if (isCallerSystemOrPhone()) {
return true;
}
- return getContext().checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+ return getContext().checkCallingPermission(STATUS_BAR_SERVICE)
== PERMISSION_GRANTED;
}
@@ -10628,7 +10655,7 @@ public class NotificationManagerService extends SystemService {
if (isCallerSystemOrPhone()) {
return;
}
- getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+ getContext().enforceCallingPermission(STATUS_BAR_SERVICE,
message);
}
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index df570a02eba5..0145577fb945 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -23,6 +23,7 @@ import static android.service.notification.NotificationServiceProto.RULE_TYPE_MA
import static android.service.notification.NotificationServiceProto.RULE_TYPE_UNKNOWN;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Flags;
import android.app.NotificationManager;
import android.content.pm.PackageManager;
@@ -256,13 +257,21 @@ class ZenModeEventLogger {
return true;
}
+ if (Flags.modesApi() && hasActiveRuleCountDiff()) {
+ // Rules with INTERRUPTION_FILTER_ALL were always possible but before MODES_API
+ // they were completely useless; now they can apply effects, so we want to log
+ // when they become active/inactive, even though DND itself (as in "notification
+ // blocking") is off.
+ return true;
+ }
+
// If zen mode didn't change, did the policy or number of active rules change? We only
// care about changes that take effect while zen mode is on, so make sure the current
// zen mode is not "OFF"
if (mNewZenMode == ZEN_MODE_OFF) {
return false;
}
- return hasPolicyDiff() || hasRuleCountDiff();
+ return hasPolicyDiff() || hasActiveRuleCountDiff();
}
// Does the difference in zen mode go from off to on or vice versa?
@@ -294,6 +303,16 @@ class ZenModeEventLogger {
}
}
+ if (Flags.modesApi() && mNewZenMode == ZEN_MODE_OFF) {
+ // If the mode is OFF -> OFF then there cannot be any *effective* change to policy.
+ // (Note that, in theory, a policy diff is impossible since we don't merge the
+ // policies of INTERRUPTION_FILTER_ALL rules; this is a "just in case" check).
+ if (hasPolicyDiff() || hasChannelsBypassingDiff()) {
+ Log.wtf(TAG, "Detected policy diff even though DND is OFF and not toggled");
+ }
+ return ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED;
+ }
+
// zen mode didn't change; we must be here because of a policy change or rule change
if (hasPolicyDiff() || hasChannelsBypassingDiff()) {
return ZenStateChangedEvent.DND_POLICY_CHANGED;
@@ -345,7 +364,7 @@ class ZenModeEventLogger {
* Returns whether the previous config and new config have a different number of active
* automatic or manual rules.
*/
- private boolean hasRuleCountDiff() {
+ private boolean hasActiveRuleCountDiff() {
return numActiveRulesInConfig(mPrevConfig) != numActiveRulesInConfig(mNewConfig);
}
@@ -381,9 +400,11 @@ class ZenModeEventLogger {
// Determine the number of (automatic & manual) rules active after the change takes place.
int getNumRulesActive() {
- // If the zen mode has turned off, that means nothing can be active.
- if (mNewZenMode == ZEN_MODE_OFF) {
- return 0;
+ if (!Flags.modesApi()) {
+ // If the zen mode has turned off, that means nothing can be active.
+ if (mNewZenMode == ZEN_MODE_OFF) {
+ return 0;
+ }
}
return numActiveRulesInConfig(mNewConfig);
}
@@ -478,8 +499,19 @@ class ZenModeEventLogger {
/**
* Convert the new policy to a DNDPolicyProto format for output in logs.
+ *
+ * <p>If {@code mNewZenMode} is {@code ZEN_MODE_OFF} (which can mean either no rules
+ * active, or only rules with {@code INTERRUPTION_FILTER_ALL} active) then this returns
+ * {@code null} (which will be mapped to a missing submessage in the proto). Although this
+ * is not the value of {@code NotificationManager#getConsolidatedNotificationPolicy()}, it
+ * makes sense for logging since that policy is not actually influencing anything.
*/
+ @Nullable
byte[] getDNDPolicyProto() {
+ if (Flags.modesApi() && mNewZenMode == ZEN_MODE_OFF) {
+ return null;
+ }
+
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
ProtoOutputStream proto = new ProtoOutputStream(bytes);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 0a46901a93d1..b7a203814c94 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1490,7 +1490,12 @@ public class ZenModeHelper {
for (ZenRule automaticRule : mConfig.automaticRules.values()) {
if (automaticRule.isAutomaticActive()) {
- applyCustomPolicy(policy, automaticRule);
+ // Active rules with INTERRUPTION_FILTER_ALL are not included in consolidated
+ // policy. This is relevant in case some other active rule has a more
+ // restrictive INTERRUPTION_FILTER but a more lenient ZenPolicy!
+ if (!Flags.modesApi() || automaticRule.zenMode != Global.ZEN_MODE_OFF) {
+ applyCustomPolicy(policy, automaticRule);
+ }
if (Flags.modesApi()) {
deviceEffectsBuilder.add(automaticRule.zenDeviceEffects);
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 8e79922a996a..49db7fc280d9 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -50,3 +50,10 @@ flag {
# Referenced in WM where WM starts before DeviceConfig
is_fixed_read_only: true
}
+
+flag {
+ name: "notification_reduce_messagequeue_usage"
+ namespace: "systemui"
+ description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler"
+ bug: "311051285"
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5b13d3fead90..edce3ec4b37c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -473,6 +473,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private TalkbackShortcutController mTalkbackShortcutController;
+ private WindowWakeUpPolicy mWindowWakeUpPolicy;
+
boolean mSafeMode;
// Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
@@ -640,15 +642,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// Whether to lock the device after the next dreaming transition has finished.
private boolean mLockAfterDreamingTransitionFinished;
- // Allowed theater mode wake actions
- private boolean mAllowTheaterModeWakeFromKey;
- private boolean mAllowTheaterModeWakeFromPowerKey;
- private boolean mAllowTheaterModeWakeFromMotion;
- private boolean mAllowTheaterModeWakeFromMotionWhenNotDreaming;
- private boolean mAllowTheaterModeWakeFromCameraLens;
- private boolean mAllowTheaterModeWakeFromLidSwitch;
- private boolean mAllowTheaterModeWakeFromWakeGesture;
-
// If true, the power button long press behavior will be invoked even if the default display is
// non-interactive. If false, the power button long press behavior will be skipped if the
// default display is non-interactive.
@@ -930,8 +923,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (shouldEnableWakeGestureLp()) {
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
"Wake Up");
- wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromWakeGesture,
- PowerManager.WAKE_REASON_GESTURE, "android.policy:GESTURE");
+ mWindowWakeUpPolicy.wakeUpFromWakeGesture();
}
}
}
@@ -1067,7 +1059,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|| handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
if (!mPowerKeyHandled) {
if (!interactive) {
- wakeUpFromPowerKey(event.getDownTime());
+ wakeUpFromWakeKey(event);
}
} else {
// handled by another power key policy.
@@ -1309,7 +1301,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.THEATER_MODE_ON, 0);
if (!interactive) {
- wakeUpFromPowerKey(eventTime);
+ wakeUpFromWakeKey(eventTime, KEYCODE_POWER);
}
} else {
Slog.i(TAG, "Toggling theater mode on.");
@@ -1325,7 +1317,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case MULTI_PRESS_POWER_BRIGHTNESS_BOOST:
Slog.i(TAG, "Starting brightness boost.");
if (!interactive) {
- wakeUpFromPowerKey(eventTime);
+ wakeUpFromWakeKey(eventTime, KEYCODE_POWER);
}
mPowerManager.boostScreenBrightness(eventTime);
break;
@@ -2312,22 +2304,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mLidNavigationAccessibility = mContext.getResources().getInteger(
com.android.internal.R.integer.config_lidNavigationAccessibility);
- mAllowTheaterModeWakeFromKey = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_allowTheaterModeWakeFromKey);
- mAllowTheaterModeWakeFromPowerKey = mAllowTheaterModeWakeFromKey
- || mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey);
- mAllowTheaterModeWakeFromMotion = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion);
- mAllowTheaterModeWakeFromMotionWhenNotDreaming = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_allowTheaterModeWakeFromMotionWhenNotDreaming);
- mAllowTheaterModeWakeFromCameraLens = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens);
- mAllowTheaterModeWakeFromLidSwitch = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch);
- mAllowTheaterModeWakeFromWakeGesture = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture);
-
mGoToSleepOnButtonPressTheaterMode = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_goToSleepOnButtonPressTheaterMode);
@@ -2457,6 +2433,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_keyguardDrawnTimeout);
mKeyguardDelegate = injector.getKeyguardServiceDelegate();
mTalkbackShortcutController = injector.getTalkbackShortcutController();
+ mWindowWakeUpPolicy = new WindowWakeUpPolicy(mContext);
initKeyCombinationRules();
initSingleKeyGestureRules(injector.getLooper());
mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker();
@@ -4483,8 +4460,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
updateRotation(true);
if (lidOpen) {
- wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromLidSwitch,
- PowerManager.WAKE_REASON_LID, "android.policy:LID");
+ mWindowWakeUpPolicy.wakeUpFromLid();
} else if (getLidBehavior() != LID_BEHAVIOR_SLEEP) {
mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
}
@@ -4510,8 +4486,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
} else {
intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
}
- wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromCameraLens,
- PowerManager.WAKE_REASON_CAMERA_LAUNCH, "android.policy:CAMERA_COVER");
+ mWindowWakeUpPolicy.wakeUpFromCameraCover(whenNanos / 1000000);
startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
}
mCameraLensCoverState = lensCoverState;
@@ -4589,7 +4564,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
boolean shouldTurnOnTv = false;
if (down && (keyCode == KeyEvent.KEYCODE_POWER
|| keyCode == KeyEvent.KEYCODE_TV_POWER)) {
- wakeUpFromPowerKey(event.getDownTime());
+ wakeUpFromWakeKey(event);
shouldTurnOnTv = true;
} else if (down && (isWakeKey || keyCode == KeyEvent.KEYCODE_WAKEUP)
&& isWakeKeyWhenScreenOff(keyCode)) {
@@ -5104,9 +5079,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (mRequestedOrSleepingDefaultDisplay) {
mCameraGestureTriggeredDuringGoingToSleep = true;
// Wake device up early to prevent display doing redundant turning off/on stuff.
- wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromPowerKey,
- PowerManager.WAKE_REASON_CAMERA_LAUNCH,
- "android.policy:CAMERA_GESTURE_PREVENT_LOCK");
+ mWindowWakeUpPolicy.wakeUpFromPowerKeyCameraGesture();
}
return true;
}
@@ -5204,8 +5177,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
long whenNanos, int policyFlags) {
if ((policyFlags & FLAG_WAKE) != 0) {
- if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion,
- PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(whenNanos / 1000000)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5219,8 +5191,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// there will be no dream to intercept the touch and wake into ambient. The device should
// wake up in this case.
if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
- if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming,
- PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(whenNanos / 1000000)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5554,39 +5525,24 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return sleepDurationRealtime > mWakeUpToLastStateTimeout;
}
- private void wakeUpFromPowerKey(long eventTime) {
- if (wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey,
- PowerManager.WAKE_REASON_POWER_BUTTON, "android.policy:POWER")) {
- // Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
- if (shouldWakeUpWithHomeIntent()) {
- startDockOrHome(DEFAULT_DISPLAY, /*fromHomeKey*/ false, /*wakenFromDreams*/ true,
- PowerManager.wakeReasonToString(PowerManager.WAKE_REASON_POWER_BUTTON));
- }
- }
+ private void wakeUpFromWakeKey(KeyEvent event) {
+ wakeUpFromWakeKey(event.getEventTime(), event.getKeyCode());
}
- private void wakeUpFromWakeKey(KeyEvent event) {
- if (wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey,
- PowerManager.WAKE_REASON_WAKE_KEY, "android.policy:KEY")) {
+ private void wakeUpFromWakeKey(long eventTime, int keyCode) {
+ if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode)) {
+ final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER;
// Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
- if (shouldWakeUpWithHomeIntent() && event.getKeyCode() == KEYCODE_HOME) {
- startDockOrHome(DEFAULT_DISPLAY, /*fromHomeKey*/ true, /*wakenFromDreams*/ true,
- PowerManager.wakeReasonToString(PowerManager.WAKE_REASON_WAKE_KEY));
+ if (shouldWakeUpWithHomeIntent() && keyCanLaunchHome) {
+ startDockOrHome(
+ DEFAULT_DISPLAY,
+ /*fromHomeKey*/ keyCode == KEYCODE_HOME,
+ /*wakenFromDreams*/ true,
+ "Wake from " + KeyEvent. keyCodeToString(keyCode));
}
}
}
- private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason,
- String details) {
- final boolean theaterModeEnabled = isTheaterModeEnabled();
- if (!wakeInTheaterMode && theaterModeEnabled) {
- return false;
- }
-
- mPowerManager.wakeUp(wakeTime, reason, details);
- return true;
- }
-
private void finishKeyguardDrawn() {
if (!mDefaultDisplayPolicy.finishKeyguardDrawn()) {
return;
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
new file mode 100644
index 000000000000..392d0d4fdb52
--- /dev/null
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -0,0 +1,176 @@
+/*
+ * 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.policy;
+
+import static android.os.PowerManager.WAKE_REASON_CAMERA_LAUNCH;
+import static android.os.PowerManager.WAKE_REASON_GESTURE;
+import static android.os.PowerManager.WAKE_REASON_LID;
+import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON;
+import static android.os.PowerManager.WAKE_REASON_WAKE_KEY;
+import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
+import static android.view.KeyEvent.KEYCODE_POWER;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeReason;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Slog;
+
+
+/** Policy controlling the decision and execution of window-related wake ups. */
+class WindowWakeUpPolicy {
+ private static final String TAG = "WindowWakeUpPolicy";
+
+ private static final boolean DEBUG = false;
+
+ private final Context mContext;
+ private final PowerManager mPowerManager;
+
+ private final boolean mAllowTheaterModeWakeFromKey;
+ private final boolean mAllowTheaterModeWakeFromPowerKey;
+ private final boolean mAllowTheaterModeWakeFromMotion;
+ private final boolean mAllowTheaterModeWakeFromMotionWhenNotDreaming;
+ private final boolean mAllowTheaterModeWakeFromCameraLens;
+ private final boolean mAllowTheaterModeWakeFromLidSwitch;
+ private final boolean mAllowTheaterModeWakeFromWakeGesture;
+
+ WindowWakeUpPolicy(Context context) {
+ mContext = context;
+ mPowerManager = context.getSystemService(PowerManager.class);
+
+ final Resources res = context.getResources();
+ mAllowTheaterModeWakeFromKey = res.getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromKey);
+ mAllowTheaterModeWakeFromPowerKey = mAllowTheaterModeWakeFromKey
+ || res.getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey);
+ mAllowTheaterModeWakeFromMotion = res.getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion);
+ mAllowTheaterModeWakeFromMotionWhenNotDreaming = res.getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromMotionWhenNotDreaming);
+ mAllowTheaterModeWakeFromCameraLens = res.getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens);
+ mAllowTheaterModeWakeFromLidSwitch = res.getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch);
+ mAllowTheaterModeWakeFromWakeGesture = res.getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture);
+ }
+
+ /**
+ * Wakes up from a key event.
+ *
+ * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+ * @param keyCode the {@link android.view.KeyEvent} key code of the key event.
+ * @return {@code true} if the policy allows the requested wake up and the request has been
+ * executed; {@code false} otherwise.
+ */
+ boolean wakeUpFromKey(long eventTime, int keyCode) {
+ final boolean wakeAllowedDuringTheaterMode =
+ keyCode == KEYCODE_POWER
+ ? mAllowTheaterModeWakeFromPowerKey
+ : mAllowTheaterModeWakeFromKey;
+ return wakeUp(
+ eventTime,
+ wakeAllowedDuringTheaterMode,
+ keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
+ keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ }
+
+ /**
+ * Wakes up from a motion event.
+ *
+ * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+ * @return {@code true} if the policy allows the requested wake up and the request has been
+ * executed; {@code false} otherwise.
+ */
+ boolean wakeUpFromMotion(long eventTime) {
+ return wakeUp(
+ eventTime, mAllowTheaterModeWakeFromMotion, WAKE_REASON_WAKE_MOTION, "MOTION");
+ }
+
+ /**
+ * Wakes up due to an opened camera cover.
+ *
+ * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+ * @return {@code true} if the policy allows the requested wake up and the request has been
+ * executed; {@code false} otherwise.
+ */
+ boolean wakeUpFromCameraCover(long eventTime) {
+ return wakeUp(
+ eventTime,
+ mAllowTheaterModeWakeFromCameraLens,
+ WAKE_REASON_CAMERA_LAUNCH,
+ "CAMERA_COVER");
+ }
+
+ /**
+ * Wakes up due to an opened lid.
+ *
+ * @return {@code true} if the policy allows the requested wake up and the request has been
+ * executed; {@code false} otherwise.
+ */
+ boolean wakeUpFromLid() {
+ return wakeUp(
+ SystemClock.uptimeMillis(),
+ mAllowTheaterModeWakeFromLidSwitch,
+ WAKE_REASON_LID,
+ "LID");
+ }
+
+ /**
+ * Wakes up to prevent sleeping when opening camera through power button.
+ *
+ * @return {@code true} if the policy allows the requested wake up and the request has been
+ * executed; {@code false} otherwise.
+ */
+ boolean wakeUpFromPowerKeyCameraGesture() {
+ return wakeUp(
+ SystemClock.uptimeMillis(),
+ mAllowTheaterModeWakeFromPowerKey,
+ WAKE_REASON_CAMERA_LAUNCH,
+ "CAMERA_GESTURE_PREVENT_LOCK");
+ }
+
+ /**
+ * Wake up from a wake gesture.
+ *
+ * @return {@code true} if the policy allows the requested wake up and the request has been
+ * executed; {@code false} otherwise.
+ */
+ boolean wakeUpFromWakeGesture() {
+ return wakeUp(
+ SystemClock.uptimeMillis(),
+ mAllowTheaterModeWakeFromWakeGesture,
+ WAKE_REASON_GESTURE,
+ "GESTURE");
+ }
+
+ private boolean wakeUp(
+ long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason, String details) {
+ final boolean isTheaterModeEnabled =
+ Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) == 1;
+ if (!wakeInTheaterMode && isTheaterModeEnabled) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from " + details);
+ return false;
+ }
+ mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ca66a66057a3..6033220e260d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -152,6 +152,7 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.utils.Slogf;
+import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -795,6 +796,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
+ if (Flags.bundleClientTransactionFlag()) {
+ // mWmService.mResizingWindows is populated in #applySurfaceChangesTransaction()
+ handleResizingWindows();
+
+ // Called after #handleResizingWindows to include WindowStateResizeItem if any.
+ mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions();
+ }
+
// Send any pending task-info changes that were queued-up during a layout deferment
mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents();
@@ -838,12 +847,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
}
- handleResizingWindows();
+ if (!Flags.bundleClientTransactionFlag()) {
+ handleResizingWindows();
+ }
clearFrameChangingWindows();
- // Called after #handleResizingWindows to include WindowStateResizeItem if any.
- mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions();
-
if (mWmService.mDisplayFrozen) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"With display frozen, orientationChangeComplete=%b",
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dda33f3f501b..502912a98816 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5748,7 +5748,6 @@ public class WindowManagerService extends IWindowManager.Stub
case INSETS_CHANGED: {
synchronized (mGlobalLock) {
if (mWindowsInsetsChanged > 0) {
- mWindowsInsetsChanged = 0;
// We need to update resizing windows and dispatch the new insets state
// to them.
mWindowPlacerLocked.performSurfacePlacement();
@@ -6848,6 +6847,7 @@ public class WindowManagerService extends IWindowManager.Stub
pw.println(defaultDisplayContent.getLastOrientation());
pw.print(" mWaitingForConfig=");
pw.println(defaultDisplayContent.mWaitingForConfig);
+ pw.print(" mWindowsInsetsChanged="); pw.println(mWindowsInsetsChanged);
mRotationWatcherController.dump(pw);
pw.print(" Animation settings: disabled="); pw.print(mAnimationsDisabled);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 58ade1bf5c1e..949025c45287 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1443,16 +1443,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
this, mWindowFrames.getInsetsChangedInfo(),
configChanged, didFrameInsetsChange);
- if (insetsChanged) {
- mWindowFrames.setInsetsChanged(false);
- if (mWmService.mWindowsInsetsChanged > 0) {
- mWmService.mWindowsInsetsChanged--;
- }
- if (mWmService.mWindowsInsetsChanged == 0) {
- mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
- }
- }
-
+ consumeInsetsChange();
onResizeHandled();
mWmService.makeWindowFreezingScreenIfNeededLocked(this);
@@ -2349,6 +2340,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
getWindowToken());
+
+ consumeInsetsChange();
}
@Override
@@ -3722,6 +3715,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mClient instanceof IWindow.Stub;
}
+ private void consumeInsetsChange() {
+ if (mWindowFrames.hasInsetsChanged()) {
+ mWindowFrames.setInsetsChanged(false);
+ mWmService.mWindowsInsetsChanged--;
+ if (mWmService.mWindowsInsetsChanged == 0) {
+ mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
+ }
+ }
+ }
+
/**
* Called when the insets state changed.
*/
@@ -3729,10 +3732,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsChanged for %s ", this);
if (!mWindowFrames.hasInsetsChanged()) {
mWindowFrames.setInsetsChanged(true);
+ mWmService.mWindowsInsetsChanged++;
// If the new InsetsState won't be dispatched before releasing WM lock, the following
// message will be executed.
- mWmService.mWindowsInsetsChanged++;
mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED);
mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED);
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 4a2e1cba5cce..686b2a813bd3 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -65,7 +65,6 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.PackageMonitor;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
import com.android.server.infra.AbstractMasterSystemService;
@@ -1204,6 +1203,9 @@ public final class CredentialManagerService
// If the app being removed matches any of the package names from
// this list then don't add it in the output.
Set<String> providers = new HashSet<>();
+ if (rawProviders == null || packageName == null) {
+ return providers;
+ }
for (String rawComponentName : rawProviders.split(":")) {
if (TextUtils.isEmpty(rawComponentName)
|| rawComponentName.equals("null")) {
diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
index 6f9b6faa0bb0..8f5d1253406e 100644
--- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
+++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
@@ -62,7 +62,7 @@ import java.util.List;
import java.util.Set;
@RunWith(JUnit4.class)
-public class AudioPoliciesDeviceRouteControllerTest {
+public class AudioManagerRouteControllerTest {
private static final String FAKE_ROUTE_NAME = "fake name";
private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER =
@@ -89,7 +89,7 @@ public class AudioPoliciesDeviceRouteControllerTest {
private Set<AudioDeviceInfo> mAvailableAudioDeviceInfos;
@Mock private AudioManager mMockAudioManager;
@Mock private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
- private AudioPoliciesDeviceRouteController mControllerUnderTest;
+ private AudioManagerRouteController mControllerUnderTest;
private AudioDeviceCallback mAudioDeviceCallback;
private AudioProductStrategy mMediaAudioProductStrategy;
@@ -116,7 +116,7 @@ public class AudioPoliciesDeviceRouteControllerTest {
BluetoothAdapter btAdapter =
realContext.getSystemService(BluetoothManager.class).getAdapter();
mControllerUnderTest =
- new AudioPoliciesDeviceRouteController(
+ new AudioManagerRouteController(
mockContext,
mMockAudioManager,
Looper.getMainLooper(),
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index d2e83e9b0708..9eeb4f3f218f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -271,6 +271,7 @@ public class AuthenticationStatsCollectorTest {
.thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(null);
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
diff --git a/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
index fd1abff8610b..d850c73ebc26 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
@@ -53,6 +53,12 @@ public final class CredentialManagerServiceTest {
}
@Test
+ public void getStoredProviders_nullValue_success() {
+ Set<String> providers = CredentialManagerService.getStoredProviders(null, null);
+ assertThat(providers.size()).isEqualTo(0);
+ }
+
+ @Test
public void getStoredProviders_success() {
Set<String> providers =
CredentialManagerService.getStoredProviders(
diff --git a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
deleted file mode 100644
index 06f117bdbd65..000000000000
--- a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
-
-import android.content.Context;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.google.common.truth.Truth;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class BluetoothRouteControllerTest {
-
- private final BluetoothRouteController.BluetoothRoutesUpdatedListener
- mBluetoothRoutesUpdatedListener =
- () -> {
- // Empty on purpose.
- };
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- private Context mContext;
-
- @Before
- public void setUp() {
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
- }
-
- @Test
- @RequiresFlagsDisabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
- public void createInstance_audioPoliciesFlagIsDisabled_createsLegacyController() {
- BluetoothRouteController deviceRouteController =
- BluetoothRouteController.createInstance(mContext, mBluetoothRoutesUpdatedListener);
-
- Truth.assertThat(deviceRouteController).isInstanceOf(LegacyBluetoothRouteController.class);
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
- public void createInstance_audioPoliciesFlagIsEnabled_createsAudioPoliciesController() {
- BluetoothRouteController deviceRouteController =
- BluetoothRouteController.createInstance(mContext, mBluetoothRoutesUpdatedListener);
-
- Truth.assertThat(deviceRouteController)
- .isInstanceOf(AudioPoliciesBluetoothRouteController.class);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
index 0961b7d97177..eb789615b978 100644
--- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -70,7 +70,6 @@ public class DeviceRouteControllerTest {
DeviceRouteController.createInstance(
mContext, Looper.getMainLooper(), mOnDeviceRouteChangedListener);
- Truth.assertThat(deviceRouteController)
- .isInstanceOf(AudioPoliciesDeviceRouteController.class);
+ Truth.assertThat(deviceRouteController).isInstanceOf(AudioManagerRouteController.class);
}
}
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 3ab7496eff84..e1b1bff62031 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -16,11 +16,14 @@
package com.android.server.notification;
+import static android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE;
import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.SHOW_IMMEDIATELY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
import static android.app.Notification.EXTRA_PICTURE;
import static android.app.Notification.EXTRA_PICTURE_ICON;
@@ -60,6 +63,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BA
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
+import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
+import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.PackageManager.FEATURE_TELECOM;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -118,6 +123,7 @@ import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Matchers.anyBoolean;
@@ -547,6 +553,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
mContext.addMockSystemService(NotificationManager.class, mMockNm);
+ doNothing().when(mContext).sendBroadcast(any(), anyString());
doNothing().when(mContext).sendBroadcastAsUser(any(), any());
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
@@ -909,7 +916,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
private ApplicationInfo getApplicationInfo(String pkg, int uid) {
final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = pkg;
applicationInfo.uid = uid;
+ applicationInfo.sourceDir = mContext.getApplicationInfo().sourceDir;
switch (pkg) {
case PKG_N_MR1:
applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1;
@@ -5535,15 +5544,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testBumpFGImportance_channelChangePreOApp() throws Exception {
- String preOPkg = PKG_N_MR1;
- final ApplicationInfo legacy = new ApplicationInfo();
- legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
- when(mPackageManagerClient.getApplicationInfoAsUser(eq(preOPkg), anyInt(), anyInt()))
- .thenReturn(legacy);
- when(mPackageManagerClient.getPackageUidAsUser(eq(preOPkg), anyInt()))
- .thenReturn(Binder.getCallingUid());
- getContext().setMockPackageManager(mPackageManagerClient);
-
Notification.Builder nb = new Notification.Builder(mContext,
NotificationChannel.DEFAULT_CHANNEL_ID)
.setContentTitle("foo")
@@ -5551,7 +5551,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setFlag(FLAG_FOREGROUND_SERVICE, true)
.setPriority(Notification.PRIORITY_MIN);
- StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
+ StatusBarNotification sbn = new StatusBarNotification(PKG_N_MR1, PKG_N_MR1, 9,
"testBumpFGImportance_channelChangePreOApp",
Binder.getCallingUid(), 0, nb.build(),
UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0);
@@ -5571,11 +5571,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setFlag(FLAG_FOREGROUND_SERVICE, true)
.setPriority(Notification.PRIORITY_MIN);
- sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
+ sbn = new StatusBarNotification(PKG_N_MR1, PKG_N_MR1, 9,
"testBumpFGImportance_channelChangePreOApp", Binder.getCallingUid(),
0, nb.build(), UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0);
- mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg,
+ mBinderService.enqueueNotificationWithTag(PKG_N_MR1, PKG_N_MR1,
"testBumpFGImportance_channelChangePreOApp",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
waitForIdle();
@@ -5583,7 +5583,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.getNotificationRecord(sbn.getKey()).getImportance());
NotificationChannel defaultChannel = mBinderService.getNotificationChannel(
- preOPkg, mContext.getUserId(), preOPkg, NotificationChannel.DEFAULT_CHANNEL_ID);
+ PKG_N_MR1, mContext.getUserId(), PKG_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID);
assertEquals(IMPORTANCE_LOW, defaultChannel.getImportance());
}
@@ -11266,6 +11266,40 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testMigrateNotificationFilter_invalidPackage() throws Exception {
+ int[] userIds = new int[] {mUserId, 1000};
+ when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
+ List<String> disallowedApps = ImmutableList.of("apples", "bananas", "cherries");
+ for (int userId : userIds) {
+ when(mPackageManager.getPackageUid("apples", 0, userId)).thenThrow(
+ new RemoteException(""));
+ when(mPackageManager.getPackageUid("bananas", 0, userId)).thenReturn(9000);
+ when(mPackageManager.getPackageUid("cherries", 0, userId)).thenReturn(9001);
+ }
+
+ when(mListeners.getNotificationListenerFilter(any())).thenReturn(
+ new NotificationListenerFilter());
+
+ mBinderService.migrateNotificationFilter(null,
+ FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ONGOING,
+ disallowedApps);
+
+ ArgumentCaptor<NotificationListenerFilter> captor =
+ ArgumentCaptor.forClass(NotificationListenerFilter.class);
+ verify(mListeners).setNotificationListenerFilter(any(), captor.capture());
+
+ assertEquals(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ONGOING,
+ captor.getValue().getTypes());
+ // valid values stay
+ assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("bananas", 9000)));
+ assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("cherries", 9001)));
+ // don't store invalid values
+ for (VersionedPackage vp : captor.getValue().getDisallowedPackages()) {
+ assertNotEquals("apples", vp.getPackageName());
+ }
+ }
+
+ @Test
public void testMigrateNotificationFilter_noPreexistingFilter() throws Exception {
int[] userIds = new int[] {mUserId};
when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
@@ -14059,6 +14093,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
any(), any());
}
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+ public void testSetPrivateNotificationsAllowed() throws Exception {
+ when(mContext.checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS))
+ .thenReturn(PERMISSION_GRANTED);
+ mBinderService.setPrivateNotificationsAllowed(false);
+ Intent expected = new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
+ .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false);
+ ArgumentCaptor<Intent> actual = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendBroadcast(actual.capture(), eq(STATUS_BAR_SERVICE));
+
+ assertEquals(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED, actual.getValue().getAction());
+ assertFalse(actual.getValue().getBooleanExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, true));
+ assertFalse(mBinderService.getPrivateNotificationsAllowed());
+ }
+
private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
throws RemoteException {
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
index 1fcee0658afc..5b35e345e46b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
@@ -118,10 +118,13 @@ public class ZenModeEventLoggerFake extends ZenModeEventLogger {
public DNDPolicyProto getPolicyProto(int i) throws IllegalArgumentException {
checkInRange(i);
byte[] policyBytes = mChanges.get(i).getDNDPolicyProto();
+ if (policyBytes == null) {
+ return null;
+ }
try {
return DNDPolicyProto.parseFrom(policyBytes);
} catch (InvalidProtocolBufferException e) {
- return null; // couldn't turn it into proto
+ throw new RuntimeException("Couldn't parse DNDPolicyProto!", e);
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 44f0894f76d7..25c0cd9fae25 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -2429,7 +2429,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
assertFalse(mZenModeEventLogger.getIsUserAction(1));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
- checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
+ if (Flags.modesApi()) {
+ assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
+ } else {
+ checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
+ }
}
@Test
@@ -2511,7 +2515,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
assertTrue(mZenModeEventLogger.getIsUserAction(1));
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
- checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
+ if (Flags.modesApi()) {
+ assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
+ } else {
+ checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
+ }
// When the system rule is enabled, this counts as an automatic action that comes from the
// system and turns on DND
@@ -3016,6 +3024,48 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testZenModeEventLog_ruleWithInterruptionFilterAll_notLoggedAsDndChange() {
+ mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
+ setupZenConfig();
+
+ // An app adds an automatic zen rule
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "cls"),
+ Uri.parse("condition"),
+ null,
+ NotificationManager.INTERRUPTION_FILTER_ALL, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+
+ // Event 1: App activates the rule automatically.
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ // Event 2: App deactivates the rule automatically.
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ // In total, this represents 2 events.
+ assertEquals(2, mZenModeEventLogger.numLoggedChanges());
+
+ // However, they are not DND_TURNED_ON/_OFF (no notification filtering is taking place).
+ // Also, no consolidated ZenPolicy is logged (because of the same reason).
+ assertThat(mZenModeEventLogger.getEventId(0)).isEqualTo(
+ ZenModeEventLogger.ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED.getId());
+ assertThat(mZenModeEventLogger.getNumRulesActive(0)).isEqualTo(1);
+ assertThat(mZenModeEventLogger.getPolicyProto(0)).isNull();
+
+ assertThat(mZenModeEventLogger.getEventId(1)).isEqualTo(
+ ZenModeEventLogger.ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED.getId());
+ assertThat(mZenModeEventLogger.getNumRulesActive(1)).isEqualTo(0);
+ assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
+ }
+
+ @Test
public void testUpdateConsolidatedPolicy_defaultRulesOnly() {
setupZenConfig();
@@ -3203,6 +3253,52 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testUpdateConsolidatedPolicy_ignoresActiveRulesWithInterruptionFilterAll() {
+ setupZenConfig();
+
+ // Rules with INTERRUPTION_FILTER_ALL are skipped when calculating consolidated policy.
+ // Note: rules with filter != PRIORITY should not have a custom policy. However, as of V
+ // this is only validated on rule addition, but not on rule update. :/
+
+ // Rule 1: PRIORITY, custom policy but not very strict (in fact, less strict than default).
+ AutomaticZenRule zenRuleWithPriority = new AutomaticZenRule("Priority",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "cls"),
+ Uri.parse("priority"),
+ new ZenPolicy.Builder().allowMedia(true).build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String rule1Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ zenRuleWithPriority, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(rule1Id,
+ new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ // Rule 2: ALL, but somehow with a super strict ZenPolicy.
+ AutomaticZenRule zenRuleWithAll = new AutomaticZenRule("All",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "cls"),
+ Uri.parse("priority"),
+ new ZenPolicy.Builder().disallowAllSounds().build(),
+ NotificationManager.INTERRUPTION_FILTER_ALL, true);
+ String rule2Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ zenRuleWithAll, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(rule2Id,
+ new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ // Consolidated Policy should be default + rule1.
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isFalse(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // priority rule
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isFalse(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isTrue(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isTrue(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()).isTrue(); // default
+ }
+
+ @Test
public void zenRuleToAutomaticZenRule_allFields() {
mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
index bfc17713b6a8..336bfdd0fb14 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
@@ -35,6 +35,7 @@ import android.util.Slog;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.Keep;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.usage.BroadcastResponseStatsTracker.NotificationEventType;
@@ -178,6 +179,7 @@ public class BroadcastResponseStatsLogger {
}
}
+ @Keep
public static final class BroadcastEvent implements Data {
public int sourceUid;
public int targetUserId;
@@ -198,6 +200,7 @@ public class BroadcastResponseStatsLogger {
}
}
+ @Keep
public static final class NotificationEvent implements Data {
public int type;
public String packageName;