summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Dialog.java15
-rw-r--r--core/java/android/app/Notification.java23
-rw-r--r--core/java/android/app/TaskInfo.java16
-rw-r--r--core/java/android/content/pm/CrossProfileApps.java9
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl2
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java6
-rw-r--r--core/java/android/inputmethodservice/NavigationBarController.java34
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java7
-rw-r--r--core/java/android/os/VibrationEffect.java12
-rw-r--r--core/java/android/os/Vibrator.java6
-rw-r--r--core/java/android/os/VibratorInfo.java28
-rw-r--r--core/java/android/os/storage/StorageManagerInternal.java16
-rw-r--r--core/java/android/os/vibrator/PrebakedSegment.java5
-rw-r--r--core/java/android/os/vibrator/PrimitiveSegment.java6
-rw-r--r--core/java/android/os/vibrator/RampSegment.java8
-rw-r--r--core/java/android/os/vibrator/StepSegment.java8
-rw-r--r--core/java/android/os/vibrator/VibrationEffectSegment.java4
-rw-r--r--core/java/android/security/OWNERS3
-rw-r--r--core/java/android/security/keystore/OWNERS6
-rw-r--r--core/java/android/security/keystore/recovery/OWNERS5
-rw-r--r--core/java/android/speech/OWNERS3
-rw-r--r--core/java/android/text/DynamicLayout.java4
-rw-r--r--core/java/android/text/StaticLayout.java53
-rw-r--r--core/java/android/view/InsetsController.java48
-rw-r--r--core/java/android/view/InsetsSource.java10
-rw-r--r--core/java/android/view/PendingInsetsController.java9
-rw-r--r--core/java/android/view/WindowInsetsController.java10
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java14
-rw-r--r--core/java/com/android/internal/widget/MessagingImageMessage.java33
-rw-r--r--core/java/com/android/internal/widget/MessagingLayout.java12
-rw-r--r--core/java/com/android/internal/widget/MessagingMessage.java17
-rw-r--r--core/java/com/android/internal/widget/MessagingTextMessage.java44
-rw-r--r--core/res/res/drawable/focus_event_rotary_input_background.xml30
-rw-r--r--core/res/res/values/config.xml62
-rw-r--r--core/res/res/values/strings.xml16
-rw-r--r--core/res/res/values/symbols.xml13
-rw-r--r--core/tests/coretests/Android.bp2
-rw-r--r--core/tests/coretests/certs/Android.bp5
-rw-r--r--core/tests/coretests/certs/README2
-rw-r--r--core/tests/coretests/certs/unit_test.derbin0 -> 802 bytes
-rw-r--r--core/tests/coretests/res/raw/fsverity_sigbin672 -> 392 bytes
-rw-r--r--core/tests/coretests/src/android/security/keystore/OWNERS1
-rw-r--r--core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java7
-rw-r--r--core/tests/vibrator/src/android/os/VibrationEffectTest.java29
-rw-r--r--core/tests/vibrator/src/android/os/VibratorInfoTest.java193
-rw-r--r--core/tests/vibrator/src/android/os/VibratorTest.java200
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java93
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java35
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java137
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java84
-rw-r--r--graphics/java/android/graphics/fonts/SystemFonts.java16
-rw-r--r--keystore/OWNERS2
-rw-r--r--keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java33
-rw-r--r--libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml34
-rw-r--r--libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml20
-rw-r--r--libs/WindowManager/Shell/res/layout/compat_ui_layout.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml41
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java173
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java136
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java211
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java49
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java154
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java350
-rw-r--r--libs/androidfw/OWNERS2
-rw-r--r--packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig2
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java254
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt1
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt24
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt3
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java38
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml37
-rw-r--r--packages/SystemUI/res/layout/combined_qs_header.xml2
-rw-r--r--packages/SystemUI/res/layout/screen_share_dialog.xml3
-rw-r--r--packages/SystemUI/res/values-sw600dp/dimens.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/flags.xml2
-rw-r--r--packages/SystemUI/res/xml/large_screen_shade_header.xml2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt85
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java35
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java23
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java18
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java28
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistManager.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt101
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt117
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/model/RemoteUserInput.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java27
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt188
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt113
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt61
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt147
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt307
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt255
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt92
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java56
-rw-r--r--packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt67
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt7
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java37
-rw-r--r--services/core/java/com/android/server/biometrics/AuthenticationStats.java32
-rw-r--r--services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java104
-rw-r--r--services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java215
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java36
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java38
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java115
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java7
-rw-r--r--services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java104
-rw-r--r--services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java28
-rw-r--r--services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java22
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java13
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java7
-rw-r--r--services/core/java/com/android/server/input/FocusEventDebugView.java129
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java2
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java9
-rw-r--r--services/core/java/com/android/server/net/watchlist/OWNERS1
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java47
-rw-r--r--services/core/java/com/android/server/pm/Computer.java2
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java16
-rw-r--r--services/core/java/com/android/server/pm/IPackageManagerBase.java7
-rw-r--r--services/core/java/com/android/server/pm/MovePackageHelper.java38
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerInternalBase.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java33
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java21
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java12
-rw-r--r--services/core/java/com/android/server/wm/OWNERS2
-rw-r--r--services/core/java/com/android/server/wm/StartingData.java18
-rw-r--r--services/core/java/com/android/server/wm/Task.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java12
-rw-r--r--services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt2
-rw-r--r--services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java38
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java229
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java252
-rw-r--r--services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java94
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java19
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java34
-rw-r--r--telecomm/java/android/telecom/Connection.java6
-rw-r--r--telecomm/java/android/telecom/RemoteConnectionManager.java49
-rw-r--r--tests/Input/src/com/android/server/input/FocusEventDebugViewTest.java118
-rw-r--r--tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt59
-rw-r--r--tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt149
-rw-r--r--tools/aapt/Command.cpp4
-rw-r--r--tools/hiddenapi/OWNERS1
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java2
246 files changed, 6531 insertions, 2128 deletions
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 4851279eea97..d0d76a4c8285 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -454,12 +454,11 @@ public class Dialog implements DialogInterface, Window.Callback,
*/
protected void onStart() {
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
- if (mContext != null
+ if (allowsRegisterDefaultOnBackInvokedCallback() && mContext != null
&& WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
// Add onBackPressed as default back behavior.
mDefaultBackCallback = this::onBackPressed;
getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
- mDefaultBackCallback = null;
}
}
@@ -470,9 +469,18 @@ public class Dialog implements DialogInterface, Window.Callback,
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
if (mDefaultBackCallback != null) {
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback);
+ mDefaultBackCallback = null;
}
}
+ /**
+ * Whether this dialog allows to register the default onBackInvokedCallback.
+ * @hide
+ */
+ protected boolean allowsRegisterDefaultOnBackInvokedCallback() {
+ return true;
+ }
+
private static final String DIALOG_SHOWING_TAG = "android:dialogShowing";
private static final String DIALOG_HIERARCHY_TAG = "android:dialogHierarchy";
@@ -697,7 +705,8 @@ public class Dialog implements DialogInterface, Window.Callback,
if (event.isTracking() && !event.isCanceled()) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
- if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
+ if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)
+ || !allowsRegisterDefaultOnBackInvokedCallback()) {
onBackPressed();
return true;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 892b45ed2292..7d2ef4d9b943 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -12819,7 +12819,6 @@ public class Notification implements Parcelable
} else {
mBackgroundColor = rawColor;
}
- mProtectionColor = COLOR_INVALID; // filled in at the end
mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
mBackgroundColor, 4.5);
@@ -12836,7 +12835,6 @@ public class Notification implements Parcelable
} else {
int[] attrs = {
R.attr.colorSurface,
- R.attr.colorBackgroundFloating,
R.attr.textColorPrimary,
R.attr.textColorSecondary,
R.attr.colorAccent,
@@ -12848,15 +12846,14 @@ public class Notification implements Parcelable
};
try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE);
- mProtectionColor = getColor(ta, 1, COLOR_INVALID);
- mPrimaryTextColor = getColor(ta, 2, COLOR_INVALID);
- mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID);
- mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID);
- mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID);
- mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID);
- mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID);
- mErrorColor = getColor(ta, 8, COLOR_INVALID);
- mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff));
+ mPrimaryTextColor = getColor(ta, 1, COLOR_INVALID);
+ mSecondaryTextColor = getColor(ta, 2, COLOR_INVALID);
+ mPrimaryAccentColor = getColor(ta, 3, COLOR_INVALID);
+ mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID);
+ mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID);
+ mOnAccentTextColor = getColor(ta, 6, COLOR_INVALID);
+ mErrorColor = getColor(ta, 7, COLOR_INVALID);
+ mRippleAlpha = Color.alpha(getColor(ta, 8, 0x33ffffff));
}
mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
mBackgroundColor, nightMode);
@@ -12889,9 +12886,7 @@ public class Notification implements Parcelable
}
}
// make sure every color has a valid value
- if (mProtectionColor == COLOR_INVALID) {
- mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.8f);
- }
+ mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.9f);
}
/** calculates the contrast color for the non-colorized notifications */
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 2b5175ca6659..634089b73618 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -248,6 +248,13 @@ public class TaskInfo {
public boolean topActivityEligibleForUserAspectRatioButton;
/**
+ * Whether the user has forced the activity to be fullscreen through the user aspect ratio
+ * settings.
+ * @hide
+ */
+ public boolean isUserFullscreenOverrideEnabled;
+
+ /**
* Hint about the letterbox state of the top activity.
* @hide
*/
@@ -543,7 +550,8 @@ public class TaskInfo {
&& isSleeping == that.isSleeping
&& Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
&& parentTaskId == that.parentTaskId
- && Objects.equals(topActivity, that.topActivity);
+ && Objects.equals(topActivity, that.topActivity)
+ && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled;
}
/**
@@ -574,7 +582,8 @@ public class TaskInfo {
&& (!hasCompatUI() || configuration.getLayoutDirection()
== that.configuration.getLayoutDirection())
&& (!hasCompatUI() || configuration.uiMode == that.configuration.uiMode)
- && (!hasCompatUI() || isVisible == that.isVisible);
+ && (!hasCompatUI() || isVisible == that.isVisible)
+ && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled;
}
/**
@@ -630,6 +639,7 @@ public class TaskInfo {
topActivityLetterboxHorizontalPosition = source.readInt();
topActivityLetterboxWidth = source.readInt();
topActivityLetterboxHeight = source.readInt();
+ isUserFullscreenOverrideEnabled = source.readBoolean();
}
/**
@@ -686,6 +696,7 @@ public class TaskInfo {
dest.writeInt(topActivityLetterboxHorizontalPosition);
dest.writeInt(topActivityLetterboxWidth);
dest.writeInt(topActivityLetterboxHeight);
+ dest.writeBoolean(isUserFullscreenOverrideEnabled);
}
@Override
@@ -732,6 +743,7 @@ public class TaskInfo {
+ topActivityLetterboxHorizontalPosition
+ " topActivityLetterboxWidth=" + topActivityLetterboxWidth
+ " topActivityLetterboxHeight=" + topActivityLetterboxHeight
+ + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled
+ " locusId=" + mTopActivityLocusId
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " cameraCompatControlState="
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index c3df17d4b53e..529363f828bb 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -344,15 +344,22 @@ public class CrossProfileApps {
// If there is a label for the launcher intent, then use that as it is typically shorter.
// Otherwise, just use the top-level application name.
Intent launchIntent = pm.getLaunchIntentForPackage(mContext.getPackageName());
+ if (launchIntent == null) {
+ return getDefaultCallingApplicationLabel();
+ }
List<ResolveInfo> infos =
pm.queryIntentActivities(
launchIntent, PackageManager.ResolveInfoFlags.of(MATCH_DEFAULT_ONLY));
if (infos.size() > 0) {
return infos.get(0).loadLabel(pm);
}
+ return getDefaultCallingApplicationLabel();
+ }
+
+ private CharSequence getDefaultCallingApplicationLabel() {
return mContext.getApplicationInfo()
.loadSafeLabel(
- pm,
+ mContext.getPackageManager(),
/* ellipsizeDip= */ 0,
TextUtils.SAFE_STRING_FLAG_SINGLE_LINE
| TextUtils.SAFE_STRING_FLAG_TRIM);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c5585afb143a..ea0f5ff2896e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -304,6 +304,8 @@ interface IPackageManager {
boolean isPackageSuspendedForUser(String packageName, int userId);
+ boolean isPackageQuarantinedForUser(String packageName, int userId);
+
Bundle getSuspendedPackageAppExtras(String packageName, int userId);
/**
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 795eb4a737ef..8f653b3808c1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -576,6 +576,12 @@ public class InputMethodService extends AbstractInputMethodService {
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public static final long DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE = 148086656L;
+ /**
+ * Enable the logic to allow hiding the IME caption bar ("fake" IME navigation bar).
+ * @hide
+ */
+ public static final boolean ENABLE_HIDE_IME_CAPTION_BAR = true;
+
LayoutInflater mInflater;
TypedArray mThemeAttrs;
@UnsupportedAppUsage
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 78388efe98c7..c01664e55744 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -16,6 +16,8 @@
package android.inputmethodservice;
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
+import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import android.animation.ValueAnimator;
@@ -230,6 +232,16 @@ final class NavigationBarController {
setIconTintInternal(calculateTargetDarkIntensity(mAppearance,
mDrawLegacyNavigationBarBackground));
+
+ if (ENABLE_HIDE_IME_CAPTION_BAR) {
+ mNavigationBarFrame.setOnApplyWindowInsetsListener((view, insets) -> {
+ if (mNavigationBarFrame != null) {
+ boolean visible = insets.isVisible(captionBar());
+ mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ return view.onApplyWindowInsets(insets);
+ });
+ }
}
private void uninstallNavigationBarFrameIfNecessary() {
@@ -240,6 +252,9 @@ final class NavigationBarController {
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(mNavigationBarFrame);
}
+ if (ENABLE_HIDE_IME_CAPTION_BAR) {
+ mNavigationBarFrame.setOnApplyWindowInsetsListener(null);
+ }
mNavigationBarFrame = null;
}
@@ -414,7 +429,9 @@ final class NavigationBarController {
decor.bringChildToFront(mNavigationBarFrame);
}
}
- mNavigationBarFrame.setVisibility(View.VISIBLE);
+ if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+ mNavigationBarFrame.setVisibility(View.VISIBLE);
+ }
}
}
@@ -435,6 +452,11 @@ final class NavigationBarController {
mShouldShowImeSwitcherWhenImeIsShown;
mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
+ if (ENABLE_HIDE_IME_CAPTION_BAR) {
+ mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
+ .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight());
+ }
+
if (imeDrawsImeNavBar) {
installNavigationBarFrameIfNecessary();
if (mNavigationBarFrame == null) {
@@ -528,6 +550,16 @@ final class NavigationBarController {
return drawLegacyNavigationBarBackground;
}
+ /**
+ * Returns the height of the IME caption bar if this should be shown, or {@code 0} instead.
+ */
+ private int getImeCaptionBarHeight() {
+ return mImeDrawsImeNavBar
+ ? mService.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_frame_height)
+ : 0;
+ }
+
@Override
public String toDebugString() {
return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 5704dac7a327..e4a09a651ae1 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -79,6 +79,13 @@ final class SoftInputWindow extends Dialog {
@WindowState
private int mWindowState = WindowState.TOKEN_PENDING;
+ @Override
+ protected boolean allowsRegisterDefaultOnBackInvokedCallback() {
+ // Do not register OnBackInvokedCallback from Dialog#onStart, InputMethodService will
+ // register CompatOnBackInvokedCallback for input method window.
+ return false;
+ }
+
/**
* Set {@link IBinder} window token to the window.
*
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index b24b45d11c3a..08b32bf2b9e0 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -525,14 +525,14 @@ public abstract class VibrationEffect implements Parcelable {
public abstract long getDuration();
/**
- * Checks if a given {@link Vibrator} can play this effect as intended.
+ * Checks if a vibrator with a given {@link VibratorInfo} can play this effect as intended.
*
- * <p>See @link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more information
- * about what counts as supported by a vibrator, and what counts as not.
+ * <p>See {@link VibratorInfo#areVibrationFeaturesSupported(VibrationEffect)} for more
+ * information about what counts as supported by a vibrator, and what counts as not.
*
* @hide
*/
- public abstract boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator);
+ public abstract boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo);
/**
* Returns true if this effect could represent a touch haptic feedback.
@@ -813,9 +813,9 @@ public abstract class VibrationEffect implements Parcelable {
/** @hide */
@Override
- public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+ public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
for (VibrationEffectSegment segment : mSegments) {
- if (!segment.areVibrationFeaturesSupported(vibrator)) {
+ if (!segment.areVibrationFeaturesSupported(vibratorInfo)) {
return false;
}
}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 4e852e333ec8..79e0ca87eade 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -216,9 +216,7 @@ public abstract class Vibrator {
*/
@TestApi
public boolean hasFrequencyControl() {
- // We currently can only control frequency of the vibration using the compose PWLE method.
- return getInfo().hasCapability(
- IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ return getInfo().hasFrequencyControl();
}
/**
@@ -240,7 +238,7 @@ public abstract class Vibrator {
* @hide
*/
public boolean areVibrationFeaturesSupported(@NonNull VibrationEffect effect) {
- return effect.areVibrationFeaturesSupported(this);
+ return getInfo().areVibrationFeaturesSupported(effect);
}
/**
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 02e685699398..0b7d7c3cb877 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -242,6 +242,17 @@ public class VibratorInfo implements Parcelable {
}
/**
+ * Check whether the vibrator has frequency control.
+ *
+ * @return True if the hardware can control the frequency of the vibrations, otherwise false.
+ */
+ public boolean hasFrequencyControl() {
+ // We currently can only control frequency of the vibration using the compose PWLE method.
+ return hasCapability(
+ IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ }
+
+ /**
* Returns a default value to be applied to composed PWLE effects for braking.
*
* @return a supported braking value, one of android.hardware.vibrator.Braking.*
@@ -323,6 +334,23 @@ public class VibratorInfo implements Parcelable {
}
/**
+ * Query whether or not the vibrator supports all components of a given {@link VibrationEffect}
+ * (i.e. the vibrator can play the given effect as intended).
+ *
+ * <p>See {@link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more
+ * information on how the vibrator support is determined.
+ *
+ * @param effect the {@link VibrationEffect} to check if it is supported
+ * @return {@code true} if the vibrator can play the given {@code effect} as intended,
+ * {@code false} otherwise.
+ *
+ * @hide
+ */
+ public boolean areVibrationFeaturesSupported(@NonNull VibrationEffect effect) {
+ return effect.areVibrationFeaturesSupported(this);
+ }
+
+ /**
* Query the estimated duration of given primitive.
*
* @param primitiveId Which primitives to query for.
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 059bd846327c..22e8251b3f52 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -19,6 +19,7 @@ package android.os.storage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
import android.os.IVold;
import java.util.List;
@@ -169,4 +170,19 @@ public abstract class StorageManagerInternal {
*/
public abstract void registerCloudProviderChangeListener(
@NonNull CloudProviderChangeListener listener);
+
+ /**
+ * Prepares user data directories before moving storage or apps. This is required as adoptable
+ * storage unlock is tied to the prepare user data and storage needs to be unlocked before
+ * performing any operations on it. This will also create user data directories before
+ * initiating the move operations, which essential for ensuring the directories to have correct
+ * SELinux labels and permissions.
+ *
+ * @param fromVolumeUuid the source volume UUID from which content needs to be transferred
+ * @param toVolumeUuid the destination volume UUID to which contents are to be transferred
+ * @param users a list of users for whom to prepare storage
+ */
+ public abstract void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
+ List<UserInfo> users);
+
}
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index 42b6c2dae5eb..a035092e314f 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -23,6 +23,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.os.VibratorInfo;
import java.util.Objects;
@@ -77,8 +78,8 @@ public final class PrebakedSegment extends VibrationEffectSegment {
/** @hide */
@Override
- public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
- if (vibrator.areAllEffectsSupported(mEffectId) == Vibrator.VIBRATION_EFFECT_SUPPORT_YES) {
+ public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
+ if (vibratorInfo.isEffectSupported(mEffectId) == Vibrator.VIBRATION_EFFECT_SUPPORT_YES) {
return true;
}
if (!mFallback) {
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index c52a09ce69c7..95d97bfe4ad1 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -22,7 +22,7 @@ import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
import com.android.internal.util.Preconditions;
@@ -77,8 +77,8 @@ public final class PrimitiveSegment extends VibrationEffectSegment {
/** @hide */
@Override
- public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
- return vibrator.areAllPrimitivesSupported(mPrimitiveId);
+ public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
+ return vibratorInfo.isPrimitiveSupported(mPrimitiveId);
}
/** @hide */
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index e997bcdbdb32..5f9d1024d9a5 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -20,7 +20,7 @@ import android.annotation.NonNull;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
import com.android.internal.util.Preconditions;
@@ -96,7 +96,7 @@ public final class RampSegment extends VibrationEffectSegment {
/** @hide */
@Override
- public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+ public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
boolean areFeaturesSupported = true;
// If the start/end frequencies are not the same, require frequency control since we need to
// ramp up/down the frequency.
@@ -104,7 +104,7 @@ public final class RampSegment extends VibrationEffectSegment {
// If there is no frequency ramping, make sure that the one frequency used does not
// require frequency control.
|| frequencyRequiresFrequencyControl(mStartFrequencyHz)) {
- areFeaturesSupported &= vibrator.hasFrequencyControl();
+ areFeaturesSupported &= vibratorInfo.hasFrequencyControl();
}
// If the start/end amplitudes are not the same, require amplitude control since we need to
// ramp up/down the amplitude.
@@ -112,7 +112,7 @@ public final class RampSegment extends VibrationEffectSegment {
// If there is no amplitude ramping, make sure that the amplitude used does not
// require amplitude control.
|| amplitudeRequiresAmplitudeControl(mStartAmplitude)) {
- areFeaturesSupported &= vibrator.hasAmplitudeControl();
+ areFeaturesSupported &= vibratorInfo.hasAmplitudeControl();
}
return areFeaturesSupported;
}
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index a585aa866ed8..9576a5bba1f1 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -21,7 +21,7 @@ import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
import com.android.internal.util.Preconditions;
@@ -82,13 +82,13 @@ public final class StepSegment extends VibrationEffectSegment {
/** @hide */
@Override
- public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+ public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
boolean areFeaturesSupported = true;
if (frequencyRequiresFrequencyControl(mFrequencyHz)) {
- areFeaturesSupported &= vibrator.hasFrequencyControl();
+ areFeaturesSupported &= vibratorInfo.hasFrequencyControl();
}
if (amplitudeRequiresAmplitudeControl(mAmplitude)) {
- areFeaturesSupported &= vibrator.hasAmplitudeControl();
+ areFeaturesSupported &= vibratorInfo.hasAmplitudeControl();
}
return areFeaturesSupported;
}
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 3b286a7228fb..17ac36f3ab37 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -21,7 +21,7 @@ import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
/**
* Representation of a single segment of a {@link VibrationEffect}.
@@ -65,7 +65,7 @@ public abstract class VibrationEffectSegment implements Parcelable {
*
* @hide
*/
- public abstract boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator);
+ public abstract boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo);
/**
* Returns true if this segment could be a haptic feedback effect candidate.
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index f6b123554367..4e8d6e70091c 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -6,5 +6,4 @@ per-file NetworkSecurityPolicy.java = cbrubaker@google.com
per-file NetworkSecurityPolicy.java = klyubin@google.com
per-file FrameworkNetworkSecurityPolicy.java = cbrubaker@google.com
per-file FrameworkNetworkSecurityPolicy.java = klyubin@google.com
-per-file Confirmation*.java = jdanis@google.com
-per-file Confirmation*.java = swillden@google.com
+per-file Confirmation*.java = file:/keystore/OWNERS
diff --git a/core/java/android/security/keystore/OWNERS b/core/java/android/security/keystore/OWNERS
index 65129a46d113..d9e01161ce6d 100644
--- a/core/java/android/security/keystore/OWNERS
+++ b/core/java/android/security/keystore/OWNERS
@@ -1,5 +1 @@
-# Bug component: 189335
-
-swillden@google.com
-jdanis@google.com
-jbires@google.com
+include /keystore/OWNERS
diff --git a/core/java/android/security/keystore/recovery/OWNERS b/core/java/android/security/keystore/recovery/OWNERS
deleted file mode 100644
index 65129a46d113..000000000000
--- a/core/java/android/security/keystore/recovery/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 189335
-
-swillden@google.com
-jdanis@google.com
-jbires@google.com
diff --git a/core/java/android/speech/OWNERS b/core/java/android/speech/OWNERS
index 162e02904075..0f2f8ad3d99e 100644
--- a/core/java/android/speech/OWNERS
+++ b/core/java/android/speech/OWNERS
@@ -1,5 +1,4 @@
volnov@google.com
eugeniom@google.com
schfan@google.com
-andreaambu@google.com
-hackz@google.com \ No newline at end of file
+hackz@google.com
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 3bdaca9a9ba5..e287bd9165ce 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -622,7 +622,7 @@ public class DynamicLayout extends Layout {
sBuilder = null;
}
- if (reflowed == null) {
+ if (b == null) {
b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth());
}
@@ -641,7 +641,7 @@ public class DynamicLayout extends Layout {
.setAddLastLineLineSpacing(!islast)
.setIncludePad(false);
- reflowed = b.regenerate(true /* trackpadding */, reflowed);
+ reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
int n = reflowed.getLineCount();
// If the new layout has a blank line at the end, but it is not
// the very end of the buffer, then we already have a line that
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f843900e8022..3d1895c3eaf3 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -437,13 +437,25 @@ public class StaticLayout extends Layout {
return result;
}
- /* package */ @NonNull StaticLayout regenerate(boolean trackpadding, StaticLayout recycle) {
+ /**
+ * DO NOT USE THIS METHOD OTHER THAN DynamicLayout.
+ *
+ * This class generates a very weird StaticLayout only for getting a result of line break.
+ * Since DynamicLayout keeps StaticLayout reference in the static context for object
+ * recycling but keeping text reference in static context will end up with leaking Context
+ * due to TextWatcher via TextView.
+ *
+ * So, this is a dirty work around that creating StaticLayout without passing text reference
+ * to the super constructor, but calculating the text layout by calling generate function
+ * directly.
+ */
+ /* package */ @NonNull StaticLayout buildPartialStaticLayoutForDynamicLayout(
+ boolean trackpadding, StaticLayout recycle) {
if (recycle == null) {
- return new StaticLayout(this, trackpadding, COLUMNS_ELLIPSIZE);
- } else {
- recycle.generate(this, mIncludePad, trackpadding);
- return recycle;
+ recycle = new StaticLayout();
}
+ recycle.generate(this, mIncludePad, trackpadding);
+ return recycle;
}
private CharSequence mText;
@@ -474,6 +486,37 @@ public class StaticLayout extends Layout {
}
/**
+ * DO NOT USE THIS CONSTRUCTOR OTHER THAN FOR DYNAMIC LAYOUT.
+ * See Builder#buildPartialStaticLayoutForDynamicLayout for the reason of this constructor.
+ */
+ private StaticLayout() {
+ super(
+ null, // text
+ null, // paint
+ 0, // width
+ null, // alignment
+ null, // textDir
+ 1, // spacing multiplier
+ 0, // spacing amount
+ false, // include font padding
+ false, // fallback line spacing
+ 0, // ellipsized width
+ null, // ellipsize
+ 1, // maxLines
+ BREAK_STRATEGY_SIMPLE,
+ HYPHENATION_FREQUENCY_NONE,
+ null, // leftIndents
+ null, // rightIndents
+ JUSTIFICATION_MODE_NONE,
+ null // lineBreakConfig
+ );
+
+ mColumns = COLUMNS_ELLIPSIZE;
+ mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
+ mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
+ }
+
+ /**
* @deprecated Use {@link Builder} instead.
*/
@Deprecated
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4ecfc4044b1d..c6d8bd18bc28 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -16,10 +16,12 @@
package android.view;
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
import static android.os.Trace.TRACE_TAG_VIEW;
import static android.view.InsetsControllerProto.CONTROL;
import static android.view.InsetsControllerProto.STATE;
import static android.view.InsetsSource.ID_IME;
+import static android.view.InsetsSource.ID_IME_CAPTION_BAR;
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.WindowInsets.Type.FIRST;
import static android.view.WindowInsets.Type.LAST;
@@ -40,6 +42,7 @@ import android.app.ActivityThread;
import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.os.Handler;
@@ -652,6 +655,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private int mLastWindowingMode;
private boolean mStartingAnimation;
private int mCaptionInsetsHeight = 0;
+ private int mImeCaptionBarInsetsHeight = 0;
private boolean mAnimationsDisabled;
private boolean mCompatSysUiVisibilityStaled;
@@ -693,6 +697,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (!CAPTION_ON_SHELL && source1.getType() == captionBar()) {
return;
}
+ if (source1.getId() == ID_IME_CAPTION_BAR) {
+ return;
+ }
// Don't change the indexes of the sources while traversing. Remove it later.
mPendingRemoveIndexes.add(index1);
@@ -823,6 +830,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (mFrame.equals(frame)) {
return;
}
+ if (mImeCaptionBarInsetsHeight != 0) {
+ setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight);
+ }
mHost.notifyInsetsChanged();
mFrame.set(frame);
}
@@ -1007,6 +1017,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// Ensure to update all existing source consumers
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+ if (consumer.getId() == ID_IME_CAPTION_BAR) {
+ // The inset control for the IME caption bar will never be dispatched
+ // by the server.
+ continue;
+ }
+
final InsetsSourceControl control = mTmpControlArray.get(consumer.getId());
if (control != null) {
controllableTypes |= control.getType();
@@ -1499,7 +1515,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
continue;
}
final InsetsSourceControl control = consumer.getControl();
- if (control != null && control.getLeash() != null) {
+ if (control != null
+ && (control.getLeash() != null || control.getId() == ID_IME_CAPTION_BAR)) {
controls.put(control.getId(), new InsetsSourceControl(control));
typesReady |= consumer.getType();
}
@@ -1885,6 +1902,35 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
@Override
+ public void setImeCaptionBarInsetsHeight(int height) {
+ if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+ return;
+ }
+ Rect newFrame = new Rect(mFrame.left, mFrame.bottom - height, mFrame.right, mFrame.bottom);
+ InsetsSource source = mState.peekSource(ID_IME_CAPTION_BAR);
+ if (mImeCaptionBarInsetsHeight != height
+ || (source != null && !newFrame.equals(source.getFrame()))) {
+ mImeCaptionBarInsetsHeight = height;
+ if (mImeCaptionBarInsetsHeight != 0) {
+ mState.getOrCreateSource(ID_IME_CAPTION_BAR, captionBar())
+ .setFrame(newFrame);
+ getSourceConsumer(ID_IME_CAPTION_BAR, captionBar()).setControl(
+ new InsetsSourceControl(ID_IME_CAPTION_BAR, captionBar(),
+ null /* leash */, false /* initialVisible */,
+ new Point(), Insets.NONE),
+ new int[1], new int[1]);
+ } else {
+ mState.removeSource(ID_IME_CAPTION_BAR);
+ InsetsSourceConsumer sourceConsumer = mSourceConsumers.get(ID_IME_CAPTION_BAR);
+ if (sourceConsumer != null) {
+ sourceConsumer.setControl(null, new int[1], new int[1]);
+ }
+ }
+ mHost.notifyInsetsChanged();
+ }
+ }
+
+ @Override
public void setSystemBarsBehavior(@Behavior int behavior) {
mHost.setSystemBarsBehavior(behavior);
}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 64411866f020..ff009ed09329 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -20,6 +20,7 @@ import static android.view.InsetsSourceProto.FRAME;
import static android.view.InsetsSourceProto.TYPE;
import static android.view.InsetsSourceProto.VISIBLE;
import static android.view.InsetsSourceProto.VISIBLE_FRAME;
+import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.ime;
import android.annotation.IntDef;
@@ -47,6 +48,9 @@ public class InsetsSource implements Parcelable {
/** The insets source ID of IME */
public static final int ID_IME = createId(null, 0, ime());
+ /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */
+ static final int ID_IME_CAPTION_BAR =
+ InsetsSource.createId(null /* owner */, 1 /* index */, captionBar());
/**
* Controls whether this source suppresses the scrim. If the scrim is ignored, the system won't
@@ -215,8 +219,12 @@ public class InsetsSource implements Parcelable {
// During drag-move and drag-resizing, the caption insets position may not get updated
// before the app frame get updated. To layout the app content correctly during drag events,
// we always return the insets with the corresponding height covering the top.
+ // However, with the "fake" IME navigation bar treated as a caption bar, we return the
+ // insets with the corresponding height the bottom.
if (getType() == WindowInsets.Type.captionBar()) {
- return Insets.of(0, frame.height(), 0, 0);
+ return getId() == ID_IME_CAPTION_BAR
+ ? Insets.of(0, 0, 0, frame.height())
+ : Insets.of(0, frame.height(), 0, 0);
}
// Checks for whether there is shared edge with insets for 0-width/height window.
final boolean hasIntersection = relativeFrame.isEmpty()
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index e8f62fc0963f..a4cbc52416b3 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -44,6 +44,7 @@ public class PendingInsetsController implements WindowInsetsController {
private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
= new ArrayList<>();
private int mCaptionInsetsHeight = 0;
+ private int mImeCaptionBarInsetsHeight = 0;
private WindowInsetsAnimationControlListener mLoggingListener;
private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
@@ -91,6 +92,11 @@ public class PendingInsetsController implements WindowInsetsController {
}
@Override
+ public void setImeCaptionBarInsetsHeight(int height) {
+ mImeCaptionBarInsetsHeight = height;
+ }
+
+ @Override
public void setSystemBarsBehavior(int behavior) {
if (mReplayedInsetsController != null) {
mReplayedInsetsController.setSystemBarsBehavior(behavior);
@@ -168,6 +174,9 @@ public class PendingInsetsController implements WindowInsetsController {
if (mCaptionInsetsHeight != 0) {
controller.setCaptionInsetsHeight(mCaptionInsetsHeight);
}
+ if (mImeCaptionBarInsetsHeight != 0) {
+ controller.setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight);
+ }
if (mAnimationsDisabled) {
controller.setAnimationsDisabled(true);
}
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index bc0bab7b5e95..cc2cd7982841 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -250,6 +250,16 @@ public interface WindowInsetsController {
void setCaptionInsetsHeight(int height);
/**
+ * Sets the insets height for the IME caption bar, which corresponds to the
+ * "fake" IME navigation bar.
+ *
+ * @param height the insets height of the IME caption bar.
+ * @hide
+ */
+ default void setImeCaptionBarInsetsHeight(int height) {
+ }
+
+ /**
* Controls the behavior of system bars.
*
* @param behavior Determines how the bars behave when being hidden by the application.
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 7dda91d7b25e..5b6b36043684 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -410,9 +410,11 @@ public class ConversationLayout extends FrameLayout
// convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
// if they exist
final List<MessagingMessage> newMessagingMessages =
- createMessages(newMessages, false /* isHistoric */);
+ createMessages(newMessages, /* isHistoric= */false,
+ /* usePrecomputedText= */false);
final List<MessagingMessage> newHistoricMessagingMessages =
- createMessages(newHistoricMessages, true /* isHistoric */);
+ createMessages(newHistoricMessages, /* isHistoric= */true,
+ /* usePrecomputedText= */false);
// bind it, baby
bindViews(user, showSpinner, unreadCount,
newMessagingMessages,
@@ -981,15 +983,17 @@ public class ConversationLayout extends FrameLayout
* @param newMessages the messages to parse.
*/
private List<MessagingMessage> createMessages(
- List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+ List<Notification.MessagingStyle.Message> newMessages, boolean isHistoric,
+ boolean usePrecomputedText) {
List<MessagingMessage> result = new ArrayList<>();
for (int i = 0; i < newMessages.size(); i++) {
Notification.MessagingStyle.Message m = newMessages.get(i);
MessagingMessage message = findAndRemoveMatchingMessage(m);
if (message == null) {
- message = MessagingMessage.createMessage(this, m, mImageResolver);
+ message = MessagingMessage.createMessage(this, m,
+ mImageResolver, usePrecomputedText);
}
- message.setIsHistoric(historic);
+ message.setIsHistoric(isHistoric);
result.add(message);
}
return result;
diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java
index 098bce14e619..c132d6a90f6c 100644
--- a/core/java/com/android/internal/widget/MessagingImageMessage.java
+++ b/core/java/com/android/internal/widget/MessagingImageMessage.java
@@ -93,8 +93,9 @@ public class MessagingImageMessage extends ImageView implements MessagingMessage
}
@Override
- public boolean setMessage(Notification.MessagingStyle.Message message) {
- MessagingMessage.super.setMessage(message);
+ public boolean setMessage(Notification.MessagingStyle.Message message,
+ boolean usePrecomputedText) {
+ MessagingMessage.super.setMessage(message, usePrecomputedText);
Drawable drawable;
try {
Uri uri = message.getDataUri();
@@ -114,32 +115,42 @@ public class MessagingImageMessage extends ImageView implements MessagingMessage
}
mDrawable = drawable;
mAspectRatio = ((float) mDrawable.getIntrinsicWidth()) / intrinsicHeight;
- setImageDrawable(drawable);
- setContentDescription(message.getText());
+ if (!usePrecomputedText) {
+ finalizeInflate();
+ }
return true;
}
static MessagingMessage createMessage(IMessagingLayout layout,
- Notification.MessagingStyle.Message m, ImageResolver resolver) {
+ Notification.MessagingStyle.Message m, ImageResolver resolver,
+ boolean usePrecomputedText) {
MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
MessagingImageMessage createdMessage = sInstancePool.acquire();
if (createdMessage == null) {
createdMessage = (MessagingImageMessage) LayoutInflater.from(
layout.getContext()).inflate(
- R.layout.notification_template_messaging_image_message,
- messagingLinearLayout,
- false);
+ R.layout.notification_template_messaging_image_message,
+ messagingLinearLayout,
+ false);
createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
}
createdMessage.setImageResolver(resolver);
- boolean created = createdMessage.setMessage(m);
- if (!created) {
+ // MessagingImageMessage does not use usePrecomputedText.
+ boolean populated = createdMessage.setMessage(m, /* usePrecomputedText= */false);
+ if (!populated) {
createdMessage.recycle();
- return MessagingTextMessage.createMessage(layout, m);
+ return MessagingTextMessage.createMessage(layout, m, usePrecomputedText);
}
return createdMessage;
}
+
+ @Override
+ public void finalizeInflate() {
+ setImageDrawable(mDrawable);
+ setContentDescription(getMessage().getText());
+ }
+
private void setImageResolver(ImageResolver resolver) {
mImageResolver = resolver;
}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 8345c5cc9ef9..83557cd8a719 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -178,9 +178,9 @@ public class MessagingLayout extends FrameLayout
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
final List<MessagingMessage> historicMessagingMessages = createMessages(newHistoricMessages,
- true /* isHistoric */);
+ /* isHistoric= */true, /* usePrecomputedText= */ false);
final List<MessagingMessage> newMessagingMessages =
- createMessages(newMessages, false /* isHistoric */);
+ createMessages(newMessages, /* isHistoric= */false, /* usePrecomputedText= */false);
bindViews(user, showSpinner, historicMessagingMessages, newMessagingMessages);
}
@@ -518,15 +518,17 @@ public class MessagingLayout extends FrameLayout
* @param newMessages the messages to parse.
*/
private List<MessagingMessage> createMessages(
- List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+ List<Notification.MessagingStyle.Message> newMessages, boolean isHistoric,
+ boolean usePrecomputedText) {
List<MessagingMessage> result = new ArrayList<>();
for (int i = 0; i < newMessages.size(); i++) {
Notification.MessagingStyle.Message m = newMessages.get(i);
MessagingMessage message = findAndRemoveMatchingMessage(m);
if (message == null) {
- message = MessagingMessage.createMessage(this, m, mImageResolver);
+ message = MessagingMessage.createMessage(this, m,
+ mImageResolver, usePrecomputedText);
}
- message.setIsHistoric(historic);
+ message.setIsHistoric(isHistoric);
result.add(message);
}
return result;
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index 5ecd3b82053d..ad90a63ab187 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -34,11 +34,12 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
String IMAGE_MIME_TYPE_PREFIX = "image/";
static MessagingMessage createMessage(IMessagingLayout layout,
- Notification.MessagingStyle.Message m, ImageResolver resolver) {
+ Notification.MessagingStyle.Message m, ImageResolver resolver,
+ boolean usePrecomputedText) {
if (hasImage(m) && !ActivityManager.isLowRamDeviceStatic()) {
- return MessagingImageMessage.createMessage(layout, m, resolver);
+ return MessagingImageMessage.createMessage(layout, m, resolver, usePrecomputedText);
} else {
- return MessagingTextMessage.createMessage(layout, m);
+ return MessagingTextMessage.createMessage(layout, m, usePrecomputedText);
}
}
@@ -55,9 +56,11 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
/**
* Set a message for this view.
+ *
* @return true if setting the message worked
*/
- default boolean setMessage(Notification.MessagingStyle.Message message) {
+ default boolean setMessage(Notification.MessagingStyle.Message message,
+ boolean usePrecomputedText) {
getState().setMessage(message);
return true;
}
@@ -151,4 +154,10 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
void setVisibility(int visibility);
int getVisibility();
+
+ /**
+ * Finalize inflation of the MessagingMessages, which should be called on Main Thread.
+ * @hide
+ */
+ void finalizeInflate();
}
diff --git a/core/java/com/android/internal/widget/MessagingTextMessage.java b/core/java/com/android/internal/widget/MessagingTextMessage.java
index 19791dbad31e..bd62aad15b34 100644
--- a/core/java/com/android/internal/widget/MessagingTextMessage.java
+++ b/core/java/com/android/internal/widget/MessagingTextMessage.java
@@ -23,7 +23,9 @@ import android.annotation.StyleRes;
import android.app.Notification;
import android.content.Context;
import android.text.Layout;
+import android.text.PrecomputedText;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.widget.RemoteViews;
@@ -35,10 +37,13 @@ import com.android.internal.R;
@RemoteViews.RemoteView
public class MessagingTextMessage extends ImageFloatingTextView implements MessagingMessage {
+ private static final String TAG = "MessagingTextMessage";
private static final MessagingPool<MessagingTextMessage> sInstancePool =
new MessagingPool<>(20);
private final MessagingMessageState mState = new MessagingMessageState(this);
+ private PrecomputedText mPrecomputedText = null;
+
public MessagingTextMessage(@NonNull Context context) {
super(context);
}
@@ -63,25 +68,32 @@ public class MessagingTextMessage extends ImageFloatingTextView implements Messa
}
@Override
- public boolean setMessage(Notification.MessagingStyle.Message message) {
- MessagingMessage.super.setMessage(message);
- setText(message.getText());
+ public boolean setMessage(Notification.MessagingStyle.Message message,
+ boolean usePrecomputedText) {
+ MessagingMessage.super.setMessage(message, usePrecomputedText);
+ if (usePrecomputedText) {
+ mPrecomputedText = PrecomputedText.create(message.getText(), getTextMetricsParams());
+ } else {
+ setText(message.getText());
+ mPrecomputedText = null;
+ }
+
return true;
}
static MessagingMessage createMessage(IMessagingLayout layout,
- Notification.MessagingStyle.Message m) {
+ Notification.MessagingStyle.Message m, boolean usePrecomputedText) {
MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
MessagingTextMessage createdMessage = sInstancePool.acquire();
if (createdMessage == null) {
createdMessage = (MessagingTextMessage) LayoutInflater.from(
layout.getContext()).inflate(
- R.layout.notification_template_messaging_text_message,
- messagingLinearLayout,
- false);
+ R.layout.notification_template_messaging_text_message,
+ messagingLinearLayout,
+ false);
createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
}
- createdMessage.setMessage(m);
+ createdMessage.setMessage(m, usePrecomputedText);
return createdMessage;
}
@@ -135,4 +147,20 @@ public class MessagingTextMessage extends ImageFloatingTextView implements Messa
public void setColor(int color) {
setTextColor(color);
}
+
+ @Override
+ public void finalizeInflate() {
+ try {
+ setText(mPrecomputedText != null ? mPrecomputedText
+ : getState().getMessage().getText());
+ } catch (IllegalArgumentException exception) {
+ Log.wtf(
+ /* tag = */ TAG,
+ /* msg = */ "PrecomputedText setText failed for TextView:" + this,
+ /* tr = */ exception
+ );
+ mPrecomputedText = null;
+ setText(getState().getMessage().getText());
+ }
+ }
}
diff --git a/core/res/res/drawable/focus_event_rotary_input_background.xml b/core/res/res/drawable/focus_event_rotary_input_background.xml
new file mode 100644
index 000000000000..512cd687f2b1
--- /dev/null
+++ b/core/res/res/drawable/focus_event_rotary_input_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="focus_event_rotary_input_background"
+ android:shape="rectangle">
+
+ <!-- View background color -->
+ <solid android:color="#80741b47" />
+
+ <!-- View border color and width -->
+ <stroke android:width="1dp" android:color="#ffff00ff" />
+
+ <!-- The radius makes the corners rounded -->
+ <corners android:radius="4dp" />
+
+</shape> \ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9621f93b9d05..a78b0407be67 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -45,9 +45,7 @@
<item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
- <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_connected_display</xliff:g></item>
- <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_camera</xliff:g></item>
@@ -56,6 +54,8 @@
<item><xliff:g id="id">@string/status_bar_mute</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_volume</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_zen</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
@@ -5041,6 +5041,21 @@
</array>
<!-- See DisplayWhiteBalanceController.
+ A float array containing a list of ambient brightnesses, in Lux. This array,
+ together with config_displayWhiteBalanceLowLightAmbientBiasesStrong, is used to generate a
+ lookup table used in DisplayWhiteBalanceController. This lookup table is used to map
+ ambient brightness readings to a bias, where the bias is used to linearly interpolate
+ between ambient color temperature and
+ config_displayWhiteBalanceLowLightAmbientColorTemperatureIdle.
+ This table is optional. If used, this array must,
+ 1) Contain at least two entries
+ 2) Be the same length as config_displayWhiteBalanceLowLightAmbientBiasesStrong. -->
+ <array name ="config_displayWhiteBalanceLowLightAmbientBrightnessesStrong">
+ <item>10.0</item>
+ <item>10.0</item>
+ </array>
+
+ <!-- See DisplayWhiteBalanceController.
An array containing a list of biases. See
config_displayWhiteBalanceLowLightAmbientBrightnesses for additional details.
This array must be in the range of [0.0, 1.0]. -->
@@ -5050,12 +5065,28 @@
</array>
<!-- See DisplayWhiteBalanceController.
+ An array containing a list of biases. See
+ config_displayWhiteBalanceLowLightAmbientBrightnessesStrong for additional details.
+ This array must be in the range of [0.0, 1.0]. -->
+ <array name ="config_displayWhiteBalanceLowLightAmbientBiasesStrong">
+ <item>0.0</item>
+ <item>1.0</item>
+ </array>
+
+ <!-- See DisplayWhiteBalanceController.
The ambient color temperature (in cct) to which we interpolate towards using the
the look up table generated by config_displayWhiteBalanceLowLightAmbientBrightnesses
and config_displayWhiteBalanceLowLightAmbientBiases. -->
<item name="config_displayWhiteBalanceLowLightAmbientColorTemperature" format="float" type="dimen">6500.0</item>
<!-- See DisplayWhiteBalanceController.
+ The ambient color temperature (in cct) to which we interpolate towards using the
+ the look up table generated by config_displayWhiteBalanceLowLightAmbientBrightnessesStrong
+ and config_displayWhiteBalanceLowLightAmbientBiasesStrong. Used when device is in Idle Screen
+ Brightness mode. -->
+ <item name="config_displayWhiteBalanceLowLightAmbientColorTemperatureStrong" format="float" type="dimen">6500.0</item>
+
+ <!-- See DisplayWhiteBalanceController.
A float array containing a list of ambient brightnesses, in Lux. This array,
together with config_displayWhiteBalanceHighLightAmbientBiases, is used to generate a
lookup table used in DisplayWhiteBalanceController. This lookup table is used to map
@@ -5069,6 +5100,19 @@
</array>
<!-- See DisplayWhiteBalanceController.
+ A float array containing a list of ambient brightnesses, in Lux. This array,
+ together with config_displayWhiteBalanceHighLightAmbientBiasesStrong, is used to generate a
+ lookup table used in DisplayWhiteBalanceController. This lookup table is used to map
+ ambient brightness readings to a bias, where the bias is used to linearly interpolate
+ between ambient color temperature and
+ config_displayWhiteBalanceHighLightAmbientColorTemperatureStrong.
+ This table is optional. If used, this array must,
+ 1) Contain at least two entries
+ 2) Be the same length as config_displayWhiteBalanceHighLightAmbientBiasesStrong. -->
+ <array name ="config_displayWhiteBalanceHighLightAmbientBrightnessesStrong">
+ </array>
+
+ <!-- See DisplayWhiteBalanceController.
An array containing a list of biases. See
config_displayWhiteBalanceHighLightAmbientBrightnesses for additional details.
This array must be in the range of [0.0, 1.0]. -->
@@ -5076,12 +5120,26 @@
</array>
<!-- See DisplayWhiteBalanceController.
+ An array containing a list of biases. See
+ config_displayWhiteBalanceHighLightAmbientBrightnessesStrong for additional details.
+ This array must be in the range of [0.0, 1.0]. -->
+ <array name ="config_displayWhiteBalanceHighLightAmbientBiasesStrong">
+ </array>
+
+ <!-- See DisplayWhiteBalanceController.
The ambient color temperature (in cct) to which we interpolate towards using the
the look up table generated by config_displayWhiteBalanceHighLightAmbientBrightnesses
and config_displayWhiteBalanceHighLightAmbientBiases. -->
<item name="config_displayWhiteBalanceHighLightAmbientColorTemperature" format="float" type="dimen">8000.0</item>
<!-- See DisplayWhiteBalanceController.
+ The ambient color temperature (in cct) to which we interpolate towards using the
+ the look up table generated by config_displayWhiteBalanceHighLightAmbientBrightnessesStrong
+ and config_displayWhiteBalanceHighLightAmbientBiasesStrong. Used when device is in Idle
+ Screen Brightness mode. -->
+ <item name="config_displayWhiteBalanceHighLightAmbientColorTemperatureStrong" format="float" type="dimen">8000.0</item>
+
+ <!-- See DisplayWhiteBalanceController.
A float array containing a list of ambient color temperatures, in Kelvin. This array,
together with config_displayWhiteBalanceDisplayColorTemperatures, is used to generate a
lookup table used in DisplayWhiteBalanceController. This lookup table is used to map
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c5aa8b05b948..0dd6c749b5a9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1895,22 +1895,22 @@
<!-- Content description which should be used for the fingerprint icon. -->
<string name="fingerprint_icon_content_description">Fingerprint icon</string>
+ <!-- Notification name shown when the system requires the user to set up device unlock. [CHAR LIMIT=NONE] -->
+ <string name="device_unlock_notification_name">Device unlock</string>
+ <!-- Notification title shown when the system suggests the user to set up another way to unlock. [CHAR LIMIT=NONE] -->
+ <string name="alternative_unlock_setup_notification_title">Try another way to unlock</string>
+ <!-- Notification content shown when the system suggests the user to enroll their face. [CHAR LIMIT=NONE] -->
+ <string name="alternative_face_setup_notification_content">Use Face Unlock when your fingerprint isn\'t recognized, like when your fingers are wet</string>
+ <!-- Notification content shown when the system suggests the user to enroll their fingerprint. [CHAR LIMIT=NONE] -->
+ <string name="alternative_fp_setup_notification_content">Use Fingerprint Unlock when your face isn\'t recognized, like when there\'s not enough light</string>
<!-- Notification name shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
<string name="face_recalibrate_notification_name">Face Unlock</string>
<!-- Notification title shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
<string name="face_recalibrate_notification_title">Issue with Face Unlock</string>
<!-- Notification content shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
<string name="face_recalibrate_notification_content">Tap to delete your face model, then add your face again</string>
- <!-- Title of a notification that directs the user to set up Face Unlock by enrolling their face. [CHAR LIMIT=NONE] -->
- <string name="face_setup_notification_title">Set up Face Unlock</string>
- <!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] -->
- <string name="face_setup_notification_content">Unlock your phone by looking at it</string>
<!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
<string name="face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
- <!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
- <string name="fingerprint_setup_notification_title">Set up more ways to unlock</string>
- <!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
- <string name="fingerprint_setup_notification_content">Tap to add a fingerprint</string>
<!-- Notification name shown when the system requires the user to re-calibrate their fingerprint. [CHAR LIMIT=NONE] -->
<string name="fingerprint_recalibrate_notification_name">Fingerprint Unlock</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 851c0c2ce7c6..eaccd43d9fab 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2597,6 +2597,12 @@
<!-- Biometric FRR config -->
<java-symbol type="fraction" name="config_biometricNotificationFrrThreshold" />
+ <!-- Biometric FRR notification messages -->
+ <java-symbol type="string" name="device_unlock_notification_name" />
+ <java-symbol type="string" name="alternative_unlock_setup_notification_title" />
+ <java-symbol type="string" name="alternative_face_setup_notification_content" />
+ <java-symbol type="string" name="alternative_fp_setup_notification_content" />
+
<!-- Device credential strings for BiometricManager -->
<java-symbol type="string" name="screen_lock_app_setting_name" />
<java-symbol type="string" name="screen_lock_dialog_default_subtitle" />
@@ -4167,11 +4173,17 @@
<java-symbol type="array" name="config_displayWhiteBalanceIncreaseThresholds" />
<java-symbol type="array" name="config_displayWhiteBalanceDecreaseThresholds" />
<java-symbol type="array" name="config_displayWhiteBalanceLowLightAmbientBrightnesses" />
+ <java-symbol type="array" name="config_displayWhiteBalanceLowLightAmbientBrightnessesStrong" />
<java-symbol type="array" name="config_displayWhiteBalanceLowLightAmbientBiases" />
+ <java-symbol type="array" name="config_displayWhiteBalanceLowLightAmbientBiasesStrong" />
<java-symbol type="dimen" name="config_displayWhiteBalanceLowLightAmbientColorTemperature" />
+ <java-symbol type="dimen" name="config_displayWhiteBalanceLowLightAmbientColorTemperatureStrong" />
<java-symbol type="array" name="config_displayWhiteBalanceHighLightAmbientBrightnesses" />
+ <java-symbol type="array" name="config_displayWhiteBalanceHighLightAmbientBrightnessesStrong" />
<java-symbol type="array" name="config_displayWhiteBalanceHighLightAmbientBiases" />
+ <java-symbol type="array" name="config_displayWhiteBalanceHighLightAmbientBiasesStrong" />
<java-symbol type="dimen" name="config_displayWhiteBalanceHighLightAmbientColorTemperature" />
+ <java-symbol type="dimen" name="config_displayWhiteBalanceHighLightAmbientColorTemperatureStrong" />
<java-symbol type="array" name="config_displayWhiteBalanceAmbientColorTemperatures" />
<java-symbol type="array" name="config_displayWhiteBalanceDisplayColorTemperatures" />
<java-symbol type="array" name="config_displayWhiteBalanceStrongAmbientColorTemperatures" />
@@ -5185,6 +5197,7 @@
<java-symbol type="style" name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" />
<java-symbol type="drawable" name="focus_event_pressed_key_background" />
+ <java-symbol type="drawable" name="focus_event_rotary_input_background" />
<java-symbol type="string" name="config_defaultShutdownVibrationFile" />
<java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index c14da299c6ef..7f56eb70b153 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -88,7 +88,7 @@ android_test {
resource_dirs: ["res"],
resource_zips: [":FrameworksCoreTests_apks_as_resources"],
- java_resources: [":ApkVerityTestCertDer"],
+ java_resources: [":FrameworksCoreTests_unit_test_cert_der"],
data: [
":BinderDeathRecipientHelperApp1",
diff --git a/core/tests/coretests/certs/Android.bp b/core/tests/coretests/certs/Android.bp
index 8d4ecf4253c3..cefdc4dd60b1 100644
--- a/core/tests/coretests/certs/Android.bp
+++ b/core/tests/coretests/certs/Android.bp
@@ -13,3 +13,8 @@ android_app_certificate {
name: "FrameworksCoreTests_unit_test_cert",
certificate: "unit_test",
}
+
+filegroup {
+ name: "FrameworksCoreTests_unit_test_cert_der",
+ srcs: ["unit_test.der"],
+}
diff --git a/core/tests/coretests/certs/README b/core/tests/coretests/certs/README
index 00917a188934..b5c096e61a2e 100644
--- a/core/tests/coretests/certs/README
+++ b/core/tests/coretests/certs/README
@@ -2,3 +2,5 @@ Generate with:
development/tools/make_key unit_test '/CN=unit_test'
development/tools/make_key unit_test_diff '/CN=unit_test_diff'
+
+openssl x509 -in unit_test.x509.pem -out unit_test.der -outform der
diff --git a/core/tests/coretests/certs/unit_test.der b/core/tests/coretests/certs/unit_test.der
new file mode 100644
index 000000000000..4dbbc4946bda
--- /dev/null
+++ b/core/tests/coretests/certs/unit_test.der
Binary files differ
diff --git a/core/tests/coretests/res/raw/fsverity_sig b/core/tests/coretests/res/raw/fsverity_sig
index b2f335dc0342..2c28f0be3cba 100644
--- a/core/tests/coretests/res/raw/fsverity_sig
+++ b/core/tests/coretests/res/raw/fsverity_sig
Binary files differ
diff --git a/core/tests/coretests/src/android/security/keystore/OWNERS b/core/tests/coretests/src/android/security/keystore/OWNERS
new file mode 100644
index 000000000000..d9e01161ce6d
--- /dev/null
+++ b/core/tests/coretests/src/android/security/keystore/OWNERS
@@ -0,0 +1 @@
+include /keystore/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
index 151365481f20..a978e3b9e739 100644
--- a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
@@ -229,11 +229,12 @@ public class VerityUtilsTest {
@Test
public void testSignatureGeneratedExternally() throws Exception {
var context = InstrumentationRegistry.getInstrumentation().getContext();
- byte[] cert = getClass().getClassLoader().getResourceAsStream("ApkVerityTestCert.der")
+ byte[] cert = getClass().getClassLoader().getResourceAsStream("unit_test.der")
.readAllBytes();
// The signature is generated by:
- // fsverity sign <(echo -n fs-verity) fsverity_sig --key=ApkVerityTestKey.pem \
- // --cert=ApkVerityTestCert.pem
+ // openssl pkcs8 -topk8 -nocrypt -in certs/unit_test.pk8 -out certs/unit_test.key.pem
+ // fsverity sign <(echo -n fs-verity) fsverity_sig --key=certs/unit_test.key.pem \
+ // --cert=certs/unit_test.x509.pem
byte[] sig = context.getResources().openRawResource(R.raw.fsverity_sig).readAllBytes();
// The fs-verity digest is generated by:
// fsverity digest --compact <(echo -n fs-verity)
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index 8be489ecd140..e8758754f059 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -41,8 +41,6 @@ import android.os.VibrationEffect.Composition.UnreachableAfterRepeatingIndefinit
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
-import androidx.test.InstrumentationRegistry;
-
import com.android.internal.R;
import org.jetbrains.annotations.NotNull;
@@ -838,31 +836,29 @@ public class VibrationEffectTest {
@Test
public void testAreVibrationFeaturesSupported_allSegmentsSupported() {
- Vibrator vibrator =
- createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
- .build());
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+ .build();
assertTrue(VibrationEffect.createWaveform(
/* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(VibrationEffect.createWaveform(
/* timings= */ new long[] {1, 2, 3},
/* amplitudes= */ new int[] {10, 20, 40},
/* repeatIndex= */ 2)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(
VibrationEffect.startComposition()
.addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
.repeatEffectIndefinitely(TEST_ONE_SHOT)
.compose()
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testAreVibrationFeaturesSupported_withUnsupportedSegments() {
- Vibrator vibrator =
- createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1).build());
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1).build();
assertFalse(
VibrationEffect.startComposition()
@@ -872,7 +868,7 @@ public class VibrationEffectTest {
/* amplitudes= */ new int[] {10, 20, 40},
/* repeatIndex= */ -1))
.compose()
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
@@ -996,13 +992,4 @@ public class VibrationEffectTest {
return context;
}
-
- private Vibrator createVibratorWithCustomInfo(VibratorInfo info) {
- return new SystemVibrator(InstrumentationRegistry.getContext()) {
- @Override
- public VibratorInfo getInfo() {
- return info;
- }
- };
- }
}
diff --git a/core/tests/vibrator/src/android/os/VibratorInfoTest.java b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
index ff917aacba38..808c4ece9435 100644
--- a/core/tests/vibrator/src/android/os/VibratorInfoTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
@@ -57,6 +57,17 @@ public class VibratorInfoTest {
}
@Test
+ public void testHasFrequencyControl() {
+ VibratorInfo noCapabilities = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
+ assertFalse(noCapabilities.hasFrequencyControl());
+ VibratorInfo composeAndFrequencyControl = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
+ .setCapabilities(
+ IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
+ .build();
+ assertTrue(composeAndFrequencyControl.hasFrequencyControl());
+ }
+
+ @Test
public void testHasCapabilities() {
VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
@@ -335,4 +346,186 @@ public class VibratorInfoTest {
assertEquals(original, restored);
assertEquals(original.hashCode(), restored.hashCode());
}
+
+ @Test
+ public void areVibrationFeaturesSupported_noAmplitudeControl_fractionalAmplitudeUnsupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1).build();
+
+ // Have at least one fractional amplitude (amplitude not min (0) or max (255) or DEFAULT).
+ assertFalse(info.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30)));
+ assertFalse(info.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 255)));
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_noAmplitudeControl_nonFractionalAmplitudeSupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1).build();
+
+ // All amplitudes are min, max, or default. Requires no amplitude control.
+ assertTrue(info.areVibrationFeaturesSupported(
+ waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_withAmplitudeControl_allWaveformsSupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+ .build();
+
+ // All forms of amplitudes are valid when amplitude control is available.
+ assertTrue(info.areVibrationFeaturesSupported(
+ waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
+ assertTrue(info.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30, 50)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ waveformWithAmplitudes(7, 255, 0, 0, 60)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_compositionsWithSupportedPrimitivesSupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose()));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ /* scale= */ 0.2f,
+ /* delay= */ 200)
+ .compose()));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_compositionsWithUnupportedPrimitivesUnsupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+ .compose()));
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+ .compose()));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_composedEffects_allComponentsSupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
+ .build();
+
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addEffect(VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {10, 20, 255},
+ /* repeatIndex= */ -1))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
+ .compose()));
+
+ info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
+ .setSupportedEffects(VibrationEffect.EFFECT_POP, VibrationEffect.EFFECT_CLICK)
+ .build();
+
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+ .addEffect(VibrationEffect.createWaveform(
+ // These timings are given either 0 or default amplitudes, which
+ // do not require vibrator's amplitude control.
+ /* timings= */ new long[] {1, 2, 3},
+ /* repeatIndex= */ -1))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+ .compose()));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_composedEffects_someComponentsUnupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
+ .build();
+
+ // Not supported due to the TICK primitive, which the vibrator has no support for.
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addEffect(VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {10, 20, 255},
+ /* repeatIndex= */ -1))
+ .compose()));
+ // Not supported due to the THUD effect, which the vibrator has no support for.
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addEffect(VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {10, 20, 255},
+ /* repeatIndex= */ -1))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_THUD))
+ .compose()));
+
+ info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
+ .setSupportedEffects(VibrationEffect.EFFECT_POP)
+ .build();
+
+ // Not supported due to fractional amplitudes (amplitudes not min (0) or max (255) or
+ // DEFAULT), because the vibrator has no amplitude control.
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+ .addEffect(VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {10, 20, 255},
+ /* repeatIndex= */ -1))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
+ .compose()));
+ }
+
+ private static VibrationEffect waveformWithAmplitudes(int...amplitudes) {
+ long[] timings = new long[amplitudes.length];
+ for (int i = 0; i < timings.length; i++) {
+ timings[i] = i * 2; // Arbitrary timings.
+ }
+ return VibrationEffect.createWaveform(timings, amplitudes, /* repeatIndex= */ -1);
+ }
}
diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
index c559e34d92a3..8141ca4b22a8 100644
--- a/core/tests/vibrator/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -578,189 +578,6 @@ public class VibratorTest {
assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes);
}
- @Test
- public void areVibrationFeaturesSupported_noAmplitudeControl_fractionalAmplitudes() {
- Vibrator vibrator =
- createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedEffects(VibrationEffect.EFFECT_THUD)
- .build());
-
- // Have at least one fractional amplitude (amplitude not min (0) or max (255) or DEFAULT).
- assertFalse(vibrator.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30)));
- assertFalse(vibrator.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 255)));
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
- }
-
- @Test
- public void areVibrationFeaturesSupported_noAmplitudeControl_nonFractionalAmplitudes() {
- Vibrator vibrator =
- createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedEffects(VibrationEffect.EFFECT_THUD)
- .build());
-
- // All amplitudes are min, max, or default. Requires no amplitude control.
- assertTrue(vibrator.areVibrationFeaturesSupported(
- waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
- }
-
- @Test
- public void areVibrationFeaturesSupported_withAmplitudeControl() {
- Vibrator vibrator =
- createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
- .build());
-
- // All forms of amplitudes are valid when amplitude control is available.
- assertTrue(vibrator.areVibrationFeaturesSupported(
- waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
- assertTrue(vibrator.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30, 50)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- waveformWithAmplitudes(7, 255, 0, 0, 60)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
- }
-
- @Test
- public void areVibrationFeaturesSupported_primitiveCompositionsWithSupportedPrimitives() {
- Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .build());
-
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .compose()));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- /* scale= */ 0.2f,
- /* delay= */ 200)
- .compose()));
- }
-
- @Test
- public void areVibrationFeaturesSupported_primitiveCompositionsWithUnupportedPrimitives() {
- Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .build());
-
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
- .compose()));
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
- .compose()));
- }
-
- @Test
- public void areVibrationFeaturesSupported_composedEffects_allComponentsSupported() {
- Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
- .build());
-
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .addEffect(VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3},
- /* amplitudes= */ new int[] {10, 20, 255},
- /* repeatIndex= */ -1))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
- .compose()));
-
- vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
- .setSupportedEffects(VibrationEffect.EFFECT_POP, VibrationEffect.EFFECT_CLICK)
- .build());
-
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
- .addEffect(VibrationEffect.createWaveform(
- // These timings are given either 0 or default amplitudes, which
- // do not require vibrator's amplitude control.
- /* timings= */ new long[] {1, 2, 3},
- /* repeatIndex= */ -1))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
- .compose()));
- }
-
- @Test
- public void areVibrationFeaturesSupported_composedEffects_someComponentsUnupported() {
- Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
- .build());
-
- // Not supported due to the TICK primitive, which the vibrator has no support for.
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
- .addEffect(VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3},
- /* amplitudes= */ new int[] {10, 20, 255},
- /* repeatIndex= */ -1))
- .compose()));
- // Not supported due to the THUD effect, which the vibrator has no support for.
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .addEffect(VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3},
- /* amplitudes= */ new int[] {10, 20, 255},
- /* repeatIndex= */ -1))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_THUD))
- .compose()));
-
- vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
- .setSupportedEffects(VibrationEffect.EFFECT_POP)
- .build());
-
- // Not supported due to fractional amplitudes (amplitudes not min (0) or max (255) or
- // DEFAULT), because the vibrator has no amplitude control.
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
- .addEffect(VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3},
- /* amplitudes= */ new int[] {10, 20, 255},
- /* repeatIndex= */ -1))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
- .compose()));
- }
-
/**
* Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
*/
@@ -768,21 +585,4 @@ public class VibratorTest {
assertTrue(info.getFrequencyProfile().isEmpty());
assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
}
-
- private Vibrator createVibratorWithCustomInfo(VibratorInfo info) {
- return new SystemVibrator(mContextSpy) {
- @Override
- public VibratorInfo getInfo() {
- return info;
- }
- };
- }
-
- private static VibrationEffect waveformWithAmplitudes(int...amplitudes) {
- long[] timings = new long[amplitudes.length];
- for (int i = 0; i < timings.length; i++) {
- timings[i] = i * 2; // Arbitrary timings.
- }
- return VibrationEffect.createWaveform(timings, amplitudes, /* repeatIndex= */ -1);
- }
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
index 8268077e72e9..4f5f3c0ddeaf 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -25,18 +25,14 @@ import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertThrows;
import android.os.Parcel;
-import android.os.SystemVibrator;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.os.VibratorInfo;
-import androidx.test.InstrumentationRegistry;
-
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.junit.runners.JUnit4;
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
public class PrebakedSegmentTest {
@Test
@@ -149,121 +145,121 @@ public class PrebakedSegmentTest {
@Test
public void testVibrationFeaturesSupport_idsWithFallback_fallbackEnabled_vibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(
VibrationEffect.EFFECT_TICK,
VibrationEffect.EFFECT_CLICK,
VibrationEffect.EFFECT_DOUBLE_CLICK,
VibrationEffect.EFFECT_HEAVY_CLICK);
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithFallback_fallbackEnabled_noVibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithFallback_fallbackDisabled_vibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(
VibrationEffect.EFFECT_TICK,
VibrationEffect.EFFECT_CLICK,
VibrationEffect.EFFECT_DOUBLE_CLICK,
VibrationEffect.EFFECT_HEAVY_CLICK);
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithFallback_fallbackDisabled_noVibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackEnabled_vibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(
VibrationEffect.EFFECT_THUD,
VibrationEffect.EFFECT_POP,
VibrationEffect.EFFECT_TEXTURE_TICK);
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_THUD)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_POP)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackEnabled_noVibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
assertFalse(createSegmentWithFallback(VibrationEffect.EFFECT_THUD)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithFallback(VibrationEffect.EFFECT_POP)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackDisabled_vibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(
VibrationEffect.EFFECT_THUD,
VibrationEffect.EFFECT_POP,
VibrationEffect.EFFECT_TEXTURE_TICK);
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_THUD)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_POP)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackDisabled_noVibSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_THUD)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_POP)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
@@ -283,14 +279,9 @@ public class PrebakedSegmentTest {
return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
}
- private static Vibrator createVibratorWithSupportedEffects(int... supportedEffects) {
- return new SystemVibrator(InstrumentationRegistry.getContext()) {
- @Override
- public VibratorInfo getInfo() {
- return new VibratorInfo.Builder(/* id= */ 1)
- .setSupportedEffects(supportedEffects)
- .build();
- }
- };
+ private static VibratorInfo createVibratorInfoWithSupportedEffects(int... supportedEffects) {
+ return new VibratorInfo.Builder(/* id= */ 1)
+ .setSupportedEffects(supportedEffects)
+ .build();
}
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index 6f5adcd26ba5..ec5a084c2ddb 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -25,18 +25,14 @@ import static org.testng.Assert.assertThrows;
import android.hardware.vibrator.IVibrator;
import android.os.Parcel;
-import android.os.SystemVibrator;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.os.VibratorInfo;
-import androidx.test.InstrumentationRegistry;
-
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.junit.runners.JUnit4;
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
public class PrimitiveSegmentTest {
private static final float TOLERANCE = 1e-2f;
@@ -146,15 +142,15 @@ public class PrimitiveSegmentTest {
public void testVibrationFeaturesSupport_primitiveSupportedByVibrator() {
assertTrue(createSegment(VibrationEffect.Composition.PRIMITIVE_CLICK)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK)));
assertTrue(createSegment(VibrationEffect.Composition.PRIMITIVE_THUD)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_THUD)));
assertTrue(createSegment(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)));
}
@@ -162,15 +158,15 @@ public class PrimitiveSegmentTest {
public void testVibrationFeaturesSupport_primitiveNotSupportedByVibrator() {
assertFalse(createSegment(VibrationEffect.Composition.PRIMITIVE_CLICK)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_THUD)));
assertFalse(createSegment(VibrationEffect.Composition.PRIMITIVE_THUD)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK)));
assertFalse(createSegment(VibrationEffect.Composition.PRIMITIVE_THUD)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)));
}
@@ -193,15 +189,10 @@ public class PrimitiveSegmentTest {
return new PrimitiveSegment(primitiveId, 0.2f, 10);
}
- private static Vibrator createVibratorWithSupportedPrimitive(int primitiveId) {
- return new SystemVibrator(InstrumentationRegistry.getContext()) {
- @Override
- public VibratorInfo getInfo() {
- return new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(primitiveId, 10)
- .build();
- }
- };
+ private static VibratorInfo createVibratorInfoWithSupportedPrimitive(int primitiveId) {
+ return new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(primitiveId, 10)
+ .build();
}
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index 68870e5a2979..5caa86bb9fb5 100644
--- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -23,31 +23,21 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.hardware.vibrator.IVibrator;
import android.os.Parcel;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.junit.MockitoRule;
+import org.junit.runners.JUnit4;
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
public class RampSegmentTest {
private static final float TOLERANCE = 1e-2f;
- @Rule
- public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Mock
- private Vibrator mVibrator;
-
@Test
public void testCreation() {
RampSegment ramp = new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
@@ -147,71 +137,71 @@ public class RampSegmentTest {
@Test
public void testVibrationFeaturesSupport_amplitudeAndFrequencyControls_supported() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
// Increasing amplitude
- assertTrue(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+ assertTrue(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(info));
// Increasing frequency
- assertTrue(new RampSegment(0.5f, 0.5f, 0, 1, 10).areVibrationFeaturesSupported(mVibrator));
+ assertTrue(new RampSegment(0.5f, 0.5f, 0, 1, 10).areVibrationFeaturesSupported(info));
// Decreasing amplitude
- assertTrue(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+ assertTrue(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(info));
// Decreasing frequency
- assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 10).areVibrationFeaturesSupported(mVibrator));
+ assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 10).areVibrationFeaturesSupported(info));
// Zero duration
- assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 0).areVibrationFeaturesSupported(mVibrator));
+ assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 0).areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_noAmplitudeControl_unsupportedForChangingAmplitude() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
// Test with increasing/decreasing amplitudes.
- assertFalse(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+ assertFalse(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_noAmplitudeControl_fractionalAmplitudeUnsupported() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
- assertFalse(new RampSegment(0.2f, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(0, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(0.2f, 0, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+ assertFalse(new RampSegment(0.2f, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(0, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(0.2f, 0, 0, 0, 10).areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_unchangingZeroAmplitude_supported() {
RampSegment amplitudeZeroWithIncreasingFrequency = new RampSegment(1, 1, 0.5f, 0.8f, 10);
RampSegment amplitudeZeroWithDecreasingFrequency = new RampSegment(1, 1, 0.8f, 0.5f, 10);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
- assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
- assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_unchangingOneAmplitude_supported() {
RampSegment amplitudeOneWithIncreasingFrequency = new RampSegment(1, 1, 0.5f, 0.8f, 10);
RampSegment amplitudeOneWithDecreasingFrequency = new RampSegment(1, 1, 0.8f, 0.5f, 10);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
- assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
- assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(info));
}
@Test
@@ -220,52 +210,52 @@ public class RampSegmentTest {
new RampSegment(DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0.5f, 0.8f, 10);
RampSegment defaultAmplitudeDecreasingFrequency =
new RampSegment(DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0.8f, 0.5f, 10);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
- assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
- assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_noFrequencyControl_unsupportedForChangingFrequency() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
- when(mVibrator.hasFrequencyControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ false);
// Test with increasing/decreasing frequencies.
- assertFalse(new RampSegment(0, 0, 0.2f, 0.4f, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(0, 0, 0.4f, 0.2f, 10).areVibrationFeaturesSupported(mVibrator));
+ assertFalse(new RampSegment(0, 0, 0.2f, 0.4f, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(0, 0, 0.4f, 0.2f, 10).areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_noFrequencyControl_fractionalFrequencyUnsupported() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
- when(mVibrator.hasFrequencyControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ false);
- assertFalse(new RampSegment(0, 0, 0.2f, 0.2f, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(0, 0, 0.2f, 0, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(0, 0, 0, 0.2f, 10).areVibrationFeaturesSupported(mVibrator));
+ assertFalse(new RampSegment(0, 0, 0.2f, 0.2f, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(0, 0, 0.2f, 0, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(0, 0, 0, 0.2f, 10).areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_unchangingZeroFrequency_supported() {
RampSegment frequencyZeroWithIncreasingAmplitude = new RampSegment(0.1f, 1, 0, 0, 10);
RampSegment frequencyZeroWithDecreasingAmplitude = new RampSegment(1, 0.1f, 0, 0, 10);
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
- when(mVibrator.hasFrequencyControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ false);
- assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
- assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(info));
+ assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(info));
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
- assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
- assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(info));
+ assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(info));
}
@Test
@@ -274,4 +264,17 @@ public class RampSegmentTest {
// duration checked in VibrationEffect implementations.
assertTrue(new RampSegment(0.5f, 1, 0, 0, 5_000).isHapticFeedbackCandidate());
}
+
+ private static VibratorInfo createVibInfo(
+ boolean hasAmplitudeControl, boolean hasFrequencyControl) {
+ VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1);
+ long capabilities = 0;
+ if (hasAmplitudeControl) {
+ capabilities |= IVibrator.CAP_AMPLITUDE_CONTROL;
+ }
+ if (hasFrequencyControl) {
+ capabilities |= (IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ }
+ return builder.setCapabilities(capabilities).build();
+ }
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
index 34bb892b07d3..44db30603089 100644
--- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -21,31 +21,20 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.hardware.vibrator.IVibrator;
import android.os.Parcel;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.junit.MockitoRule;
+import org.junit.runners.JUnit4;
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
public class StepSegmentTest {
private static final float TOLERANCE = 1e-2f;
-
- @Rule
- public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Mock
- private Vibrator mVibrator;
-
@Test
public void testCreation() {
StepSegment step = new StepSegment(/* amplitude= */ 1f, /* frequencyHz= */ 1f,
@@ -160,26 +149,26 @@ public class StepSegmentTest {
public void testVibrationFeaturesSupport_zeroAmplitude_supported() {
StepSegment segment =
new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 0);
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_maxAmplitude_supported() {
StepSegment segment =
new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 0);
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
}
@Test
@@ -189,60 +178,60 @@ public class StepSegmentTest {
/* amplitude= */ VibrationEffect.DEFAULT_AMPLITUDE,
/* frequencyHz= */ 0,
/* duration= */ 0);
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_fractionalAmplitude_hasAmplitudeCtrl_supported() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
assertTrue(new StepSegment(/* amplitude= */ 0.2f, /* frequencyHz= */ 0, /* duration= */ 0)
- .areVibrationFeaturesSupported(mVibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_fractionalAmplitude_hasNoAmplitudeCtrl_notSupported() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
assertFalse(new StepSegment(/* amplitude= */ 0.2f, /* frequencyHz= */ 0, /* duration= */ 0)
- .areVibrationFeaturesSupported(mVibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_zeroFrequency_supported() {
StepSegment segment =
new StepSegment(/* amplitude= */ 0f, /* frequencyHz= */ 0, /* duration= */ 0);
- when(mVibrator.hasFrequencyControl()).thenReturn(false);
+ VibratorInfo info = createVibInfoForFrequency(/* hasFrequencyControl= */ false);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ info = createVibInfoForFrequency(/* hasFrequencyControl= */ true);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_nonZeroFrequency_hasFrequencyCtrl_supported() {
StepSegment segment =
new StepSegment(/* amplitude= */ 0f, /* frequencyHz= */ 0.2f, /* duration= */ 0);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ VibratorInfo info = createVibInfoForFrequency(/* hasFrequencyControl= */ true);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_nonZeroFrequency_hasNoFrequencyCtrl_notSupported() {
StepSegment segment =
new StepSegment(/* amplitude= */ 0f, /* frequencyHz= */ 0.2f, /* duration= */ 0);
- when(mVibrator.hasFrequencyControl()).thenReturn(false);
+ VibratorInfo info = createVibInfoForFrequency(/* hasFrequencyControl= */ false);
- assertFalse(segment.areVibrationFeaturesSupported(mVibrator));
+ assertFalse(segment.areVibrationFeaturesSupported(info));
}
@Test
@@ -251,4 +240,21 @@ public class StepSegmentTest {
// duration checked in VibrationEffect implementations.
assertTrue(new StepSegment(0, 0, 5_000).isHapticFeedbackCandidate());
}
+
+ private static VibratorInfo createVibInfoForAmplitude(boolean hasAmplitudeControl) {
+ VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1);
+ if (hasAmplitudeControl) {
+ builder.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ }
+ return builder.build();
+ }
+
+ private static VibratorInfo createVibInfoForFrequency(boolean hasFrequencyControl) {
+ VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1);
+ if (hasFrequencyControl) {
+ builder.setCapabilities(
+ IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ }
+ return builder.build();
+ }
}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 98629a24696a..36bfb98e726b 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -235,6 +235,22 @@ public final class SystemFonts {
}
/**
+ * Get the updated FontConfig.
+ *
+ * @param updatableFontMap a font mapping of updated font files.
+ * @hide
+ */
+ public static @NonNull FontConfig getSystemFontConfigForTesting(
+ @NonNull String fontsXml,
+ @Nullable Map<String, File> updatableFontMap,
+ long lastModifiedDate,
+ int configVersion
+ ) {
+ return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
+ updatableFontMap, lastModifiedDate, configVersion);
+ }
+
+ /**
* Get the system preinstalled FontConfig.
* @hide
*/
diff --git a/keystore/OWNERS b/keystore/OWNERS
index 7ab9d761e236..913f65586cd6 100644
--- a/keystore/OWNERS
+++ b/keystore/OWNERS
@@ -1,4 +1,4 @@
+# Bug component: 189335
eranm@google.com
jbires@google.com
-jdanis@google.com
swillden@google.com
diff --git a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
index 54955c6b7fab..1394bd443f03 100644
--- a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
+++ b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
@@ -325,32 +325,25 @@ public abstract class KeyStore2ParameterUtils {
args.add(KeyStore2ParameterUtils.makeBool(
KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED));
} else {
- if (spec.getUserAuthenticationValidityDurationSeconds() == 0) {
- // Every use of this key needs to be authorized by the user.
- addSids(args, spec);
- args.add(KeyStore2ParameterUtils.makeEnum(
- KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType()
+ addSids(args, spec);
+ args.add(KeyStore2ParameterUtils.makeEnum(
+ KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType()
+ ));
+ if (spec.getUserAuthenticationValidityDurationSeconds() != 0) {
+ args.add(KeyStore2ParameterUtils.makeInt(
+ KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
+ spec.getUserAuthenticationValidityDurationSeconds()
));
-
- if (spec.isUserAuthenticationValidWhileOnBody()) {
+ }
+ if (spec.isUserAuthenticationValidWhileOnBody()) {
+ if (spec.getUserAuthenticationValidityDurationSeconds() == 0) {
throw new ProviderException(
"Key validity extension while device is on-body is not "
+ "supported for keys requiring fingerprint authentication");
}
- } else {
- addSids(args, spec);
- args.add(KeyStore2ParameterUtils.makeEnum(
- KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType()
+ args.add(KeyStore2ParameterUtils.makeBool(
+ KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY
));
- args.add(KeyStore2ParameterUtils.makeInt(
- KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
- spec.getUserAuthenticationValidityDurationSeconds()
- ));
- if (spec.isUserAuthenticationValidWhileOnBody()) {
- args.add(KeyStore2ParameterUtils.makeBool(
- KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY
- ));
- }
}
}
}
diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml
new file mode 100644
index 000000000000..6e4752c9d27d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml
@@ -0,0 +1,34 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="@color/compat_controls_background"
+ android:strokeAlpha="0.8"
+ android:fillAlpha="0.8"
+ android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
+ <group
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:fillColor="@color/compat_controls_text"
+ android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml
new file mode 100644
index 000000000000..141a1ce60b8e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml
@@ -0,0 +1,20 @@
+<?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"
+ android:color="@color/compat_background_ripple">
+ <item android:drawable="@drawable/user_aspect_ratio_settings_button"/>
+</ripple> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index dfaeeeb81c07..257fe1544bbb 100644
--- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -55,7 +55,7 @@
<include android:id="@+id/size_compat_hint"
android:visibility="gone"
- android:layout_width="@dimen/size_compat_hint_width"
+ android:layout_width="@dimen/compat_hint_width"
android:layout_height="wrap_content"
layout="@layout/compat_mode_hint"/>
diff --git a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
new file mode 100644
index 000000000000..433d8546ece0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+<com.android.wm.shell.compatui.UserAspectRatioSettingsLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="bottom|end">
+
+ <include android:id="@+id/user_aspect_ratio_settings_hint"
+ android:visibility="gone"
+ android:layout_width="@dimen/compat_hint_width"
+ android:layout_height="wrap_content"
+ layout="@layout/compat_mode_hint"/>
+
+ <ImageButton
+ android:id="@+id/user_aspect_ratio_settings_button"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/compat_button_margin"
+ android:layout_marginBottom="@dimen/compat_button_margin"
+ android:src="@drawable/user_aspect_ratio_settings_button_ripple"
+ android:background="@android:color/transparent"
+ android:contentDescription="@string/user_aspect_ratio_settings_button_description"/>
+
+</com.android.wm.shell.compatui.UserAspectRatioSettingsLayout>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index ac73e1d87ba2..597e899d098d 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -260,8 +260,8 @@
+ compat_button_margin - compat_hint_corner_radius - compat_hint_point_width / 2). -->
<dimen name="compat_hint_padding_end">7dp</dimen>
- <!-- The width of the size compat hint. -->
- <dimen name="size_compat_hint_width">188dp</dimen>
+ <!-- The width of the compat hint. -->
+ <dimen name="compat_hint_width">188dp</dimen>
<!-- The width of the camera compat hint. -->
<dimen name="camera_compat_hint_width">143dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index b192fdf245e2..8cbc3d016b01 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -173,7 +173,13 @@
<string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
<!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
- <string name="restart_button_description">Tap to restart this app for a better view.</string>
+ <string name="restart_button_description">Tap to restart this app for a better view</string>
+
+ <!-- Tooltip text of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] -->
+ <string name="user_aspect_ratio_settings_button_hint">Change this app\'s aspect ratio in Settings</string>
+
+ <!-- Content description of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] -->
+ <string name="user_aspect_ratio_settings_button_description">Change aspect ratio</string>
<!-- Description of the camera compat button for applying stretched issues treatment in the hint for
compatibility control. [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 0998e7134e00..54f89846ac85 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -16,12 +16,17 @@
package com.android.wm.shell.compatui;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -41,7 +46,6 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -104,6 +108,13 @@ public class CompatUIController implements OnDisplaysChangedListener,
private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>();
/**
+ * The active user aspect ratio settings button layout if there is one (there can be at most
+ * one active).
+ */
+ @Nullable
+ private UserAspectRatioSettingsWindowManager mUserAspectRatioSettingsLayout;
+
+ /**
* The active Letterbox Education layout if there is one (there can be at most one active).
*
* <p>An active layout is a layout that is eligible to be shown for the associated task but
@@ -121,38 +132,51 @@ public class CompatUIController implements OnDisplaysChangedListener,
/** Avoid creating display context frequently for non-default display. */
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
+ @NonNull
private final Context mContext;
+ @NonNull
private final ShellController mShellController;
+ @NonNull
private final DisplayController mDisplayController;
+ @NonNull
private final DisplayInsetsController mDisplayInsetsController;
+ @NonNull
private final DisplayImeController mImeController;
+ @NonNull
private final SyncTransactionQueue mSyncQueue;
+ @NonNull
private final ShellExecutor mMainExecutor;
+ @NonNull
private final Lazy<Transitions> mTransitionsLazy;
+ @NonNull
private final DockStateReader mDockStateReader;
+ @NonNull
private final CompatUIConfiguration mCompatUIConfiguration;
// Only show each hint once automatically in the process life.
+ @NonNull
private final CompatUIHintsState mCompatUIHintsState;
+ @NonNull
private final CompatUIShellCommandHandler mCompatUIShellCommandHandler;
- private CompatUICallback mCallback;
+ @Nullable
+ private CompatUICallback mCompatUICallback;
// Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
// be shown.
private boolean mKeyguardShowing;
- public CompatUIController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- DisplayController displayController,
- DisplayInsetsController displayInsetsController,
- DisplayImeController imeController,
- SyncTransactionQueue syncQueue,
- ShellExecutor mainExecutor,
- Lazy<Transitions> transitionsLazy,
- DockStateReader dockStateReader,
- CompatUIConfiguration compatUIConfiguration,
- CompatUIShellCommandHandler compatUIShellCommandHandler) {
+ public CompatUIController(@NonNull Context context,
+ @NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
+ @NonNull DisplayController displayController,
+ @NonNull DisplayInsetsController displayInsetsController,
+ @NonNull DisplayImeController imeController,
+ @NonNull SyncTransactionQueue syncQueue,
+ @NonNull ShellExecutor mainExecutor,
+ @NonNull Lazy<Transitions> transitionsLazy,
+ @NonNull DockStateReader dockStateReader,
+ @NonNull CompatUIConfiguration compatUIConfiguration,
+ @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -175,9 +199,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
mCompatUIShellCommandHandler.onInit();
}
- /** Sets the callback for UI interactions. */
- public void setCompatUICallback(CompatUICallback callback) {
- mCallback = callback;
+ /** Sets the callback for Compat UI interactions. */
+ public void setCompatUICallback(@NonNull CompatUICallback compatUiCallback) {
+ mCompatUICallback = compatUiCallback;
}
/**
@@ -187,7 +211,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
* @param taskInfo {@link TaskInfo} task the activity is in.
* @param taskListener listener to handle the Task Surface placement.
*/
- public void onCompatInfoChanged(TaskInfo taskInfo,
+ public void onCompatInfoChanged(@NonNull TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (taskInfo != null && !taskInfo.topActivityInSizeCompat) {
mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
@@ -203,6 +227,16 @@ public class CompatUIController implements OnDisplaysChangedListener,
createOrUpdateRestartDialogLayout(taskInfo, taskListener);
if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+ // The user aspect ratio button should not be handled when a new TaskInfo is
+ // sent because of a double tap or when in multi-window mode.
+ if (taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ return;
+ }
+ if (!taskInfo.isFromLetterboxDoubleTap) {
+ createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
+ }
}
}
@@ -280,8 +314,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
return mDisplaysWithIme.contains(displayId);
}
- private void createOrUpdateCompatLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateCompatLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId);
if (layout != null) {
if (layout.needsToBeRecreated(taskInfo, taskListener)) {
@@ -314,7 +348,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
return new CompatUIWindowManager(context,
- taskInfo, mSyncQueue, mCallback, taskListener,
+ taskInfo, mSyncQueue, mCompatUICallback, taskListener,
mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState,
mCompatUIConfiguration, this::onRestartButtonClicked);
}
@@ -328,12 +362,12 @@ public class CompatUIController implements OnDisplaysChangedListener,
mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
onCompatInfoChanged(taskInfoState.first, taskInfoState.second);
} else {
- mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
+ mCompatUICallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
}
}
- private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateLetterboxEduLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mActiveLetterboxEduLayout != null) {
if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
mActiveLetterboxEduLayout.release();
@@ -377,8 +411,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
mDockStateReader, mCompatUIConfiguration);
}
- private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateRestartDialogLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
RestartDialogWindowManager layout =
mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
if (layout != null) {
@@ -423,7 +457,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
private void onRestartDialogCallback(
Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId);
- mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
+ mCompatUICallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
}
private void onRestartDialogDismissCallback(
@@ -432,8 +466,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
onCompatInfoChanged(stateInfo.first, stateInfo.second);
}
- private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mActiveReachabilityEduLayout != null) {
if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
mActiveReachabilityEduLayout.release();
@@ -474,14 +508,67 @@ public class CompatUIController implements OnDisplaysChangedListener,
ShellTaskOrganizer.TaskListener taskListener) {
return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue,
taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
- mCompatUIConfiguration, mMainExecutor);
+ mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed);
}
+ private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo,
+ @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+ // We need to update the UI otherwise it will not be shown until the user relaunches the app
+ createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
+ }
+
+ private void createOrUpdateUserAspectRatioSettingsLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ if (mUserAspectRatioSettingsLayout != null) {
+ if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ } else {
+ // UI already exists, update the UI layout.
+ if (!mUserAspectRatioSettingsLayout.updateCompatInfo(taskInfo, taskListener,
+ showOnDisplay(mUserAspectRatioSettingsLayout.getDisplayId()))) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ }
+ return;
+ }
+ }
+
+ // Create a new UI layout.
+ final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+ if (context == null) {
+ return;
+ }
+ final UserAspectRatioSettingsWindowManager newLayout =
+ createUserAspectRatioSettingsWindowManager(context, taskInfo, taskListener);
+ if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) {
+ // The new layout is eligible to be shown, add it the active layouts.
+ mUserAspectRatioSettingsLayout = newLayout;
+ }
+ }
+
+ @VisibleForTesting
+ @NonNull
+ UserAspectRatioSettingsWindowManager createUserAspectRatioSettingsWindowManager(
+ @NonNull Context context, @NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ return new UserAspectRatioSettingsWindowManager(context, taskInfo, mSyncQueue,
+ taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
+ mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor);
+ }
+
+ private void launchUserAspectRatioSettings(
+ @NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+ final Intent intent = new Intent(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+ }
private void removeLayouts(int taskId) {
- final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
- if (layout != null) {
- layout.release();
+ final CompatUIWindowManager compatLayout = mActiveCompatLayouts.get(taskId);
+ if (compatLayout != null) {
+ compatLayout.release();
mActiveCompatLayouts.remove(taskId);
}
@@ -502,6 +589,12 @@ public class CompatUIController implements OnDisplaysChangedListener,
mActiveReachabilityEduLayout.release();
mActiveReachabilityEduLayout = null;
}
+
+ if (mUserAspectRatioSettingsLayout != null
+ && mUserAspectRatioSettingsLayout.getTaskId() == taskId) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ }
}
private Context getOrCreateDisplayContext(int displayId) {
@@ -557,6 +650,10 @@ public class CompatUIController implements OnDisplaysChangedListener,
if (mActiveReachabilityEduLayout != null && condition.test(mActiveReachabilityEduLayout)) {
callback.accept(mActiveReachabilityEduLayout);
}
+ if (mUserAspectRatioSettingsLayout != null && condition.test(
+ mUserAspectRatioSettingsLayout)) {
+ callback.accept(mUserAspectRatioSettingsLayout);
+ }
}
/** An implementation of {@link OnInsetsChangedListener} for a given display id. */
@@ -591,4 +688,14 @@ public class CompatUIController implements OnDisplaysChangedListener,
insetsChanged(insetsState);
}
}
+
+ /**
+ * A class holding the state of the compat UI hints, which is shared between all compat UI
+ * window managers.
+ */
+ static class CompatUIHintsState {
+ boolean mHasShownSizeCompatHint;
+ boolean mHasShownCameraCompatHint;
+ boolean mHasShownUserAspectRatioSettingsButtonHint;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 065806df3dc8..ce3c5093fdd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -38,6 +38,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import java.util.function.Consumer;
@@ -235,15 +236,4 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN
&& mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED;
}
-
- /**
- * A class holding the state of the compat UI hints, which is shared between all compat UI
- * window managers.
- */
- static class CompatUIHintsState {
- @VisibleForTesting
- boolean mHasShownSizeCompatHint;
- @VisibleForTesting
- boolean mHasShownCameraCompatHint;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index 95bb1fe1c986..9de3f9dec34e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -36,6 +36,8 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import java.util.function.BiConsumer;
+
/**
* Window manager for the reachability education
*/
@@ -73,6 +75,8 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
// we need to animate them.
private boolean mHasLetterboxSizeChanged;
+ private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback;
+
@Nullable
@VisibleForTesting
ReachabilityEduLayout mLayout;
@@ -80,7 +84,8 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
ReachabilityEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
- CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor) {
+ CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor,
+ BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
@@ -89,6 +94,7 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
mCompatUIConfiguration = compatUIConfiguration;
mMainExecutor = mainExecutor;
+ mOnDismissCallback = onDismissCallback;
}
@Override
@@ -217,13 +223,17 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
return;
}
final TaskInfo lastTaskInfo = getLastTaskInfo();
+ final boolean hasSeenHorizontalReachabilityEdu =
+ mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo);
+ final boolean hasSeenVerticalReachabilityEdu =
+ mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo);
final boolean eligibleForDisplayHorizontalEducation = mForceUpdate
- || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo)
+ || !hasSeenHorizontalReachabilityEdu
|| (mHasUserDoubleTapped
&& (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION
|| mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
final boolean eligibleForDisplayVerticalEducation = mForceUpdate
- || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo)
+ || !hasSeenVerticalReachabilityEdu
|| (mHasUserDoubleTapped
&& (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION
|| mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
@@ -239,6 +249,14 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
if (!mHasLetterboxSizeChanged) {
updateHideTime();
mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS);
+ // If reachability education has been seen for the first time, trigger callback to
+ // display aspect ratio settings button once reachability education disappears
+ if (hasShownHorizontalReachabilityEduFirstTime(hasSeenHorizontalReachabilityEdu)
+ || hasShownVerticalReachabilityEduFirstTime(
+ hasSeenVerticalReachabilityEdu)) {
+ mMainExecutor.executeDelayed(this::triggerOnDismissCallback,
+ DISAPPEAR_DELAY_MS);
+ }
}
mHasUserDoubleTapped = false;
} else {
@@ -246,6 +264,38 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
}
}
+ /**
+ * Compares the value of
+ * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} before and after the
+ * layout is shown. Horizontal reachability education is considered seen for the first time if
+ * prior to viewing the layout,
+ * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} is {@code false}
+ * but becomes {@code true} once the current layout is shown.
+ */
+ private boolean hasShownHorizontalReachabilityEduFirstTime(
+ boolean previouslyShownHorizontalReachabilityEducation) {
+ return !previouslyShownHorizontalReachabilityEducation
+ && mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(getLastTaskInfo());
+ }
+
+ /**
+ * Compares the value of
+ * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} before and after the
+ * layout is shown. Horizontal reachability education is considered seen for the first time if
+ * prior to viewing the layout,
+ * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} is {@code false}
+ * but becomes {@code true} once the current layout is shown.
+ */
+ private boolean hasShownVerticalReachabilityEduFirstTime(
+ boolean previouslyShownVerticalReachabilityEducation) {
+ return !previouslyShownVerticalReachabilityEducation
+ && mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(getLastTaskInfo());
+ }
+
+ private void triggerOnDismissCallback() {
+ mOnDismissCallback.accept(getLastTaskInfo(), getTaskListener());
+ }
+
private void hideReachability() {
if (mLayout == null || !shouldHideEducation()) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
new file mode 100644
index 000000000000..5eeb3b650074
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Layout for the user aspect ratio button which opens the app list page in settings
+ * and allows users to change apps aspect ratio.
+ */
+public class UserAspectRatioSettingsLayout extends LinearLayout {
+
+ private static final float ALPHA_FULL_TRANSPARENT = 0f;
+
+ private static final float ALPHA_FULL_OPAQUE = 1f;
+
+ private static final long VISIBILITY_ANIMATION_DURATION_MS = 50;
+
+ private static final String ALPHA_PROPERTY_NAME = "alpha";
+
+ private UserAspectRatioSettingsWindowManager mWindowManager;
+
+ public UserAspectRatioSettingsLayout(Context context) {
+ this(context, null);
+ }
+
+ public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ void inject(@NonNull UserAspectRatioSettingsWindowManager windowManager) {
+ mWindowManager = windowManager;
+ }
+
+ void setUserAspectRatioSettingsHintVisibility(boolean show) {
+ setViewVisibility(R.id.user_aspect_ratio_settings_hint, show);
+ }
+
+ void setUserAspectRatioButtonVisibility(boolean show) {
+ setViewVisibility(R.id.user_aspect_ratio_settings_button, show);
+ // Hint should never be visible without button.
+ if (!show) {
+ setUserAspectRatioSettingsHintVisibility(/* show= */ false);
+ }
+ }
+
+ private void setViewVisibility(@IdRes int resId, boolean show) {
+ final View view = findViewById(resId);
+ int visibility = show ? View.VISIBLE : View.GONE;
+ if (view.getVisibility() == visibility) {
+ return;
+ }
+ if (show) {
+ showItem(view);
+ } else {
+ view.setVisibility(visibility);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ // Need to relayout after changes like hiding / showing a hint since they affect size.
+ // Doing this directly in setUserAspectRatioButtonVisibility can result in flaky animation.
+ mWindowManager.relayout();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ final ImageButton userAspectRatioButton =
+ findViewById(R.id.user_aspect_ratio_settings_button);
+ userAspectRatioButton.setOnClickListener(
+ view -> mWindowManager.onUserAspectRatioSettingsButtonClicked());
+ userAspectRatioButton.setOnLongClickListener(view -> {
+ mWindowManager.onUserAspectRatioSettingsButtonLongClicked();
+ return true;
+ });
+
+ final LinearLayout sizeCompatHint = findViewById(R.id.user_aspect_ratio_settings_hint);
+ ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text))
+ .setText(R.string.user_aspect_ratio_settings_button_hint);
+ sizeCompatHint.setOnClickListener(
+ view -> setUserAspectRatioSettingsHintVisibility(/* show= */ false));
+ }
+
+ private void showItem(@NonNull View view) {
+ view.setVisibility(View.VISIBLE);
+ final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+ ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE);
+ fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS);
+ fadeIn.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.VISIBLE);
+ }
+ });
+ fadeIn.start();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
new file mode 100644
index 000000000000..bd53dc7390c8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Window manager for the user aspect ratio settings button which allows users to go to
+ * app settings and change apps aspect ratio.
+ */
+class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract {
+
+ private static final long SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 500L;
+
+ private static final long HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 4000L;
+
+ private long mNextButtonHideTimeMs = -1L;
+
+ private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnButtonClicked;
+
+ private final ShellExecutor mShellExecutor;
+
+ @VisibleForTesting
+ @NonNull
+ final CompatUIHintsState mCompatUIHintsState;
+
+ @Nullable
+ private UserAspectRatioSettingsLayout mLayout;
+
+ // Remember the last reported states in case visibility changes due to keyguard or IME updates.
+ @VisibleForTesting
+ boolean mHasUserAspectRatioSettingsButton;
+
+ UserAspectRatioSettingsWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo,
+ @NonNull SyncTransactionQueue syncQueue,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener,
+ @NonNull DisplayLayout displayLayout, @NonNull CompatUIHintsState compatUIHintsState,
+ @NonNull BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onButtonClicked,
+ @NonNull ShellExecutor shellExecutor) {
+ super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ mShellExecutor = shellExecutor;
+ mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+ mCompatUIHintsState = compatUIHintsState;
+ mOnButtonClicked = onButtonClicked;
+ }
+
+ @Override
+ protected int getZOrder() {
+ return TASK_CHILD_LAYER_COMPAT_UI + 1;
+ }
+
+ @Override
+ protected @Nullable View getLayout() {
+ return mLayout;
+ }
+
+ @Override
+ protected void removeLayout() {
+ mLayout = null;
+ }
+
+ @Override
+ protected boolean eligibleToShowLayout() {
+ return mHasUserAspectRatioSettingsButton;
+ }
+
+ @Override
+ protected View createLayout() {
+ mLayout = inflateLayout();
+ mLayout.inject(this);
+
+ updateVisibilityOfViews();
+
+ return mLayout;
+ }
+
+ @VisibleForTesting
+ UserAspectRatioSettingsLayout inflateLayout() {
+ return (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.user_aspect_ratio_settings_layout, null);
+ }
+
+ @Override
+ public boolean updateCompatInfo(@NonNull TaskInfo taskInfo,
+ @NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
+ final boolean prevHasUserAspectRatioSettingsButton = mHasUserAspectRatioSettingsButton;
+ mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+
+ if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
+ return false;
+ }
+
+ if (prevHasUserAspectRatioSettingsButton != mHasUserAspectRatioSettingsButton) {
+ updateVisibilityOfViews();
+ }
+ return true;
+ }
+
+ /** Called when the user aspect ratio settings button is clicked. */
+ void onUserAspectRatioSettingsButtonClicked() {
+ mOnButtonClicked.accept(getLastTaskInfo(), getTaskListener());
+ }
+
+ /** Called when the user aspect ratio settings button is long clicked. */
+ void onUserAspectRatioSettingsButtonLongClicked() {
+ if (mLayout == null) {
+ return;
+ }
+ mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+ mShellExecutor.executeDelayed(this::hideUserAspectRatioButton,
+ HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+ }
+
+ @Override
+ @VisibleForTesting
+ public void updateSurfacePosition() {
+ if (mLayout == null) {
+ return;
+ }
+ // Position of the button in the container coordinate.
+ final Rect taskBounds = getTaskBounds();
+ final Rect taskStableBounds = getTaskStableBounds();
+ final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? taskStableBounds.left - taskBounds.left
+ : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
+ final int positionY = taskStableBounds.bottom - taskBounds.top
+ - mLayout.getMeasuredHeight();
+ updateSurfacePosition(positionX, positionY);
+ }
+
+ @VisibleForTesting
+ void updateVisibilityOfViews() {
+ if (mHasUserAspectRatioSettingsButton) {
+ mShellExecutor.executeDelayed(this::showUserAspectRatioButton,
+ SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+ mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+ mShellExecutor.executeDelayed(this::hideUserAspectRatioButton,
+ HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+ } else {
+ mShellExecutor.removeCallbacks(this::showUserAspectRatioButton);
+ mShellExecutor.execute(this::hideUserAspectRatioButton);
+ }
+ }
+
+ private void showUserAspectRatioButton() {
+ if (mLayout == null) {
+ return;
+ }
+ mLayout.setUserAspectRatioButtonVisibility(true);
+ // Only show by default for the first time.
+ if (!mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint) {
+ mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+ }
+ }
+
+ private void hideUserAspectRatioButton() {
+ if (mLayout == null || !isHideDelayReached(mNextButtonHideTimeMs)) {
+ return;
+ }
+ mLayout.setUserAspectRatioButtonVisibility(false);
+ }
+
+ private boolean isHideDelayReached(long nextHideTime) {
+ return SystemClock.uptimeMillis() >= nextHideTime;
+ }
+
+ private long updateHideTime(long hideDelay) {
+ return SystemClock.uptimeMillis() + hideDelay;
+ }
+
+ private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
+ return taskInfo.topActivityEligibleForUserAspectRatioButton
+ && (taskInfo.topActivityBoundsLetterboxed
+ || taskInfo.isUserFullscreenOverrideEnabled);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 0d55018ba580..19c60c2a9117 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,7 +63,6 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
-import android.os.SystemProperties;
import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
@@ -1735,17 +1734,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// animation.
// TODO(b/272819817): cleanup the null-check and extra logging.
final boolean hasTopActivityInfo = mTaskInfo.topActivityInfo != null;
- if (!hasTopActivityInfo) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "%s: TaskInfo.topActivityInfo is null", TAG);
- }
- if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", true)
- && hasTopActivityInfo) {
+ if (hasTopActivityInfo) {
animator.setAppIconContentOverlay(
mContext, currentBounds, mTaskInfo.topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
} else {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: TaskInfo.topActivityInfo is null", TAG);
animator.setColorContentOverlay(mContext);
}
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 2563d984b793..db7e2c0c529f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -49,7 +49,6 @@ import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
-import android.os.SystemProperties;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -902,17 +901,13 @@ public class PipTransition extends PipTransitionController {
// animation.
// TODO(b/272819817): cleanup the null-check and extra logging.
final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
- if (!hasTopActivityInfo) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "%s: TaskInfo.topActivityInfo is null", TAG);
- }
- if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", true)
- && hasTopActivityInfo) {
+ if (hasTopActivityInfo) {
animator.setAppIconContentOverlay(
mContext, currentBounds, taskInfo.topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
} else {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: TaskInfo.topActivityInfo is null", TAG);
animator.setColorContentOverlay(mContext);
}
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
index f9332e4bdb2e..2d3403599484 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
@@ -40,6 +40,7 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac
"persist.wm.debug.enable_pip_keep_clear_algorithm_gravity", false);
protected int mKeepClearAreasPadding;
+ private int mImeOffset;
public PhonePipKeepClearAlgorithm(Context context) {
reloadResources(context);
@@ -48,6 +49,7 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac
private void reloadResources(Context context) {
final Resources res = context.getResources();
mKeepClearAreasPadding = res.getDimensionPixelSize(R.dimen.pip_keep_clear_areas_padding);
+ mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
}
/**
@@ -61,7 +63,7 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac
Rect insets = new Rect();
pipBoundsAlgorithm.getInsetBounds(insets);
if (pipBoundsState.isImeShowing()) {
- insets.bottom -= pipBoundsState.getImeHeight();
+ insets.bottom -= (pipBoundsState.getImeHeight() + mImeOffset);
}
// if PiP is stashed we only adjust the vertical position if it's outside of insets and
// ignore all keep clear areas, since it's already on the side
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index a6501f05475f..efc69ebd395c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -58,6 +58,8 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,8 +68,6 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import dagger.Lazy;
-
/**
* Tests for {@link CompatUIController}.
*
@@ -82,21 +82,36 @@ public class CompatUIControllerTest extends ShellTestCase {
private CompatUIController mController;
private ShellInit mShellInit;
- private @Mock ShellController mMockShellController;
- private @Mock DisplayController mMockDisplayController;
- private @Mock DisplayInsetsController mMockDisplayInsetsController;
- private @Mock DisplayLayout mMockDisplayLayout;
- private @Mock DisplayImeController mMockImeController;
- private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
- private @Mock SyncTransactionQueue mMockSyncQueue;
- private @Mock ShellExecutor mMockExecutor;
- private @Mock Lazy<Transitions> mMockTransitionsLazy;
- private @Mock CompatUIWindowManager mMockCompatLayout;
- private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
- private @Mock RestartDialogWindowManager mMockRestartDialogLayout;
- private @Mock DockStateReader mDockStateReader;
- private @Mock CompatUIConfiguration mCompatUIConfiguration;
- private @Mock CompatUIShellCommandHandler mCompatUIShellCommandHandler;
+ @Mock
+ private ShellController mMockShellController;
+ @Mock
+ private DisplayController mMockDisplayController;
+ @Mock
+ private DisplayInsetsController mMockDisplayInsetsController;
+ @Mock
+ private DisplayLayout mMockDisplayLayout;
+ @Mock
+ private DisplayImeController mMockImeController;
+ @Mock
+ private ShellTaskOrganizer.TaskListener mMockTaskListener;
+ @Mock
+ private SyncTransactionQueue mMockSyncQueue;
+ @Mock
+ private ShellExecutor mMockExecutor;
+ @Mock
+ private Lazy<Transitions> mMockTransitionsLazy;
+ @Mock
+ private CompatUIWindowManager mMockCompatLayout;
+ @Mock
+ private LetterboxEduWindowManager mMockLetterboxEduLayout;
+ @Mock
+ private RestartDialogWindowManager mMockRestartDialogLayout;
+ @Mock
+ private DockStateReader mDockStateReader;
+ @Mock
+ private CompatUIConfiguration mCompatUIConfiguration;
+ @Mock
+ private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 5f294d53b662..3bce2b824e28 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -44,7 +44,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import junit.framework.Assert;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 78c3cbdaace6..4c837e635939 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -53,7 +53,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import junit.framework.Assert;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index 973a99c269ea..a802f15a0a41 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -40,6 +40,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.BiConsumer;
+
/**
* Tests for {@link ReachabilityEduWindowManager}.
*
@@ -57,6 +59,8 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
private CompatUIConfiguration mCompatUIConfiguration;
@Mock
private DisplayLayout mDisplayLayout;
+ @Mock
+ private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback;
private TestShellExecutor mExecutor;
private TaskInfo mTaskInfo;
private ReachabilityEduWindowManager mWindowManager;
@@ -104,6 +108,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) {
return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue,
- mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor);
+ mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor,
+ mOnDismissCallback);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
new file mode 100644
index 000000000000..1fee153877b5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
+import android.content.ComponentName;
+import android.testing.AndroidTestingRunner;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.SurfaceControlViewHost;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Tests for {@link UserAspectRatioSettingsLayout}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:UserAspectRatioSettingsLayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
+
+ private static final int TASK_ID = 1;
+
+ @Mock
+ private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock
+ private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener>
+ mOnUserAspectRatioSettingsButtonClicked;
+ @Mock
+ private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock
+ private SurfaceControlViewHost mViewHost;
+ @Captor
+ private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor;
+ @Captor
+ private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor;
+
+ private UserAspectRatioSettingsWindowManager mWindowManager;
+ private UserAspectRatioSettingsLayout mLayout;
+ private TaskInfo mTaskInfo;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
+ mSyncTransactionQueue, mTaskListener, new DisplayLayout(),
+ new CompatUIController.CompatUIHintsState(),
+ mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor());
+
+ mLayout = (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.user_aspect_ratio_settings_layout, null);
+ mLayout.inject(mWindowManager);
+
+ spyOn(mWindowManager);
+ spyOn(mLayout);
+ doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+ doReturn(mLayout).when(mWindowManager).inflateLayout();
+ }
+
+ @Test
+ public void testOnClickForUserAspectRatioSettingsButton() {
+ final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
+ button.performClick();
+
+ verify(mWindowManager).onUserAspectRatioSettingsButtonClicked();
+ verify(mOnUserAspectRatioSettingsButtonClicked).accept(
+ mUserAspectRationTaskInfoCaptor.capture(),
+ mUserAspectRatioTaskListenerCaptor.capture());
+ final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result =
+ new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(),
+ mUserAspectRatioTaskListenerCaptor.getValue());
+ Assert.assertEquals(mTaskInfo, result.first);
+ Assert.assertEquals(mTaskListener, result.second);
+ }
+
+ @Test
+ public void testOnLongClickForUserAspectRatioButton() {
+ doNothing().when(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
+
+ final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
+ button.performLongClick();
+
+ verify(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
+ }
+
+ @Test
+ public void testOnClickForUserAspectRatioSettingsHint() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+ final LinearLayout sizeCompatHint = mLayout.findViewById(
+ R.id.user_aspect_ratio_settings_hint);
+ sizeCompatHint.performClick();
+
+ verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ false);
+ }
+
+ private static TaskInfo createTaskInfo(boolean hasSizeCompat,
+ @CameraCompatControlState int cameraCompatControlState) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = TASK_ID;
+ taskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.cameraCompatControlState = cameraCompatControlState;
+ taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
new file mode 100644
index 000000000000..b48538ca99ca
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.view.WindowInsets.Type.navigationBars;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.util.Pair;
+import android.view.DisplayInfo;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.BiConsumer;
+
+/**
+ * Tests for {@link UserAspectRatioSettingsWindowManager}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:UserAspectRatioSettingsWindowManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
+
+ private static final int TASK_ID = 1;
+
+ @Mock private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock
+ private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener>
+ mOnUserAspectRatioSettingsButtonClicked;
+ @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock private UserAspectRatioSettingsLayout mLayout;
+ @Mock private SurfaceControlViewHost mViewHost;
+ @Captor
+ private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor;
+ @Captor
+ private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor;
+
+ private final Set<String> mPackageNameCache = new HashSet<>();
+
+ private UserAspectRatioSettingsWindowManager mWindowManager;
+ private TaskInfo mTaskInfo;
+
+ private TestShellExecutor mExecutor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mExecutor = new TestShellExecutor();
+ mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ false, /* topActivityBoundsLetterboxed */ true);
+ mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
+ mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mOnUserAspectRatioSettingsButtonClicked, mExecutor);
+ spyOn(mWindowManager);
+ doReturn(mLayout).when(mWindowManager).inflateLayout();
+ doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+ }
+
+ @Test
+ public void testCreateUserAspectRatioButton() {
+ // Doesn't create layout if show is false.
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ false));
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Doesn't create hint popup.
+ mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager).inflateLayout();
+ mExecutor.flushAll();
+ verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true);
+ verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+
+ // Creates hint popup.
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ mWindowManager.release();
+ mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = false;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager).inflateLayout();
+ assertNotNull(mLayout);
+ mExecutor.flushAll();
+ verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true);
+ verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ assertTrue(mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint);
+
+ // Returns false and doesn't create layout if mHasUserAspectRatioSettingsButton is false.
+ clearInvocations(mWindowManager);
+ mWindowManager.release();
+ mWindowManager.mHasUserAspectRatioSettingsButton = false;
+ assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager, never()).inflateLayout();
+ }
+
+ @Test
+ public void testRelease() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+
+ mWindowManager.release();
+
+ verify(mViewHost).release();
+ }
+
+ @Test
+ public void testUpdateCompatInfo() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+
+ // No diff
+ clearInvocations(mWindowManager);
+ TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
+
+ verify(mWindowManager, never()).updateSurfacePosition();
+ verify(mWindowManager, never()).release();
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+
+
+ // Change task listener, recreate button.
+ clearInvocations(mWindowManager);
+ final ShellTaskOrganizer.TaskListener newTaskListener = mock(
+ ShellTaskOrganizer.TaskListener.class);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+ verify(mWindowManager).release();
+ verify(mWindowManager).createLayout(/* canShow= */ true);
+
+ // Change has eligibleForUserAspectRatioButton to false, dispose the component
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ false, /* topActivityBoundsLetterboxed */ true);
+ assertFalse(
+ mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+ verify(mWindowManager).release();
+ }
+
+ @Test
+ public void testUpdateCompatInfoLayoutNotInflatedYet() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ false);
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Change topActivityInSizeCompat to false and pass canShow true, layout shouldn't be
+ // inflated
+ clearInvocations(mWindowManager);
+ TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ false, /* topActivityBoundsLetterboxed */ true);
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated.
+ clearInvocations(mWindowManager);
+ taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true);
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+ }
+
+ @Test
+ public void testUpdateDisplayLayout() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+
+ mWindowManager.updateDisplayLayout(displayLayout1);
+ verify(mWindowManager).updateSurfacePosition();
+
+ // No update if the display bounds is the same.
+ clearInvocations(mWindowManager);
+ final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+ mWindowManager.updateDisplayLayout(displayLayout2);
+ verify(mWindowManager, never()).updateSurfacePosition();
+ }
+
+ @Test
+ public void testUpdateDisplayLayoutInsets() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ final DisplayLayout displayLayout = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+
+ mWindowManager.updateDisplayLayout(displayLayout);
+ verify(mWindowManager).updateSurfacePosition();
+
+ // Update if the insets change on the existing display layout
+ clearInvocations(mWindowManager);
+ InsetsState insetsState = new InsetsState();
+ insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000));
+ InsetsSource insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ insetsSource.setFrame(0, 1800, 1000, 2000);
+ insetsState.addSource(insetsSource);
+ displayLayout.setInsets(mContext.getResources(), insetsState);
+ mWindowManager.updateDisplayLayout(displayLayout);
+ verify(mWindowManager).updateSurfacePosition();
+ }
+
+ @Test
+ public void testUpdateVisibility() {
+ // Create button if it is not created.
+ mWindowManager.removeLayout();
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.updateVisibility(/* canShow= */ true);
+
+ verify(mWindowManager).createLayout(/* canShow= */ true);
+
+ // Hide button.
+ clearInvocations(mWindowManager);
+ doReturn(View.VISIBLE).when(mLayout).getVisibility();
+ mWindowManager.updateVisibility(/* canShow= */ false);
+
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+ verify(mLayout).setVisibility(View.GONE);
+
+ // Show button.
+ doReturn(View.GONE).when(mLayout).getVisibility();
+ mWindowManager.updateVisibility(/* canShow= */ true);
+
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+ verify(mLayout).setVisibility(View.VISIBLE);
+ }
+
+ @Test
+ public void testAttachToParentSurface() {
+ final SurfaceControl.Builder b = new SurfaceControl.Builder();
+ mWindowManager.attachToParentSurface(b);
+
+ verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b);
+ }
+
+ @Test
+ public void testOnUserAspectRatioButtonClicked() {
+ mWindowManager.onUserAspectRatioSettingsButtonClicked();
+
+ verify(mOnUserAspectRatioSettingsButtonClicked).accept(
+ mUserAspectRationTaskInfoCaptor.capture(),
+ mUserAspectRatioTaskListenerCaptor.capture());
+ final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result =
+ new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(),
+ mUserAspectRatioTaskListenerCaptor.getValue());
+ Assert.assertEquals(mTaskInfo, result.first);
+ Assert.assertEquals(mTaskListener, result.second);
+ }
+
+ @Test
+ public void testOnUserAspectRatioButtonLongClicked_showHint() {
+ // Not create hint popup.
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+ verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+
+ mWindowManager.onUserAspectRatioSettingsButtonLongClicked();
+
+ verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ }
+
+ @Test
+ public void testWhenDockedStateHasChanged_needsToBeRecreated() {
+ ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
+ newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
+
+ Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
+ }
+
+ private static TaskInfo createTaskInfo(boolean eligibleForUserAspectRatioButton,
+ boolean topActivityBoundsLetterboxed) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = TASK_ID;
+ taskInfo.topActivityEligibleForUserAspectRatioButton = eligibleForUserAspectRatioButton;
+ taskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed;
+ taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
+ taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
+ return taskInfo;
+ }
+}
diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS
index 17f5164cf417..436f10737cb4 100644
--- a/libs/androidfw/OWNERS
+++ b/libs/androidfw/OWNERS
@@ -4,4 +4,4 @@ zyy@google.com
patb@google.com
per-file CursorWindow.cpp=omakoto@google.com
-per-file LocaleDataTables.cpp=vichang@google.com,ngeoffray@google.com,nikitai@google.com
+per-file LocaleDataTables.cpp=vichang@google.com,ngeoffray@google.com
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
index 0b74fa8ee361..d1bcb5746414 100644
--- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -2,7 +2,7 @@ package: "com.android.settingslib.media.flags"
flag {
name: "use_media_router2_for_info_media_manager"
- namespace: "placeholder_namespace"
+ namespace: "media_solutions"
description: "Gates whether to use a MediaRouter2-based implementation of InfoMediaManager, instead of the legacy MediaRouter2Manager-based implementation."
bug: "192657812"
} \ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index ef062dfd3ec3..4b10b56f49fb 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -23,14 +23,16 @@ import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.ContentProvider;
-import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.MatrixCursor;
@@ -44,8 +46,8 @@ import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
+import android.util.ArrayMap;
-import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
@@ -77,13 +79,14 @@ public class SettingsHelperTest {
@Mock private Context mContext;
@Mock private Resources mResources;
- @Mock private ContentResolver mContentResolver;
@Mock private AudioManager mAudioManager;
@Mock private TelephonyManager mTelephonyManager;
+ @Mock private MockContentResolver mContentResolver;
+ private MockSettingsProvider mSettingsProvider;
+
@Before
public void setUp() {
- clearLongPressPowerValues();
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(eq(Context.AUDIO_SERVICE))).thenReturn(mAudioManager);
when(mContext.getSystemService(eq(Context.TELEPHONY_SERVICE))).thenReturn(
@@ -91,14 +94,20 @@ public class SettingsHelperTest {
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getApplicationContext()).thenReturn(mContext);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
- when(mContext.getContentResolver()).thenReturn(getContentResolver());
mSettingsHelper = spy(new SettingsHelper(mContext));
+ mContentResolver = spy(new MockContentResolver());
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ mSettingsProvider = new MockSettingsProvider(mContext);
+ mContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
}
@After
public void tearDown() {
- clearLongPressPowerValues();
+ Settings.Global.putString(mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS,
+ null);
+ Settings.Global.putString(mContentResolver, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
+ null);
}
@Test
@@ -123,33 +132,30 @@ public class SettingsHelperTest {
mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
SETTING_KEY, SETTING_VALUE, /* restoredFromSdkInt */ 0);
- verifyZeroInteractions(mContentResolver);
+ // The only time of interaction happened during setUp()
+ verify(mContentResolver, times(1))
+ .addProvider(Settings.AUTHORITY, mSettingsProvider);
+
+ verifyNoMoreInteractions(mContentResolver);
}
@Test
public void testRestoreValue_lppForAssistantEnabled_updatesValue() {
- ContentResolver cr =
- InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getContentResolver();
when(mResources.getBoolean(
R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
true);
- mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+ mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
Settings.Global.POWER_BUTTON_LONG_PRESS, "5", 0);
- assertThat(
- Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))
- .isEqualTo(5);
- assertThat(Settings.Global.getInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
- -1)).isEqualTo(2);
+ assertThat(Settings.Global.getInt(
+ mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS, -1)).isEqualTo(5);
+ assertThat(Settings.Global.getInt(
+ mContentResolver, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, -1)).isEqualTo(2);
}
@Test
public void testRestoreValue_lppForAssistantNotEnabled_updatesValueToDefaultConfig() {
- ContentResolver cr =
- InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getContentResolver();
when(mResources.getBoolean(
R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
true);
@@ -161,21 +167,17 @@ public class SettingsHelperTest {
R.integer.config_keyChordPowerVolumeUp)).thenReturn(
1);
- mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+ mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
Settings.Global.POWER_BUTTON_LONG_PRESS, "2", 0);
- assertThat(
- Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))
- .isEqualTo(1);
- assertThat(Settings.Global.getInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
- -1)).isEqualTo(1);
+ assertThat(Settings.Global.getInt(
+ mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS, -1)).isEqualTo(1);
+ assertThat(Settings.Global.getInt(
+ mContentResolver, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, -1)).isEqualTo(1);
}
@Test
public void testRestoreValue_lppForAssistantNotEnabledDefaultConfig_updatesValue() {
- ContentResolver cr =
- InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getContentResolver();
when(mResources.getBoolean(
R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
true);
@@ -187,47 +189,39 @@ public class SettingsHelperTest {
R.integer.config_keyChordPowerVolumeUp)).thenReturn(
1);
- mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+ mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
Settings.Global.POWER_BUTTON_LONG_PRESS, "2", 0);
- assertThat(
- Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))
- .isEqualTo(1);
- assertThat(Settings.Global.getInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
- -1)).isEqualTo(1);
+ assertThat(Settings.Global.getInt(
+ mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS, -1)).isEqualTo(1);
+ assertThat(Settings.Global.getInt(
+ mContentResolver, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, -1)).isEqualTo(1);
}
@Test
public void testRestoreValue_lppForAssistantNotAvailable_doesNotRestore() {
- ContentResolver cr =
- InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getContentResolver();
- when(mResources.getBoolean(
- R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
- false);
+ when(mResources.getBoolean(R.bool.config_longPressOnPowerForAssistantSettingAvailable))
+ .thenReturn(false);
- mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
- Settings.Global.POWER_BUTTON_LONG_PRESS, "5", 0);
+ mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
+ Settings.Global.POWER_BUTTON_LONG_PRESS, "500", 0);
- assertThat((Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
- -1))).isEqualTo(-1);
+ assertThat((Settings.Global.getInt(
+ mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))).isEqualTo(-1);
}
@Test
public void testRestoreValue_lppForAssistantInvalid_doesNotRestore() {
- ContentResolver cr =
- InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getContentResolver();
when(mResources.getBoolean(
R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
false);
- mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+ mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
Settings.Global.POWER_BUTTON_LONG_PRESS, "trees", 0);
- assertThat((Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
- -1))).isEqualTo(-1);
+ assertThat((Settings.Global.getInt(
+ mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))).isEqualTo(-1);
}
@Test
@@ -363,9 +357,6 @@ public class SettingsHelperTest {
final String newRingtoneValueCanonicalized =
"content://media/internal/audio/media/100?title=Song&canonical=1";
- MockContentResolver mMockContentResolver = new MockContentResolver();
- when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
ContentProvider mockMediaContentProvider =
new MockContentProvider(mContext) {
@Override
@@ -386,25 +377,22 @@ public class SettingsHelperTest {
}
};
- ContentProvider mockSettingsContentProvider =
- new MockSettingsProvider(mContext, getContentResolver());
- mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
- mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+ mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
- resetRingtoneSettingsToDefault(mMockContentResolver);
- assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ resetRingtoneSettingsToDefault();
+ assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
.isEqualTo(DEFAULT_RINGTONE_VALUE);
mSettingsHelper.restoreValue(
mContext,
- mMockContentResolver,
+ mContentResolver,
new ContentValues(),
Uri.EMPTY,
Settings.System.RINGTONE,
sourceRingtoneValue,
0);
- assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
.isEqualTo(newRingtoneValueCanonicalized);
}
@@ -417,9 +405,6 @@ public class SettingsHelperTest {
final String newRingtoneValueCanonicalized =
"content://0@media/external/audio/media/100?title=Song&canonical=1";
- MockContentResolver mMockContentResolver = new MockContentResolver();
- when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
cursor.addRow(new Object[] {100L});
@@ -458,24 +443,21 @@ public class SettingsHelperTest {
}
};
- ContentProvider mockSettingsContentProvider =
- new MockSettingsProvider(mContext, getContentResolver());
- mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
- mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
- mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+ mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
- resetRingtoneSettingsToDefault(mMockContentResolver);
+ resetRingtoneSettingsToDefault();
mSettingsHelper.restoreValue(
mContext,
- mMockContentResolver,
+ mContentResolver,
new ContentValues(),
Uri.EMPTY,
Settings.System.RINGTONE,
sourceRingtoneValue,
0);
- assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
.isEqualTo(newRingtoneValueCanonicalized);
}
@@ -488,9 +470,6 @@ public class SettingsHelperTest {
final String newRingtoneValueCanonicalized =
"content://0@media/external/audio/media/200?title=notificationPing&canonicalize=1";
- MockContentResolver mMockContentResolver = new MockContentResolver();
- when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
cursor.addRow(new Object[] {200L});
@@ -529,17 +508,14 @@ public class SettingsHelperTest {
}
};
- ContentProvider mockSettingsContentProvider =
- new MockSettingsProvider(mContext, getContentResolver());
- mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
- mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
- mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+ mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
- resetRingtoneSettingsToDefault(mMockContentResolver);
+ resetRingtoneSettingsToDefault();
mSettingsHelper.restoreValue(
mContext,
- mMockContentResolver,
+ mContentResolver,
new ContentValues(),
Uri.EMPTY,
Settings.System.NOTIFICATION_SOUND,
@@ -548,7 +524,7 @@ public class SettingsHelperTest {
assertThat(
Settings.System.getString(
- mMockContentResolver, Settings.System.NOTIFICATION_SOUND))
+ mContentResolver, Settings.System.NOTIFICATION_SOUND))
.isEqualTo(newRingtoneValueCanonicalized);
}
@@ -561,9 +537,6 @@ public class SettingsHelperTest {
final String newRingtoneValueCanonicalized =
"content://0@media/external/audio/media/300?title=alarmSound&canonical=1";
- MockContentResolver mMockContentResolver = new MockContentResolver();
- when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
cursor.addRow(new Object[] {300L});
@@ -600,26 +573,29 @@ public class SettingsHelperTest {
assertThat(selectionArgs).isEqualTo(new String[] {"alarmSound"});
return cursor;
}
+
+ @Override
+ public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType,
+ Bundle opts) {
+ return null;
+ }
};
- ContentProvider mockSettingsContentProvider =
- new MockSettingsProvider(mContext, getContentResolver());
- mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
- mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
- mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+ mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
- resetRingtoneSettingsToDefault(mMockContentResolver);
+ resetRingtoneSettingsToDefault();
mSettingsHelper.restoreValue(
mContext,
- mMockContentResolver,
+ mContentResolver,
new ContentValues(),
Uri.EMPTY,
Settings.System.ALARM_ALERT,
sourceRingtoneValue,
0);
- assertThat(Settings.System.getString(mMockContentResolver, Settings.System.ALARM_ALERT))
+ assertThat(Settings.System.getString(mContentResolver, Settings.System.ALARM_ALERT))
.isEqualTo(newRingtoneValueCanonicalized);
}
@@ -628,9 +604,6 @@ public class SettingsHelperTest {
final String sourceRingtoneValue =
"content://0@media/external/audio/media/1?title=Song&canonical=1";
- MockContentResolver mMockContentResolver = new MockContentResolver();
- when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
// This is to mock the case that there are multiple results by querying title +
// ringtone_type.
MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
@@ -651,32 +624,26 @@ public class SettingsHelperTest {
}
};
- ContentProvider mockSettingsContentProvider =
- new MockSettingsProvider(mContext, getContentResolver());
- mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
- mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
- mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+ mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
- resetRingtoneSettingsToDefault(mMockContentResolver);
+ resetRingtoneSettingsToDefault();
mSettingsHelper.restoreValue(
mContext,
- mMockContentResolver,
+ mContentResolver,
new ContentValues(),
Uri.EMPTY,
Settings.System.RINGTONE,
sourceRingtoneValue,
0);
- assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
.isEqualTo(DEFAULT_RINGTONE_VALUE);
}
@Test
public void testRestoreValue_customRingtone_restoreSilentValue() {
- MockContentResolver mMockContentResolver = new MockContentResolver();
- when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
ContentProvider mockMediaContentProvider =
new MockContentProvider(mContext) {
@Override
@@ -691,37 +658,46 @@ public class SettingsHelperTest {
}
};
- ContentProvider mockSettingsContentProvider =
- new MockSettingsProvider(mContext, getContentResolver());
- mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
- mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+ mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
- resetRingtoneSettingsToDefault(mMockContentResolver);
+ resetRingtoneSettingsToDefault();
mSettingsHelper.restoreValue(
mContext,
- mMockContentResolver,
+ mContentResolver,
new ContentValues(),
Uri.EMPTY,
Settings.System.RINGTONE,
"_silent",
0);
- assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
.isEqualTo(null);
}
- public static class MockSettingsProvider extends MockContentProvider {
- ContentResolver mBaseContentResolver;
-
- public MockSettingsProvider(Context context, ContentResolver baseContentResolver) {
+ private static class MockSettingsProvider extends MockContentProvider {
+ private final ArrayMap<String, String> mKeyValueStore = new ArrayMap<>();
+ MockSettingsProvider(Context context) {
super(context);
- this.mBaseContentResolver = baseContentResolver;
}
@Override
public Bundle call(String method, String request, Bundle args) {
- return mBaseContentResolver.call(Settings.AUTHORITY, method, request, args);
+ if (method.startsWith("PUT_")) {
+ mKeyValueStore.put(request, args.getString("value"));
+ return null;
+ } else if (method.startsWith("GET_")) {
+ return Bundle.forPair("value", mKeyValueStore.getOrDefault(request, ""));
+ }
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ String name = values.getAsString("name");
+ String value = values.getAsString("value");
+ mKeyValueStore.put(name, value);
+ return null;
}
}
@@ -752,15 +728,13 @@ public class SettingsHelperTest {
}
private int getAutoRotationSettingValue() {
- return Settings.System.getInt(
- getContentResolver(),
+ return Settings.System.getInt(mContentResolver,
Settings.System.ACCELEROMETER_ROTATION,
/* default= */ -1);
}
private void setAutoRotationSettingValue(int value) {
- Settings.System.putInt(
- getContentResolver(),
+ Settings.System.putInt(mContentResolver,
Settings.System.ACCELEROMETER_ROTATION,
value
);
@@ -769,7 +743,7 @@ public class SettingsHelperTest {
private void restoreAutoRotationSetting(int newValue) {
mSettingsHelper.restoreValue(
mContext,
- getContentResolver(),
+ mContentResolver,
new ContentValues(),
/* destination= */ Settings.System.CONTENT_URI,
/* name= */ Settings.System.ACCELEROMETER_ROTATION,
@@ -777,31 +751,19 @@ public class SettingsHelperTest {
/* restoredFromSdkInt= */ 0);
}
- private ContentResolver getContentResolver() {
- return InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getContentResolver();
- }
-
- private void clearLongPressPowerValues() {
- ContentResolver cr = InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getContentResolver();
- Settings.Global.putString(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, null);
- Settings.Global.putString(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, null);
- }
-
- private void resetRingtoneSettingsToDefault(ContentResolver contentResolver) {
+ private void resetRingtoneSettingsToDefault() {
Settings.System.putString(
- contentResolver, Settings.System.RINGTONE, DEFAULT_RINGTONE_VALUE);
+ mContentResolver, Settings.System.RINGTONE, DEFAULT_RINGTONE_VALUE);
Settings.System.putString(
- contentResolver, Settings.System.NOTIFICATION_SOUND, DEFAULT_NOTIFICATION_VALUE);
+ mContentResolver, Settings.System.NOTIFICATION_SOUND, DEFAULT_NOTIFICATION_VALUE);
Settings.System.putString(
- contentResolver, Settings.System.ALARM_ALERT, DEFAULT_ALARM_VALUE);
+ mContentResolver, Settings.System.ALARM_ALERT, DEFAULT_ALARM_VALUE);
- assertThat(Settings.System.getString(contentResolver, Settings.System.RINGTONE))
+ assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
.isEqualTo(DEFAULT_RINGTONE_VALUE);
- assertThat(Settings.System.getString(contentResolver, Settings.System.NOTIFICATION_SOUND))
+ assertThat(Settings.System.getString(mContentResolver, Settings.System.NOTIFICATION_SOUND))
.isEqualTo(DEFAULT_NOTIFICATION_VALUE);
- assertThat(Settings.System.getString(contentResolver, Settings.System.ALARM_ALERT))
+ assertThat(Settings.System.getString(mContentResolver, Settings.System.ALARM_ALERT))
.isEqualTo(DEFAULT_ALARM_VALUE);
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 889c026a0568..dd71dfa0b008 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -48,6 +48,7 @@ fun SceneScope.Notifications(
Column(
modifier =
modifier
+ .element(key = Notifications.Elements.Notifications)
.fillMaxWidth()
.defaultMinSize(minHeight = 300.dp)
.clip(RoundedCornerShape(32.dp))
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
index 38712b01ae44..291617f8edde 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
@@ -1,11 +1,12 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.scene.ui.composable.QuickSettings
fun TransitionBuilder.goneToQuickSettingsTransition() {
spec = tween(durationMillis = 500)
- fade(QuickSettings.rootElementKey)
+ translate(QuickSettings.rootElementKey, Edge.Top, true)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 1d57c1a377e5..45df2b1bb20c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -1,11 +1,12 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.scene.ui.composable.Shade
fun TransitionBuilder.goneToShadeTransition() {
spec = tween(durationMillis = 500)
- fade(Shade.rootElementKey)
+ translate(Shade.rootElementKey, Edge.Top, true)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
index 9a8a3e2048d1..e63bc4e458eb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
@@ -1,11 +1,12 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.scene.ui.composable.QuickSettings
fun TransitionBuilder.lockscreenToQuickSettingsTransition() {
spec = tween(durationMillis = 500)
- fade(QuickSettings.rootElementKey)
+ translate(QuickSettings.rootElementKey, Edge.Top, true)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index 6c7964b30989..21a10b1bc936 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -10,6 +10,5 @@ fun TransitionBuilder.shadeToQuickSettingsTransition() {
spec = tween(durationMillis = 500)
translate(Notifications.Elements.Notifications, Edge.Bottom)
- fade(Notifications.Elements.Notifications)
timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e539c955a3c6..e7a53e5baaf4 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -62,6 +62,7 @@ class DefaultClockController(
private val burmeseLineSpacing =
resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
+ protected var onSecondaryDisplay: Boolean = false
override val events: DefaultClockEvents
override val config = ClockConfig()
@@ -142,6 +143,11 @@ class DefaultClockController(
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
recomputePadding(targetRegion)
}
+
+ override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {
+ this@DefaultClockController.onSecondaryDisplay = onSecondaryDisplay
+ recomputePadding(null)
+ }
}
open fun recomputePadding(targetRegion: Rect?) {}
@@ -182,13 +188,19 @@ class DefaultClockController(
override fun recomputePadding(targetRegion: Rect?) {
// We center the view within the targetRegion instead of within the parent
// view by computing the difference and adding that to the padding.
- val parent = view.parent
- val yDiff =
- if (targetRegion != null && parent is View && parent.isLaidOut())
- targetRegion.centerY() - parent.height / 2f
- else 0f
val lp = view.getLayoutParams() as FrameLayout.LayoutParams
- lp.topMargin = (-0.5f * view.bottom + yDiff).toInt()
+ lp.topMargin =
+ if (onSecondaryDisplay) {
+ // On the secondary display we don't want any additional top/bottom margin.
+ 0
+ } else {
+ val parent = view.parent
+ val yDiff =
+ if (targetRegion != null && parent is View && parent.isLaidOut())
+ targetRegion.centerY() - parent.height / 2f
+ else 0f
+ (-0.5f * view.bottom + yDiff).toInt()
+ }
view.setLayoutParams(lp)
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index d962732ba884..527f80072222 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -177,6 +177,9 @@ interface ClockFaceEvents {
* targetRegion is relative to the parent view.
*/
fun onTargetRegionChanged(targetRegion: Rect?)
+
+ /** Called to notify the clock about its display. */
+ fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean)
}
/** Tick rates for clocks */
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index cf7d2c57923c..3d9645a3d983 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -58,9 +58,26 @@ public interface VolumeDialogController {
void userActivity();
void getState();
- boolean areCaptionsEnabled();
- void setCaptionsEnabled(boolean isEnabled);
-
+ /**
+ * Get Captions enabled state
+ *
+ * @param checkForSwitchState set true when we'd like to switch captions enabled state after
+ * getting the latest captions state.
+ */
+ void getCaptionsEnabledState(boolean checkForSwitchState);
+
+ /**
+ * Set Captions enabled state
+ *
+ * @param enabled the captions enabled state we'd like to update.
+ */
+ void setCaptionsEnabledState(boolean enabled);
+
+ /**
+ * Get Captions component state
+ *
+ * @param fromTooltip if it's triggered from tooltip.
+ */
void getCaptionsComponentState(boolean fromTooltip);
@ProvidesInterface(version = StreamState.VERSION)
@@ -192,7 +209,22 @@ public interface VolumeDialogController {
void onScreenOff();
void onShowSafetyWarning(int flags);
void onAccessibilityModeChanged(Boolean showA11yStream);
+
+ /**
+ * Callback function for captions component state changed event
+ *
+ * @param isComponentEnabled the lateset captions component state.
+ * @param fromTooltip if it's triggered from tooltip.
+ */
void onCaptionComponentStateChanged(Boolean isComponentEnabled, Boolean fromTooltip);
+
+ /**
+ * Callback function for captions enabled state changed event
+ *
+ * @param isEnabled the lateset captions enabled state.
+ * @param checkBeforeSwitch intend to switch captions enabled state after the callback.
+ */
+ void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch);
// requires version 2
void onShowCsdWarning(@AudioManager.CsdWarning int csdWarning, int durationMs);
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml
new file mode 100644
index 000000000000..593f507f3c88
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/presentation"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <com.android.keyguard.KeyguardStatusView
+ android:id="@+id/clock"
+ android:layout_width="410dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical">
+
+ <include
+ android:id="@+id/keyguard_clock_container"
+ layout="@layout/keyguard_clock_switch"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </com.android.keyguard.KeyguardStatusView>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 12f13e9a2138..3a15ae4f17ff 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -127,6 +127,8 @@ frame when animating QS <-> QQS transition
android:gravity="center_vertical"
android:paddingStart="@dimen/shade_header_system_icons_padding_start"
android:paddingEnd="@dimen/shade_header_system_icons_padding_end"
+ android:paddingTop="@dimen/shade_header_system_icons_padding_top"
+ android:paddingBottom="@dimen/shade_header_system_icons_padding_bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/privacy_container"
app:layout_constraintTop_toTopOf="@id/clock">
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index 9af46c5b739c..37964158a4aa 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -64,8 +64,7 @@
android:layout_height="wrap_content"
android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
style="@style/TextAppearance.Dialog.Body.Message"
- android:gravity="start"
- android:lineHeight="@dimen/screenrecord_warning_line_height"/>
+ android:gravity="start"/>
<!-- Buttons -->
<LinearLayout
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index d85e0122f2b4..915dcdb9755f 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -82,6 +82,8 @@
<!-- start padding is smaller to account for status icon margins coming from drawable itself -->
<dimen name="shade_header_system_icons_padding_start">3dp</dimen>
<dimen name="shade_header_system_icons_padding_end">4dp</dimen>
+ <dimen name="shade_header_system_icons_padding_top">2dp</dimen>
+ <dimen name="shade_header_system_icons_padding_bottom">2dp</dimen>
<!-- Lockscreen shade transition values -->
<dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 5c42e45c7c6b..eb9d0b346418 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -923,4 +923,7 @@
"$packageName" part that will be replaced by the code with the package name of the target app.
-->
<string name="config_appStoreAppLinkTemplate" translatable="false"></string>
+
+ <!-- Flag controlling whether visual query attention detection has been enabled. -->
+ <bool name="config_enableVisualQueryAttentionDetection">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8310b9548b66..2dc1b45af41f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -496,9 +496,10 @@
<dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
<dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
<dimen name="shade_header_system_icons_height">@dimen/large_screen_shade_header_min_height</dimen>
- <dimen name="shade_header_system_icons_height_large_screen">32dp</dimen>
<dimen name="shade_header_system_icons_padding_start">0dp</dimen>
<dimen name="shade_header_system_icons_padding_end">0dp</dimen>
+ <dimen name="shade_header_system_icons_padding_top">0dp</dimen>
+ <dimen name="shade_header_system_icons_padding_bottom">0dp</dimen>
<!-- The top margin of the panel that holds the list of notifications.
On phones it's always 0dp but it's overridden in Car UI
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 261b08d4356f..0d45422f7369 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -39,7 +39,7 @@
<bool name="flag_battery_shield_icon">false</bool>
<!-- Whether face auth will immediately stop when the display state is OFF -->
- <bool name="flag_stop_face_auth_on_display_off">false</bool>
+ <bool name="flag_stop_face_auth_on_display_off">true</bool>
<!-- Whether we want to stop pulsing while running the face scanning animation -->
<bool name="flag_stop_pulsing_face_scanning_animation">true</bool>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 2ec6180513bf..fe61c46e341d 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -56,7 +56,7 @@
<Constraint android:id="@+id/shade_header_system_icons">
<Layout
android:layout_width="wrap_content"
- android:layout_height="@dimen/shade_header_system_icons_height_large_screen"
+ android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/privacy_container"
app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index c844db7b770d..77f6d03a03fb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -28,8 +28,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public final class InteractionJankMonitorWrapper {
- private static final String TAG = "JankMonitorWrapper";
-
// Launcher journeys.
public static final int CUJ_APP_LAUNCH_FROM_RECENTS =
InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS;
@@ -37,6 +35,8 @@ public final class InteractionJankMonitorWrapper {
InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON;
public static final int CUJ_APP_CLOSE_TO_HOME =
InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
+ public static final int CUJ_APP_CLOSE_TO_HOME_FALLBACK =
+ InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
public static final int CUJ_APP_CLOSE_TO_PIP =
InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP;
public static final int CUJ_QUICK_SWITCH =
@@ -68,6 +68,7 @@ public final class InteractionJankMonitorWrapper {
CUJ_APP_LAUNCH_FROM_RECENTS,
CUJ_APP_LAUNCH_FROM_ICON,
CUJ_APP_CLOSE_TO_HOME,
+ CUJ_APP_CLOSE_TO_HOME_FALLBACK,
CUJ_APP_CLOSE_TO_PIP,
CUJ_QUICK_SWITCH,
CUJ_APP_LAUNCH_FROM_WIDGET,
diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
new file mode 100644
index 000000000000..899cad89a0be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.app.Presentation
+import android.content.Context
+import android.graphics.Color
+import android.os.Bundle
+import android.view.Display
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import com.android.keyguard.dagger.KeyguardStatusViewComponent
+import com.android.systemui.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** [Presentation] shown in connected displays while on keyguard. */
+class ConnectedDisplayKeyguardPresentation
+@AssistedInject
+constructor(
+ @Assisted display: Display,
+ context: Context,
+ private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
+) :
+ Presentation(
+ context,
+ display,
+ R.style.Theme_SystemUI_KeyguardPresentation,
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
+ ) {
+
+ private lateinit var keyguardStatusViewController: KeyguardStatusViewController
+ private lateinit var clock: KeyguardStatusView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(
+ LayoutInflater.from(context)
+ .inflate(R.layout.keyguard_clock_presentation, /* root= */ null)
+ )
+ val window = window ?: error("no window available.")
+
+ // Logic to make the lock screen fullscreen
+ window.decorView.systemUiVisibility =
+ (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
+ window.attributes.fitInsetsTypes = 0
+ window.isNavigationBarContrastEnforced = false
+ window.navigationBarColor = Color.TRANSPARENT
+
+ clock = findViewById(R.id.clock)
+ keyguardStatusViewController =
+ keyguardStatusViewComponentFactory.build(clock).keyguardStatusViewController.apply {
+ setDisplayedOnSecondaryDisplay()
+ init()
+ }
+ }
+
+ /** [ConnectedDisplayKeyguardPresentation] factory. */
+ @AssistedFactory
+ interface Factory {
+ /** Creates a new [Presentation] for the given [display]. */
+ fun create(
+ display: Display,
+ ): ConnectedDisplayKeyguardPresentation
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index b81e08183cdc..e3f9de11bf98 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -102,6 +102,12 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey
super.onViewAttached();
mView.setKeyDownListener(mKeyDownListener);
mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (shouldLockout(deadline)) {
+ handleAttemptLockout(deadline);
+ }
}
@Override
@@ -278,12 +284,6 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey
@Override
public void onResume(int reason) {
mResumed = true;
- // if the user is currently locked out, enforce it.
- long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
- KeyguardUpdateMonitor.getCurrentUser());
- if (shouldLockout(deadline)) {
- handleAttemptLockout(deadline);
- }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 30b8ed0f1750..b5898870539f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,10 +1,5 @@
package com.android.keyguard;
-import static android.view.View.ALPHA;
-import static android.view.View.SCALE_X;
-import static android.view.View.SCALE_Y;
-import static android.view.View.TRANSLATION_Y;
-
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_X_CLOCK_DESIGN;
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_DESIGN;
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_SIZE;
@@ -149,6 +144,13 @@ public class KeyguardClockSwitch extends RelativeLayout {
updateStatusArea(/* animate= */false);
}
+ /** Sets whether the large clock is being shown on a connected display. */
+ public void setLargeClockOnSecondaryDisplay(boolean onSecondaryDisplay) {
+ if (mClock != null) {
+ mClock.getLargeClock().getEvents().onSecondaryDisplayChanged(onSecondaryDisplay);
+ }
+ }
+
/**
* Enable or disable split shade specific positioning
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 3d48f3cc5359..dd39f1d6ed28 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -104,6 +104,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+ private boolean mShownOnSecondaryDisplay = false;
private boolean mOnlyClock = false;
private boolean mIsActiveDreamLockscreenHosted = false;
private FeatureFlags mFeatureFlags;
@@ -185,8 +186,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
/**
+ * When set, limits the information shown in an external display.
+ */
+ public void setShownOnSecondaryDisplay(boolean shownOnSecondaryDisplay) {
+ mShownOnSecondaryDisplay = shownOnSecondaryDisplay;
+ }
+
+ /**
* Mostly used for alternate displays, limit the information shown
+ *
+ * @deprecated use {@link KeyguardClockSwitchController#setShownOnSecondaryDisplay}
*/
+ @Deprecated
public void setOnlyClock(boolean onlyClock) {
mOnlyClock = onlyClock;
}
@@ -221,6 +232,15 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
}
+ private void hideSliceViewAndNotificationIconContainer() {
+ View ksv = mView.findViewById(R.id.keyguard_slice_view);
+ ksv.setVisibility(View.GONE);
+
+ View nic = mView.findViewById(
+ R.id.left_aligned_notification_icon_container);
+ nic.setVisibility(View.GONE);
+ }
+
@Override
protected void onViewAttached() {
mClockRegistry.registerClockChangeListener(mClockChangedListener);
@@ -234,13 +254,15 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mKeyguardDateWeatherViewInvisibility =
mView.getResources().getInteger(R.integer.keyguard_date_weather_view_invisibility);
- if (mOnlyClock) {
- View ksv = mView.findViewById(R.id.keyguard_slice_view);
- ksv.setVisibility(View.GONE);
+ if (mShownOnSecondaryDisplay) {
+ mView.setLargeClockOnSecondaryDisplay(true);
+ displayClock(LARGE, /* animate= */ false);
+ hideSliceViewAndNotificationIconContainer();
+ return;
+ }
- View nic = mView.findViewById(
- R.id.left_aligned_notification_icon_container);
- nic.setVisibility(View.GONE);
+ if (mOnlyClock) {
+ hideSliceViewAndNotificationIconContainer();
return;
}
updateAodIcons();
@@ -293,6 +315,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
setClock(null);
mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
+ mSecureSettings.unregisterContentObserver(mShowWeatherObserver);
mKeyguardUnlockAnimationController.removeKeyguardUnlockAnimationListener(
mKeyguardUnlockAnimationListener);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 9f21a31bd2c0..1c5a5758c2a4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -43,16 +43,19 @@ import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import dagger.Lazy;
+
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import dagger.Lazy;
@SysUISingleton
public class KeyguardDisplayManager {
@@ -64,6 +67,9 @@ public class KeyguardDisplayManager {
private final DisplayTracker mDisplayTracker;
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+ private final ConnectedDisplayKeyguardPresentation.Factory
+ mConnectedDisplayKeyguardPresentationFactory;
+ private final FeatureFlags mFeatureFlags;
private final Context mContext;
private boolean mShowing;
@@ -105,7 +111,10 @@ public class KeyguardDisplayManager {
@Main Executor mainExecutor,
@UiBackground Executor uiBgExecutor,
DeviceStateHelper deviceStateHelper,
- KeyguardStateController keyguardStateController) {
+ KeyguardStateController keyguardStateController,
+ ConnectedDisplayKeyguardPresentation.Factory
+ connectedDisplayKeyguardPresentationFactory,
+ FeatureFlags featureFlags) {
mContext = context;
mNavigationBarControllerLazy = navigationBarControllerLazy;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
@@ -115,6 +124,8 @@ public class KeyguardDisplayManager {
mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
mDeviceStateHelper = deviceStateHelper;
mKeyguardStateController = keyguardStateController;
+ mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory;
+ mFeatureFlags = featureFlags;
}
private boolean isKeyguardShowable(Display display) {
@@ -185,8 +196,12 @@ public class KeyguardDisplayManager {
return false;
}
- KeyguardPresentation createPresentation(Display display) {
- return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
+ Presentation createPresentation(Display display) {
+ if (mFeatureFlags.isEnabled(Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION)) {
+ return mConnectedDisplayKeyguardPresentationFactory.create(display);
+ } else {
+ return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 20e465693108..42dbc487d774 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.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
+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.bouncer.domain.interactor.BouncerMessageInteractor;
-import com.android.systemui.bouncer.ui.BouncerMessageView;
-import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder;
import com.android.systemui.log.BouncerLogger;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.ViewController;
@@ -95,6 +95,12 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
@CallSuper
protected void onViewAttached() {
updateMessageAreaVisibility();
+ if (TextUtils.isEmpty(mMessageAreaController.getMessage())
+ && getInitialMessageResId() != 0) {
+ mMessageAreaController.setMessage(
+ mView.getResources().getString(getInitialMessageResId()),
+ /* animate= */ false);
+ }
}
private void updateMessageAreaVisibility() {
@@ -147,12 +153,6 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
}
public void startAppearAnimation() {
- if (TextUtils.isEmpty(mMessageAreaController.getMessage())
- && getInitialMessageResId() != 0) {
- mMessageAreaController.setMessage(
- mView.getResources().getString(getInitialMessageResId()),
- /* animate= */ false);
- }
mView.startAppearAnimation();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 49f788ce8f75..a30b4479fe95 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -238,6 +238,12 @@ public class KeyguardPatternViewController
}
mView.onDevicePostureChanged(mPostureController.getDevicePosture());
mPostureController.addCallback(mPostureCallback);
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ }
}
@Override
@@ -268,12 +274,6 @@ public class KeyguardPatternViewController
@Override
public void onResume(int reason) {
super.onResume(reason);
- // if the user is currently locked out, enforce it.
- long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
- KeyguardUpdateMonitor.getCurrentUser());
- if (deadline != 0) {
- handleAttemptLockout(deadline);
- }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index b3e08c0bc69f..574a0591bd51 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -79,6 +79,10 @@ public class KeyguardPinViewController
mPasswordEntry.setUserActivityListener(this::onUserInput);
mView.onDevicePostureChanged(mPostureController.getDevicePosture());
mPostureController.addCallback(mPostureCallback);
+ if (mFeatureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)) {
+ mPasswordEntry.setUsePinShapes(true);
+ updateAutoConfirmationState();
+ }
}
protected void onUserInput() {
@@ -100,10 +104,6 @@ public class KeyguardPinViewController
@Override
public void startAppearAnimation() {
- if (mFeatureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)) {
- mPasswordEntry.setUsePinShapes(true);
- updateAutoConfirmationState();
- }
super.startAppearAnimation();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index d9a1dc66928c..04692c48a123 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -84,6 +84,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.domain.interactor.UserInteractor;
@@ -146,8 +147,19 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private int mLastOrientation;
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
+ private int mCurrentUser = UserHandle.USER_NULL;
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
- () -> showPrimarySecurityScreen(false);
+ new UserSwitcherController.UserSwitchCallback() {
+ @Override
+ public void onUserSwitched() {
+ if (mCurrentUser == KeyguardUpdateMonitor.getCurrentUser()) {
+ return;
+ }
+ mCurrentUser = KeyguardUpdateMonitor.getCurrentUser();
+ showPrimarySecurityScreen(false);
+ reinflateViewFlipper((l) -> {});
+ }
+ };
@VisibleForTesting
final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
@@ -343,7 +355,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
@Override
public void onThemeChanged() {
reloadColors();
- reset();
}
@Override
@@ -401,6 +412,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final UserInteractor mUserInteractor;
private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
private final Provider<JavaAdapter> mJavaAdapter;
+ private final DeviceProvisionedController mDeviceProvisionedController;
@Nullable private Job mSceneTransitionCollectionJob;
@Inject
@@ -429,6 +441,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
BouncerMessageInteractor bouncerMessageInteractor,
Provider<JavaAdapter> javaAdapter,
UserInteractor userInteractor,
+ DeviceProvisionedController deviceProvisionedController,
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
KeyguardTransitionInteractor keyguardTransitionInteractor,
Provider<AuthenticationInteractor> authenticationInteractor
@@ -463,6 +476,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mAuthenticationInteractor = authenticationInteractor;
mJavaAdapter = javaAdapter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mDeviceProvisionedController = deviceProvisionedController;
}
@Override
@@ -847,9 +861,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
// Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
- KeyguardUpdateMonitor.getCurrentUser());
- if (securityMode == SecurityMode.None || isLockscreenDisabled) {
- finish = isLockscreenDisabled;
+ KeyguardUpdateMonitor.getCurrentUser())
+ || !mDeviceProvisionedController.isUserSetup(targetUserId);
+
+ if (securityMode == SecurityMode.None && isLockscreenDisabled) {
+ finish = true;
eventSubtype = BOUNCER_DISMISS_SIM;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
} else {
@@ -1164,7 +1180,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
private void reloadColors() {
- reinflateViewFlipper(controller -> mView.reloadColors());
+ mView.reloadColors();
}
/** Handles density or font scale changes. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 757022dd153b..c314586e4a21 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -187,6 +187,11 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
mConfigurationController.removeCallback(mConfigurationListener);
}
+ /** Sets the StatusView as shown on an external display. */
+ public void setDisplayedOnSecondaryDisplay() {
+ mKeyguardClockSwitchController.setShownOnSecondaryDisplay(true);
+ }
+
/**
* Called in notificationPanelViewController to avoid leak
*/
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 2b83e6b05bbc..590056f2f8c2 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -23,6 +23,8 @@ import android.service.voice.VoiceInteractionSession;
import android.util.Log;
import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -39,10 +41,13 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.settings.SecureSettings;
-import javax.inject.Inject;
-
import dagger.Lazy;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
/**
* Class to manage everything related to assist in SystemUI.
*/
@@ -78,6 +83,18 @@ public class AssistManager {
void hide();
}
+ /**
+ * An interface for a listener that receives notification that visual query attention has
+ * either been gained or lost.
+ */
+ public interface VisualQueryAttentionListener {
+ /** Called when visual query attention has been gained. */
+ void onAttentionGained();
+
+ /** Called when visual query attention has been lost. */
+ void onAttentionLost();
+ }
+
private static final String TAG = "AssistManager";
// Note that VERBOSE logging may leak PII (e.g. transcription contents).
@@ -127,6 +144,23 @@ public class AssistManager {
private final SecureSettings mSecureSettings;
private final DeviceProvisionedController mDeviceProvisionedController;
+
+ private final List<VisualQueryAttentionListener> mVisualQueryAttentionListeners =
+ new ArrayList<>();
+
+ private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener =
+ new IVisualQueryDetectionAttentionListener.Stub() {
+ @Override
+ public void onAttentionGained() {
+ mVisualQueryAttentionListeners.forEach(VisualQueryAttentionListener::onAttentionGained);
+ }
+
+ @Override
+ public void onAttentionLost() {
+ mVisualQueryAttentionListeners.forEach(VisualQueryAttentionListener::onAttentionLost);
+ }
+ };
+
private final CommandQueue mCommandQueue;
protected final AssistUtils mAssistUtils;
@@ -157,6 +191,7 @@ public class AssistManager {
mSecureSettings = secureSettings;
registerVoiceInteractionSessionListener();
+ registerVisualQueryRecognitionStatusListener();
mUiController = defaultUiController;
@@ -266,6 +301,24 @@ public class AssistManager {
mAssistUtils.hideCurrentSession();
}
+ /**
+ * Add the given {@link VisualQueryAttentionListener} to the list of listeners awaiting
+ * notification of gaining/losing visual query attention.
+ */
+ public void addVisualQueryAttentionListener(VisualQueryAttentionListener listener) {
+ if (!mVisualQueryAttentionListeners.contains(listener)) {
+ mVisualQueryAttentionListeners.add(listener);
+ }
+ }
+
+ /**
+ * Remove the given {@link VisualQueryAttentionListener} from the list of listeners awaiting
+ * notification of gaining/losing visual query attention.
+ */
+ public void removeVisualQueryAttentionListener(VisualQueryAttentionListener listener) {
+ mVisualQueryAttentionListeners.remove(listener);
+ }
+
private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
boolean isService) {
if (isService) {
@@ -326,6 +379,27 @@ public class AssistManager {
null, null);
}
+ private void registerVisualQueryRecognitionStatusListener() {
+ if (!mContext.getResources()
+ .getBoolean(R.bool.config_enableVisualQueryAttentionDetection)) {
+ return;
+ }
+
+ mAssistUtils.subscribeVisualQueryRecognitionStatus(
+ new IVisualQueryRecognitionStatusListener.Stub() {
+ @Override
+ public void onStartPerceiving() {
+ mAssistUtils.enableVisualQueryDetection(
+ mVisualQueryDetectionAttentionListener);
+ }
+
+ @Override
+ public void onStopPerceiving() {
+ mAssistUtils.disableVisualQueryDetection();
+ }
+ });
+ }
+
public void launchVoiceAssistFromKeyguard() {
mAssistUtils.launchVoiceAssistFromKeyguard();
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt
index 1817ea9024fc..64bf688b3c5d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt
@@ -36,7 +36,6 @@ import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FOR_MAINL
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R.string.bouncer_face_not_recognized
import com.android.systemui.R.string.keyguard_enter_password
import com.android.systemui.R.string.keyguard_enter_pattern
@@ -80,13 +79,14 @@ import com.android.systemui.R.string.kg_wrong_pin_try_again
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.keyguard.data.repository.BiometricSettingsRepository
import javax.inject.Inject
@SysUISingleton
class BouncerMessageFactory
@Inject
constructor(
- private val updateMonitor: KeyguardUpdateMonitor,
+ private val biometricSettingsRepository: BiometricSettingsRepository,
private val securityModel: KeyguardSecurityModel,
) {
@@ -99,7 +99,7 @@ constructor(
getBouncerMessage(
reason,
securityModel.getSecurityMode(userId),
- updateMonitor.isUnlockingWithFingerprintAllowed
+ biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value
)
return pair?.let {
BouncerMessageModel(
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
index 6fb0d4cc5e13..97c1bdb180a1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
@@ -123,20 +123,11 @@ constructor(
fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
) : BouncerMessageRepository {
- private val isFaceEnrolledAndEnabled =
- and(
- biometricSettingsRepository.isFaceAuthenticationEnabled,
- biometricSettingsRepository.isFaceEnrolled
- )
-
- private val isFingerprintEnrolledAndEnabled =
- and(
- biometricSettingsRepository.isFingerprintEnabledByDevicePolicy,
- biometricSettingsRepository.isFingerprintEnrolled
- )
-
private val isAnyBiometricsEnabledAndEnrolled =
- or(isFaceEnrolledAndEnabled, isFingerprintEnrolledAndEnabled)
+ or(
+ biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
+ biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
+ )
private val wasRebootedForMainlineUpdate
get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
@@ -335,8 +326,5 @@ constructor(
}
}
-private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
- flow.combine(anotherFlow) { a, b -> a && b }
-
private fun or(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
flow.combine(anotherFlow) { a, b -> a || b }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 98ae54b1340e..9a7fec1daae0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -83,9 +83,7 @@ constructor(
fun canShowAlternateBouncerForFingerprint(): Boolean {
return bouncerRepository.alternateBouncerUIAvailable.value &&
- biometricSettingsRepository.isFingerprintEnrolled.value &&
- biometricSettingsRepository.isStrongBiometricAllowed.value &&
- biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
+ biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value &&
!keyguardUpdateMonitor.isFingerprintLockedOut &&
!keyguardStateController.isUnlocked &&
!statusBarStateController.isDozing
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
index 566a74ae3e07..d58fab45093d 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
@@ -27,7 +27,8 @@ import android.text.TextUtils;
import com.android.systemui.R;
class IntentCreator {
- private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
+ private static final String EXTRA_EDIT_SOURCE = "edit_source";
+ private static final String EDIT_SOURCE_CLIPBOARD = "clipboard";
private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
static Intent getTextEditorIntent(Context context) {
@@ -74,7 +75,7 @@ class IntentCreator {
editIntent.setDataAndType(uri, "image/*");
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- editIntent.putExtra(EXTRA_EDIT_SOURCE_CLIPBOARD, true);
+ editIntent.putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD);
return editIntent;
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
index 8029ba844850..534832c0992d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
@@ -54,9 +54,10 @@ interface ControlActionCoordinator {
/**
* When a ToggleRange control is interacting with, a drag event is sent.
*
+ * @param cvh [ControlViewHolder] for the control
* @param isEdge did the drag event reach a control edge
*/
- fun drag(isEdge: Boolean)
+ fun drag(cvh: ControlViewHolder, isEdge: Boolean)
/**
* Send a request to update the value of a device using the [FloatAction].
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index a7e9efd8ab03..00d95c02c172 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -37,6 +37,8 @@ import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -57,6 +59,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
private val controlsMetricsLogger: ControlsMetricsLogger,
private val vibrator: VibratorHelper,
private val controlsSettingsRepository: ControlsSettingsRepository,
+ private val featureFlags: FeatureFlags,
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private var pendingAction: Action? = null
@@ -119,11 +122,17 @@ class ControlActionCoordinatorImpl @Inject constructor(
)
}
- override fun drag(isEdge: Boolean) {
- if (isEdge) {
- vibrate(Vibrations.rangeEdgeEffect)
+ override fun drag(cvh: ControlViewHolder, isEdge: Boolean) {
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ val constant =
+ if (isEdge)
+ HapticFeedbackConstants.SEGMENT_TICK
+ else
+ HapticFeedbackConstants.SEGMENT_FREQUENT_TICK
+ vibrator.performHapticFeedback(cvh.layout, constant)
} else {
- vibrate(Vibrations.rangeMiddleEffect)
+ val effect = if (isEdge) Vibrations.rangeEdgeEffect else Vibrations.rangeMiddleEffect
+ vibrate(effect)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index b2c95a63ad72..0d570d2dcc73 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -235,7 +235,7 @@ class ToggleRangeBehavior : Behavior {
if (isDragging) {
val isEdge = newLevel == MIN_LEVEL || newLevel == MAX_LEVEL
if (clipLayer.level != newLevel) {
- cvh.controlActionCoordinator.drag(isEdge)
+ cvh.controlActionCoordinator.drag(cvh, isEdge)
clipLayer.level = newLevel
}
} else if (newLevel != clipLayer.level) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index d9665c5b5047..484be9ce1975 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -53,6 +53,7 @@ import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.KeyguardLiftController
import com.android.systemui.statusbar.phone.LockscreenWallpaper
import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
@@ -331,4 +332,11 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(ScrimController::class)
abstract fun bindScrimController(impl: ScrimController): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(StatusBarHeadsUpChangeListener::class)
+ abstract fun bindStatusBarHeadsUpChangeListener(
+ impl: StatusBarHeadsUpChangeListener
+ ): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
index c889ac214cda..4dd97d557b30 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
@@ -16,10 +16,9 @@
package com.android.systemui.dreams.conditions;
-import com.android.internal.app.AssistUtils;
-import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener;
import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.shared.condition.Condition;
import javax.inject.Inject;
@@ -30,12 +29,10 @@ import kotlinx.coroutines.CoroutineScope;
* {@link AssistantAttentionCondition} provides a signal when assistant has the user's attention.
*/
public class AssistantAttentionCondition extends Condition {
- private final DreamOverlayStateController mDreamOverlayStateController;
- private final AssistUtils mAssistUtils;
- private boolean mEnabled;
+ private final AssistManager mAssistManager;
- private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener =
- new IVisualQueryDetectionAttentionListener.Stub() {
+ private final VisualQueryAttentionListener mVisualQueryAttentionListener =
+ new VisualQueryAttentionListener() {
@Override
public void onAttentionGained() {
updateCondition(true);
@@ -47,59 +44,26 @@ public class AssistantAttentionCondition extends Condition {
}
};
- private final DreamOverlayStateController.Callback mCallback =
- new DreamOverlayStateController.Callback() {
- @Override
- public void onStateChanged() {
- if (mDreamOverlayStateController.isDreamOverlayStatusBarVisible()) {
- enableVisualQueryDetection();
- } else {
- disableVisualQueryDetection();
- }
- }
- };
-
@Inject
public AssistantAttentionCondition(
@Application CoroutineScope scope,
- DreamOverlayStateController dreamOverlayStateController,
- AssistUtils assistUtils) {
+ AssistManager assistManager) {
super(scope);
- mDreamOverlayStateController = dreamOverlayStateController;
- mAssistUtils = assistUtils;
+ mAssistManager = assistManager;
}
@Override
protected void start() {
- mDreamOverlayStateController.addCallback(mCallback);
+ mAssistManager.addVisualQueryAttentionListener(mVisualQueryAttentionListener);
}
@Override
protected void stop() {
- disableVisualQueryDetection();
- mDreamOverlayStateController.removeCallback(mCallback);
+ mAssistManager.removeVisualQueryAttentionListener(mVisualQueryAttentionListener);
}
@Override
protected int getStartStrategy() {
return START_EAGERLY;
}
-
- private void enableVisualQueryDetection() {
- if (mEnabled) {
- return;
- }
- mEnabled = true;
- mAssistUtils.enableVisualQueryDetection(mVisualQueryDetectionAttentionListener);
- }
-
- private void disableVisualQueryDetection() {
- if (!mEnabled) {
- return;
- }
- mEnabled = false;
- mAssistUtils.disableVisualQueryDetection();
- // Make sure the condition is set to false as well.
- updateCondition(false);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4a22a6732ce7..f3900ac1c3a8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -115,7 +115,7 @@ object Flags {
// TODO(b/292213543): Tracking Bug
@JvmField
val NOTIFICATION_GROUP_EXPANSION_CHANGE =
- unreleasedFlag("notification_group_expansion_change", teamfood = false)
+ unreleasedFlag("notification_group_expansion_change", teamfood = true)
// 200 - keyguard/lockscreen
// ** Flag retired **
@@ -195,7 +195,7 @@ object Flags {
// TODO(b/294110497): Tracking Bug
@JvmField
val ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS =
- unreleasedFlag("enable_wallet_contextual_loyalty_cards", teamfood = true)
+ releasedFlag("enable_wallet_contextual_loyalty_cards")
// TODO(b/242908637): Tracking Bug
@JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag("wallpaper_fullscreen_preview")
@@ -386,8 +386,7 @@ object Flags {
@JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag("new_bluetooth_repository")
// TODO(b/292533677): Tracking Bug
- val WIFI_TRACKER_LIB_FOR_WIFI_ICON =
- unreleasedFlag("wifi_tracker_lib_for_wifi_icon", teamfood = true)
+ val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon")
// TODO(b/293863612): Tracking Bug
@JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
@@ -545,12 +544,6 @@ object Flags {
val ENABLE_PIP_SIZE_LARGE_SCREEN =
sysPropBooleanFlag("persist.wm.debug.enable_pip_size_large_screen", default = true)
- // TODO(b/265998256): Tracking bug
- @Keep
- @JvmField
- val ENABLE_PIP_APP_ICON_OVERLAY =
- sysPropBooleanFlag("persist.wm.debug.enable_pip_app_icon_overlay", default = true)
-
// TODO(b/293252410) : Tracking Bug
@JvmField
@@ -776,6 +769,10 @@ object Flags {
@JvmField
val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag("oneway_haptics_api_migration")
+ /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
+ @JvmField
+ val ENABLE_CLOCK_KEYGUARD_PRESENTATION = unreleasedFlag("enable_clock_keyguard_presentation")
+
/** Enable the Compose implementation of the PeopleSpaceActivity. */
@JvmField
val COMPOSE_PEOPLE_SPACE = unreleasedFlag("compose_people_space")
@@ -787,4 +784,8 @@ object Flags {
/** Enable the share wifi button in Quick Settings internet dialog. */
@JvmField
val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button")
+
+ /** Enable haptic slider component in the brightness slider */
+ @JvmField
+ val HAPTIC_BRIGHTNESS_SLIDER = unreleasedFlag("haptic_brightness_slider")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
new file mode 100644
index 000000000000..3f2f67dbba37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import javax.inject.Inject
+
+/**
+ * Sends key events to the appropriate interactors and then acts upon key events that haven't
+ * already been handled but should be handled by SystemUI.
+ */
+@SysUISingleton
+class KeyEventInteractor
+@Inject
+constructor(
+ private val backActionInteractor: BackActionInteractor,
+ private val keyguardKeyEventInteractor: KeyguardKeyEventInteractor,
+) {
+ fun dispatchKeyEvent(event: KeyEvent): Boolean {
+ if (keyguardKeyEventInteractor.dispatchKeyEvent(event)) {
+ return true
+ }
+
+ when (event.keyCode) {
+ KeyEvent.KEYCODE_BACK -> {
+ if (event.handleAction()) {
+ backActionInteractor.onBackRequested()
+ }
+ return true
+ }
+ }
+ return false
+ }
+
+ fun interceptMediaKey(event: KeyEvent): Boolean {
+ return keyguardKeyEventInteractor.interceptMediaKey(event)
+ }
+
+ fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+ return keyguardKeyEventInteractor.dispatchKeyEventPreIme(event)
+ }
+
+ companion object {
+ // Most actions shouldn't be handled on the down event and instead handled on subsequent
+ // key events like ACTION_UP.
+ fun KeyEvent.handleAction(): Boolean {
+ return action != KeyEvent.ACTION_DOWN
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index ff3e77c46f1c..682e841c3521 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -28,6 +28,9 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -68,34 +71,32 @@ import kotlinx.coroutines.flow.transformLatest
* upstream changes.
*/
interface BiometricSettingsRepository {
- /** Whether any fingerprints are enrolled for the current user. */
- val isFingerprintEnrolled: StateFlow<Boolean>
-
- /** Whether face authentication is enrolled for the current user. */
- val isFaceEnrolled: Flow<Boolean>
-
/**
- * Whether face authentication is enabled/disabled based on system settings like device policy,
- * biometrics setting.
+ * If the current user can enter the device using fingerprint. This is true if user has enrolled
+ * fingerprints and fingerprint auth is not disabled through settings/device policy
*/
- val isFaceAuthenticationEnabled: Flow<Boolean>
+ val isFingerprintEnrolledAndEnabled: StateFlow<Boolean>
/**
- * Whether the current user is allowed to use a strong biometric for device entry based on
- * Android Security policies. If false, the user may be able to use primary authentication for
- * device entry.
+ * If the current user can enter the device using fingerprint, right now.
+ *
+ * This returns true if there are no strong auth flags that restrict the user from using
+ * fingerprint and [isFingerprintEnrolledAndEnabled] is true
*/
- val isStrongBiometricAllowed: StateFlow<Boolean>
+ val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean>
/**
- * Whether the current user is allowed to use a convenience biometric for device entry based on
- * Android Security policies. If false, the user may be able to use strong biometric or primary
- * authentication for device entry.
+ * If the current user can use face auth to enter the device. This is true when the user has
+ * face auth enrolled, and is enabled in settings/device policy.
*/
- val isNonStrongBiometricAllowed: StateFlow<Boolean>
+ val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
- /** Whether fingerprint feature is enabled for the current user by the DevicePolicy */
- val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
+ /**
+ * If the current user can use face auth to enter the device right now. This is true when
+ * [isFaceAuthEnrolledAndEnabled] is true and strong auth settings allow face auth to run and
+ * face auth is supported by the current device posture.
+ */
+ val isFaceAuthCurrentlyAllowed: Flow<Boolean>
/**
* Whether face authentication is supported for the current device posture. Face auth can be
@@ -130,6 +131,8 @@ constructor(
@Background backgroundDispatcher: CoroutineDispatcher,
biometricManager: BiometricManager?,
devicePostureRepository: DevicePostureRepository,
+ facePropertyRepository: FacePropertyRepository,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
dumpManager: DumpManager,
) : BiometricSettingsRepository, Dumpable {
@@ -165,7 +168,9 @@ constructor(
}
override fun dump(pw: PrintWriter, args: Array<String?>) {
- pw.println("isFingerprintEnrolled=${isFingerprintEnrolled.value}")
+ pw.println("isFingerprintEnrolledAndEnabled=${isFingerprintEnrolledAndEnabled.value}")
+ pw.println("isFingerprintAuthCurrentlyAllowed=${isFingerprintAuthCurrentlyAllowed.value}")
+ pw.println("isNonStrongBiometricAllowed=${isNonStrongBiometricAllowed.value}")
pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}")
}
@@ -180,7 +185,7 @@ constructor(
user = UserHandle.ALL
)
- override val isFingerprintEnrolled: StateFlow<Boolean> =
+ private val isFingerprintEnrolled: Flow<Boolean> =
selectedUserId
.flatMapLatest { currentUserId ->
conflatedCallbackFlow {
@@ -211,7 +216,7 @@ constructor(
authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id)
)
- override val isFaceEnrolled: Flow<Boolean> =
+ private val isFaceEnrolled: Flow<Boolean> =
selectedUserId.flatMapLatest { selectedUserId: Int ->
conflatedCallbackFlow {
val callback =
@@ -245,14 +250,6 @@ constructor(
isFaceEnabledByBiometricsManager.map { biometricsEnabledForUser[userInfo.id] ?: false }
}
- override val isFaceAuthenticationEnabled: Flow<Boolean>
- get() =
- combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
- biometricsManagerSetting,
- devicePolicySetting ->
- biometricsManagerSetting && devicePolicySetting
- }
-
private val isFaceEnabledByDevicePolicy: Flow<Boolean> =
combine(selectedUserId, devicePolicyChangedForAllUsers) { userId, _ ->
devicePolicyManager.isFaceDisabled(userId)
@@ -263,6 +260,13 @@ constructor(
.flowOn(backgroundDispatcher)
.distinctUntilChanged()
+ private val isFaceAuthenticationEnabled: Flow<Boolean> =
+ combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
+ biometricsManagerSetting,
+ devicePolicySetting ->
+ biometricsManagerSetting && devicePolicySetting
+ }
+
private val isFaceEnabledByBiometricsManager: Flow<Pair<Int, Boolean>> =
conflatedCallbackFlow {
val callback =
@@ -283,7 +287,7 @@ constructor(
// being registered.
.stateIn(scope, SharingStarted.Eagerly, Pair(0, false))
- override val isStrongBiometricAllowed: StateFlow<Boolean> =
+ private val isStrongBiometricAllowed: StateFlow<Boolean> =
strongAuthTracker.isStrongBiometricAllowed.stateIn(
scope,
SharingStarted.Eagerly,
@@ -293,7 +297,7 @@ constructor(
)
)
- override val isNonStrongBiometricAllowed: StateFlow<Boolean> =
+ private val isNonStrongBiometricAllowed: StateFlow<Boolean> =
strongAuthTracker.isNonStrongBiometricAllowed.stateIn(
scope,
SharingStarted.Eagerly,
@@ -303,7 +307,19 @@ constructor(
)
)
- override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
+ private val isFingerprintBiometricAllowed: Flow<Boolean> =
+ fingerprintPropertyRepository.strength.flatMapLatest {
+ if (it == SensorStrength.STRONG) isStrongBiometricAllowed
+ else isNonStrongBiometricAllowed
+ }
+
+ private val isFaceBiometricsAllowed: Flow<Boolean> =
+ facePropertyRepository.sensorInfo.flatMapLatest {
+ if (it?.strength == SensorStrength.STRONG) isStrongBiometricAllowed
+ else isNonStrongBiometricAllowed
+ }
+
+ private val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
selectedUserId
.flatMapLatest { userId ->
devicePolicyChangedForAllUsers
@@ -319,6 +335,25 @@ constructor(
userRepository.getSelectedUserInfo().id
)
)
+
+ override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
+ isFingerprintEnrolled
+ .and(isFingerprintEnabledByDevicePolicy)
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ override val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean> =
+ isFingerprintEnrolledAndEnabled
+ .and(isFingerprintBiometricAllowed)
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+ get() = isFaceAuthenticationEnabled.and(isFaceEnrolled)
+
+ override val isFaceAuthCurrentlyAllowed: Flow<Boolean>
+ get() =
+ isFaceAuthEnrolledAndEnabled
+ .and(isFaceBiometricsAllowed)
+ .and(isFaceAuthSupportedInCurrentPosture)
}
@OptIn(ExperimentalCoroutinesApi::class)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 689414711388..93eb103d74e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -26,8 +26,6 @@ import com.android.internal.logging.UiEventLogger
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.Dumpable
import com.android.systemui.R
-import com.android.systemui.biometrics.data.repository.FacePropertyRepository
-import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -72,7 +70,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -162,7 +159,6 @@ constructor(
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val featureFlags: FeatureFlags,
- facePropertyRepository: FacePropertyRepository,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
private var authCancellationSignal: CancellationSignal? = null
@@ -182,13 +178,6 @@ constructor(
override val detectionStatus: Flow<FaceDetectionStatus>
get() = _detectionStatus.filterNotNull()
- private val isFaceBiometricsAllowed: Flow<Boolean> =
- facePropertyRepository.sensorInfo.flatMapLatest {
- if (it?.strength == SensorStrength.STRONG)
- biometricSettingsRepository.isStrongBiometricAllowed
- else biometricSettingsRepository.isNonStrongBiometricAllowed
- }
-
private val _isLockedOut = MutableStateFlow(false)
override val isLockedOut: StateFlow<Boolean> = _isLockedOut
@@ -313,8 +302,10 @@ constructor(
canFaceAuthOrDetectRun(faceDetectLog),
logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
logAndObserve(
- isFaceBiometricsAllowed.isFalse().or(trustRepository.isCurrentUserTrusted),
- "biometricIsNotAllowedOrCurrentUserIsTrusted",
+ biometricSettingsRepository.isFaceAuthCurrentlyAllowed
+ .isFalse()
+ .or(trustRepository.isCurrentUserTrusted),
+ "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted",
faceDetectLog
),
// We don't want to run face detect if fingerprint can be used to unlock the device
@@ -346,13 +337,8 @@ constructor(
private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
return listOf(
logAndObserve(
- biometricSettingsRepository.isFaceEnrolled,
- "isFaceEnrolled",
- tableLogBuffer
- ),
- logAndObserve(
- biometricSettingsRepository.isFaceAuthenticationEnabled,
- "isFaceAuthenticationEnabled",
+ biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
+ "isFaceAuthEnrolledAndEnabled",
tableLogBuffer
),
logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer),
@@ -406,7 +392,11 @@ constructor(
"currentUserIsNotTrusted",
faceAuthLog
),
- logAndObserve(isFaceBiometricsAllowed, "isFaceBiometricsAllowed", faceAuthLog),
+ logAndObserve(
+ biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
+ "isFaceAuthCurrentlyAllowed",
+ faceAuthLog
+ ),
logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
)
.reduce(::and)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
new file mode 100644
index 000000000000..635961b0ea01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.Context
+import android.media.AudioManager
+import android.view.KeyEvent
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor.Companion.handleAction
+import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import javax.inject.Inject
+
+/** Handles key events arriving when the keyguard is showing or device is dozing. */
+@SysUISingleton
+class KeyguardKeyEventInteractor
+@Inject
+constructor(
+ private val context: Context,
+ private val statusBarStateController: StatusBarStateController,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val shadeController: ShadeController,
+ private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper,
+ private val backActionInteractor: BackActionInteractor,
+) {
+
+ fun dispatchKeyEvent(event: KeyEvent): Boolean {
+ if (statusBarStateController.isDozing) {
+ when (event.keyCode) {
+ KeyEvent.KEYCODE_VOLUME_DOWN,
+ KeyEvent.KEYCODE_VOLUME_UP -> return dispatchVolumeKeyEvent(event)
+ }
+ }
+
+ if (event.handleAction()) {
+ when (event.keyCode) {
+ KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent()
+ KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent()
+ }
+ }
+ return false
+ }
+
+ /**
+ * While IME is active and a BACK event is detected, check with {@link
+ * StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event should be
+ * handled before routing to IME, in order to prevent the user from having to hit back twice to
+ * exit bouncer.
+ */
+ fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+ when (event.keyCode) {
+ KeyEvent.KEYCODE_BACK ->
+ if (
+ statusBarStateController.state == StatusBarState.KEYGUARD &&
+ statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()
+ ) {
+ return backActionInteractor.onBackRequested()
+ }
+ }
+ return false
+ }
+
+ fun interceptMediaKey(event: KeyEvent): Boolean {
+ return statusBarStateController.state == StatusBarState.KEYGUARD &&
+ statusBarKeyguardViewManager.interceptMediaKey(event)
+ }
+
+ private fun dispatchMenuKeyEvent(): Boolean {
+ val shouldUnlockOnMenuPressed =
+ isDeviceInteractive() &&
+ (statusBarStateController.state != StatusBarState.SHADE) &&
+ statusBarKeyguardViewManager.shouldDismissOnMenuPressed()
+ if (shouldUnlockOnMenuPressed) {
+ shadeController.animateCollapseShadeForced()
+ return true
+ }
+ return false
+ }
+
+ private fun dispatchSpaceEvent(): Boolean {
+ if (isDeviceInteractive() && statusBarStateController.state != StatusBarState.SHADE) {
+ shadeController.animateCollapseShadeForced()
+ return true
+ }
+ return false
+ }
+
+ private fun dispatchVolumeKeyEvent(event: KeyEvent): Boolean {
+ mediaSessionLegacyHelperWrapper
+ .getHelper(context)
+ .sendVolumeKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE, true)
+ return true
+ }
+
+ private fun isDeviceInteractive(): Boolean {
+ return keyguardInteractor.wakefulnessModel.value.isDeviceInteractive()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 23b80b05a785..ed4dd6a15c18 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -19,10 +19,10 @@ package com.android.systemui.keyguard.ui.binder
import android.os.Trace
import android.util.Log
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.launch
@@ -31,19 +31,19 @@ class KeyguardBlueprintViewBinder {
companion object {
private const val TAG = "KeyguardBlueprintViewBinder"
- fun bind(keyguardRootView: KeyguardRootView, viewModel: KeyguardBlueprintViewModel) {
- keyguardRootView.repeatWhenAttached {
+ fun bind(constraintLayout: ConstraintLayout, viewModel: KeyguardBlueprintViewModel) {
+ constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.blueprint.collect { blueprint ->
Trace.beginSection("KeyguardBlueprintController#applyBlueprint")
Log.d(TAG, "applying blueprint: $blueprint")
ConstraintSet().apply {
- clone(keyguardRootView)
+ clone(constraintLayout)
val emptyLayout = ConstraintSet.Layout()
knownIds.forEach { getConstraint(it).layout.copyFrom(emptyLayout) }
blueprint?.apply(this)
- applyTo(keyguardRootView)
+ applyTo(constraintLayout)
}
Trace.endSection()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt
new file mode 100644
index 000000000000..99243696a4e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.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.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSessionLegacyHelper
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Injectable wrapper around `MediaSessionLegacyHelper` functions */
+@SysUISingleton
+class MediaSessionLegacyHelperWrapper @Inject constructor() {
+ fun getHelper(context: Context): MediaSessionLegacyHelper {
+ return MediaSessionLegacyHelper.getHelper(context)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index e134f7c10b9b..ae0ab8423a99 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -25,6 +25,7 @@ import static android.app.StatusBarManager.WindowType;
import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
import static android.view.InsetsSource.FLAG_SUPPRESS_SCRIM;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -1714,10 +1715,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private InsetsFrameProvider[] getInsetsFrameProvider(int insetsHeight, Context userContext) {
final InsetsFrameProvider navBarProvider =
- new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars())
- .setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] {
- new InsetsFrameProvider.InsetsSizeOverride(
- TYPE_INPUT_METHOD, null)});
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars());
+ if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+ navBarProvider.setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] {
+ new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
+ });
+ }
if (insetsHeight != -1 && !mEdgeBackGestureHandler.isButtonForcedVisible()) {
navBarProvider.setInsetsSize(Insets.of(0, 0, 0, insetsHeight));
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 76d9b039e112..a7434c6ac797 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -21,16 +21,13 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.RemoteUserInput
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -109,10 +106,6 @@ constructor(
/** Whether the scene container is visible. */
val isVisible: StateFlow<Boolean> = repository.isVisible
- private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null)
- /** A flow of motion events originating from outside of the scene framework. */
- val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow()
-
/**
* Returns the keys of all scenes in the container.
*
@@ -160,11 +153,6 @@ constructor(
repository.setTransitionState(transitionState)
}
- /** Handles a remote user input. */
- fun onRemoteUserInput(input: RemoteUserInput) {
- _remoteUserInput.value = input
- }
-
/**
* Notifies that the UI has transitioned sufficiently to the given scene.
*
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/RemoteUserInput.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/RemoteUserInput.kt
deleted file mode 100644
index 680de590a3fc..000000000000
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/RemoteUserInput.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.android.systemui.scene.shared.model
-
-import android.view.MotionEvent
-
-/** A representation of user input that is used by the scene framework. */
-data class RemoteUserInput(
- val x: Float,
- val y: Float,
- val action: RemoteUserInputAction,
-) {
- companion object {
- fun translateMotionEvent(event: MotionEvent): RemoteUserInput {
- return RemoteUserInput(
- x = event.x,
- y = event.y,
- action =
- when (event.actionMasked) {
- MotionEvent.ACTION_DOWN -> RemoteUserInputAction.DOWN
- MotionEvent.ACTION_MOVE -> RemoteUserInputAction.MOVE
- MotionEvent.ACTION_UP -> RemoteUserInputAction.UP
- MotionEvent.ACTION_CANCEL -> RemoteUserInputAction.CANCEL
- else -> RemoteUserInputAction.UNKNOWN
- }
- )
- }
- }
-}
-
-enum class RemoteUserInputAction {
- DOWN,
- MOVE,
- UP,
- CANCEL,
- UNKNOWN,
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 8601b3de2f7c..cdf50bab6b42 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -2,7 +2,6 @@ package com.android.systemui.scene.ui.view
import android.content.Context
import android.util.AttributeSet
-import android.view.MotionEvent
import android.view.View
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
@@ -39,14 +38,6 @@ class SceneWindowRootView(
)
}
- override fun onTouchEvent(event: MotionEvent?): Boolean {
- return event?.let {
- viewModel.onRemoteUserInput(event)
- true
- }
- ?: false
- }
-
override fun setVisibility(visibility: Int) {
// Do nothing. We don't want external callers to invoke this. Instead, we drive our own
// visibility from our view-binder.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 3e9bbe464e2c..5c16fb54e3a0 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -16,11 +16,9 @@
package com.android.systemui.scene.ui.viewmodel
-import android.view.MotionEvent
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.RemoteUserInput
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import javax.inject.Inject
@@ -34,9 +32,6 @@ class SceneContainerViewModel
constructor(
private val interactor: SceneInteractor,
) {
- /** A flow of motion events originating from outside of the scene framework. */
- val remoteUserInput: StateFlow<RemoteUserInput?> = interactor.remoteUserInput
-
/**
* Keys of all scenes in the container.
*
@@ -68,11 +63,6 @@ constructor(
interactor.setTransitionState(transitionState)
}
- /** Handles a [MotionEvent] representing remote user input. */
- fun onRemoteUserInput(event: MotionEvent) {
- interactor.onRemoteUserInput(RemoteUserInput.translateMotionEvent(event))
- }
-
companion object {
private const val SCENE_TRANSITION_LOGGING_REASON = "user input"
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 05a0416f8f64..ab2a8d9c6e95 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -82,11 +82,15 @@ object ActionIntentCreator {
return editIntent
.setDataAndType(uri, "image/png")
+ .putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_SCREENSHOT)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
+
+ private const val EXTRA_EDIT_SOURCE = "edit_source"
+ private const val EDIT_SOURCE_SCREENSHOT = "screenshot"
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
index 0b4b7c691cfd..bdbc470a61f1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
@@ -43,6 +43,7 @@ import com.android.systemui.R;
*/
public class DraggableConstraintLayout extends ConstraintLayout
implements ViewTreeObserver.OnComputeInternalInsetsListener {
+ public static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
private static final float VELOCITY_DP_PER_MS = 1;
private static final int MAXIMUM_DISMISS_DISTANCE_DP = 400;
@@ -179,8 +180,13 @@ public class DraggableConstraintLayout extends ConstraintLayout
Region r = new Region();
Rect rect = new Rect();
for (int i = 0; i < getChildCount(); i++) {
- getChildAt(i).getGlobalVisibleRect(rect);
- r.op(rect, Region.Op.UNION);
+ View child = getChildAt(i);
+ if (child.getVisibility() == View.VISIBLE) {
+ child.getGlobalVisibleRect(rect);
+ rect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+ r.op(rect, Region.Op.UNION);
+ }
}
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
inoutInfo.touchableRegion.set(r);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 3903bb2815ef..03e1e15c5210 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -68,7 +68,6 @@ import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ScrollCaptureResponse;
-import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -123,7 +122,6 @@ public class ScreenshotView extends FrameLayout implements
public static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
- private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
private final Resources mResources;
private final Interpolator mFastOutSlowIn;
@@ -284,17 +282,22 @@ public class ScreenshotView extends FrameLayout implements
Region swipeRegion = new Region();
final Rect tmpRect = new Rect();
+ int swipePadding = (int) FloatingWindowUtil.dpToPx(
+ mDisplayMetrics, DraggableConstraintLayout.SWIPE_PADDING_DP * -1);
mScreenshotPreview.getBoundsOnScreen(tmpRect);
- tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+ tmpRect.inset(swipePadding, swipePadding);
swipeRegion.op(tmpRect, Region.Op.UNION);
mActionsContainerBackground.getBoundsOnScreen(tmpRect);
- tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+ tmpRect.inset(swipePadding, swipePadding);
swipeRegion.op(tmpRect, Region.Op.UNION);
mDismissButton.getBoundsOnScreen(tmpRect);
swipeRegion.op(tmpRect, Region.Op.UNION);
+ View messageContainer = findViewById(R.id.screenshot_message_container);
+ if (messageContainer != null) {
+ messageContainer.getBoundsOnScreen(tmpRect);
+ swipeRegion.op(tmpRect, Region.Op.UNION);
+ }
View messageDismiss = findViewById(R.id.message_dismiss_button);
if (messageDismiss != null) {
messageDismiss.getBoundsOnScreen(tmpRect);
@@ -378,16 +381,6 @@ public class ScreenshotView extends FrameLayout implements
mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
- int swipePaddingPx = (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, SWIPE_PADDING_DP);
- TouchDelegate previewDelegate = new TouchDelegate(
- new Rect(swipePaddingPx, swipePaddingPx, swipePaddingPx, swipePaddingPx),
- mScreenshotPreview);
- mScreenshotPreview.setTouchDelegate(previewDelegate);
- TouchDelegate actionsDelegate = new TouchDelegate(
- new Rect(swipePaddingPx, swipePaddingPx, swipePaddingPx, swipePaddingPx),
- mActionsContainerBackground);
- mActionsContainerBackground.setTouchDelegate(actionsDelegate);
-
setFocusable(true);
mActionsContainer.setScrollX(0);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 132cd6115bc7..df7d88fe9c90 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -163,6 +163,7 @@ import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.QuickStepContract;
@@ -355,6 +356,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final KeyguardRootView mKeyguardRootView;
private final QuickSettingsController mQsController;
+ private final ShadeInteractor mShadeInteractor;
private final TouchHandler mTouchHandler = new TouchHandler();
private long mDownTime;
@@ -559,7 +561,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private float mHintDistance;
private float mInitialOffsetOnTouch;
private boolean mCollapsedAndHeadsUpOnDown;
- private float mExpandedFraction = 0;
private float mExpansionDragDownAmountPx = 0;
private boolean mPanelClosedOnDown;
private boolean mHasLayoutedSinceDown;
@@ -709,10 +710,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
VibratorHelper vibratorHelper,
LatencyTracker latencyTracker,
PowerManager powerManager,
- AccessibilityManager accessibilityManager, @DisplayId int displayId,
+ AccessibilityManager accessibilityManager,
+ @DisplayId int displayId,
KeyguardUpdateMonitor keyguardUpdateMonitor,
MetricsLogger metricsLogger,
ShadeLogger shadeLogger,
+ ShadeInteractor shadeInteractor,
ConfigurationController configurationController,
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -785,6 +788,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mView = view;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLockscreenGestureLogger = lockscreenGestureLogger;
+ mShadeInteractor = shadeInteractor;
mShadeExpansionStateManager = shadeExpansionStateManager;
mShadeLog = shadeLogger;
mGutsManager = gutsManager;
@@ -2244,7 +2248,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
if (!isFalseTouch(x, y, interactionType)) {
mShadeLog.logFlingExpands(vel, vectorVel, interactionType,
this.mFlingAnimationUtils.getMinVelocityPxPerSecond(),
- mExpandedFraction > 0.5f, mAllowExpandForSmallExpansion);
+ getExpandedFraction() > 0.5f, mAllowExpandForSmallExpansion);
if (Math.abs(vectorVel) < this.mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
expands = shouldExpandWhenNotFlinging();
} else {
@@ -2419,7 +2423,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
void requestScrollerTopPaddingUpdate(boolean animate) {
mNotificationStackScrollLayoutController.updateTopPadding(
mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
- getKeyguardNotificationStaticPadding(), mExpandedFraction), animate);
+ getKeyguardNotificationStaticPadding(), getExpandedFraction()), animate);
if (isKeyguardShowing()
&& mKeyguardBypassController.getBypassEnabled()) {
// update the position of the header
@@ -2501,10 +2505,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private void onHeightUpdated(float expandedHeight) {
if (expandedHeight <= 0) {
mShadeLog.logExpansionChanged("onHeightUpdated: fully collapsed.",
- mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ getExpandedFraction(), isExpanded(), mTracking, mExpansionDragDownAmountPx);
} else if (isFullyExpanded()) {
mShadeLog.logExpansionChanged("onHeightUpdated: fully expanded.",
- mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ getExpandedFraction(), isExpanded(), mTracking, mExpansionDragDownAmountPx);
}
if (!mQsController.getExpanded() || mQsController.isExpandImmediate()
|| mIsExpandingOrCollapsing && mQsController.getExpandedWhenExpandingStarted()) {
@@ -3198,6 +3202,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
}
+ @Override
+ public void performHapticFeedback(int constant) {
+ mVibratorHelper.performHapticFeedback(mView, constant);
+ }
+
private class ShadeHeadsUpTrackerImpl implements ShadeHeadsUpTracker {
@Override
public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
@@ -3425,7 +3434,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
ipw.print("mHintDistance="); ipw.println(mHintDistance);
ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch);
ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown);
- ipw.print("mExpandedFraction="); ipw.println(mExpandedFraction);
+ ipw.print("getExpandedFraction()="); ipw.println(getExpandedFraction());
ipw.print("mExpansionDragDownAmountPx="); ipw.println(mExpansionDragDownAmountPx);
ipw.print("mPanelClosedOnDown="); ipw.println(mPanelClosedOnDown);
ipw.print("mHasLayoutedSinceDown="); ipw.println(mHasLayoutedSinceDown);
@@ -3761,7 +3770,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
// don't fling while in keyguard to avoid jump in shade expand animation;
// touch has been intercepted already so flinging here is redundant
- if (mBarState == KEYGUARD && mExpandedFraction >= 1.0) {
+ if (mBarState == KEYGUARD && getExpandedFraction() >= 1.0) {
mShadeLog.d("NPVC endMotionEvent - skipping fling on keyguard");
} else {
fling(vel, expand, isFalseTouch(x, y, interactionType));
@@ -3857,6 +3866,16 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@VisibleForTesting
void setExpandedHeight(float height) {
debugLog("setExpandedHeight(%.1f)", height);
+ int maxPanelTransitionDistance = getMaxPanelTransitionDistance();
+ if (maxPanelTransitionDistance == 0) {
+ setExpandedFracAndHeight(0, height);
+ } else {
+ setExpandedFracAndHeight(height / maxPanelTransitionDistance, height);
+ }
+ }
+
+ private void setExpandedFracAndHeight(float frac, float height) {
+ mShadeInteractor.setExpansion(frac);
setExpandedHeightInternal(height);
}
@@ -3912,11 +3931,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mHeightAnimator.end();
}
}
- mExpandedFraction = Math.min(1f,
- maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
- mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction);
+ mQsController.setShadeExpansion(mExpandedHeight, getExpandedFraction());
mExpansionDragDownAmountPx = h;
- mAmbientState.setExpansionFraction(mExpandedFraction);
+ mAmbientState.setExpansionFraction(getExpandedFraction());
onHeightUpdated(mExpandedHeight);
updateExpansionAndVisibility();
});
@@ -3944,8 +3961,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
/** Sets the expanded height relative to a number from 0 to 1. */
@VisibleForTesting
void setExpandedFraction(float frac) {
- final int maxDist = getMaxPanelTransitionDistance();
- setExpandedHeight(maxDist * frac);
+ setExpandedFracAndHeight(frac, getMaxPanelTransitionDistance() * frac);
}
float getExpandedHeight() {
@@ -3953,7 +3969,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
float getExpandedFraction() {
- return mExpandedFraction;
+ return mShadeInteractor.getExpansion().getValue();
}
@Override
@@ -3975,7 +3991,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Override
public boolean isFullyCollapsed() {
- return mExpandedFraction <= 0.0f;
+ return getExpandedFraction() <= 0.0f;
}
@Override
@@ -4134,7 +4150,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
animator.getAnimatedFraction()));
setOverExpansionInternal(expansion, false /* isFromGesture */);
}
- setExpandedHeightInternal((float) animation.getAnimatedValue());
+ setExpandedHeight((float) animation.getAnimatedValue());
});
return animator;
}
@@ -4148,14 +4164,14 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Override
public void updateExpansionAndVisibility() {
mShadeExpansionStateManager.onPanelExpansionChanged(
- mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ getExpandedFraction(), isExpanded(), mTracking, mExpansionDragDownAmountPx);
updateVisibility();
}
@Override
public boolean isExpanded() {
- return mExpandedFraction > 0f
+ return getExpandedFraction() > 0f
|| mInstantExpanding
|| isPanelVisibleBecauseOfHeadsUp()
|| mTracking
@@ -4913,7 +4929,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mMotionAborted = false;
mPanelClosedOnDown = isFullyCollapsed();
mShadeLog.logPanelClosedOnDown("intercept down touch", mPanelClosedOnDown,
- mExpandedFraction);
+ getExpandedFraction());
mCollapsedAndHeadsUpOnDown = false;
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
@@ -5132,7 +5148,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mMinExpandHeight = 0.0f;
mPanelClosedOnDown = isFullyCollapsed();
mShadeLog.logPanelClosedOnDown("handle down touch", mPanelClosedOnDown,
- mExpandedFraction);
+ getExpandedFraction());
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
mMotionAborted = false;
@@ -5191,7 +5207,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
if (isFullyCollapsed()) {
// If panel is fully collapsed, reset haptic effect before adding movement.
mHasVibratedOnOpen = false;
- mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction);
+ mShadeLog.logHasVibrated(mHasVibratedOnOpen, getExpandedFraction());
}
addMovement(event);
if (!isFullyCollapsed()) {
@@ -5227,7 +5243,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
// otherwise {@link NotificationStackScrollLayout}
// wrongly enables stack height updates at the start of lockscreen swipe-up
mAmbientState.setSwipingUp(h <= 0);
- setExpandedHeightInternal(newHeight);
+ setExpandedHeight(newHeight);
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index bea12de5ab49..656411874de5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -263,9 +263,11 @@ constructor(
resources.getDimensionPixelSize(
R.dimen.shade_header_system_icons_padding_start
),
- systemIcons.paddingTop,
+ resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_top),
resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_end),
- systemIcons.paddingBottom
+ resources.getDimensionPixelSize(
+ R.dimen.shade_header_system_icons_padding_bottom
+ )
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index d5b5c87ec781..182a676c9841 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -248,6 +248,16 @@ interface ShadeViewController {
/** Starts tracking a shade expansion gesture that originated from the status bar. */
fun startTrackingExpansionFromStatusBar()
+ /**
+ * Performs haptic feedback from a view with a haptic feedback constant.
+ *
+ * The implementation of this method should use the [android.view.View.performHapticFeedback]
+ * method with the provided constant.
+ *
+ * @param[constant] One of [android.view.HapticFeedbackConstants]
+ */
+ fun performHapticFeedback(constant: Int)
+
// ******* End Keyguard Section *********
/** Returns the ShadeHeadsUpTracker. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 287ac528385f..09b74b213ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -86,6 +86,8 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController {
return false
}
override fun startTrackingExpansionFromStatusBar() {}
+ override fun performHapticFeedback(constant: Int) {}
+
override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index ebb9935ca813..76dca4ca5b03 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -34,13 +34,34 @@ interface ShadeRepository {
/** ShadeModel information regarding shade expansion events */
val shadeModel: Flow<ShadeModel>
- /** Amount qs has expanded. Quick Settings can be expanded without the full shade expansion. */
+ /**
+ * Value from `0` to `1` representing the amount the shade has expanded where `1` is fully
+ * expanded and `0` is fully collapsed. Quick Settings can be expanded without a fully expanded
+ * shade.
+ */
val qsExpansion: StateFlow<Float>
+ /**
+ * Value from `0` to `1` representing the amount the shade has expanded where `1` is fully
+ * expanded and `0` is fully collapsed.
+ */
+ val expansion: StateFlow<Float>
+
/** Amount shade has expanded with regard to the UDFPS location */
val udfpsTransitionToFullShadeProgress: StateFlow<Float>
+ /**
+ * Set shade expansion to a value from `0` to `1` representing the amount the shade has expanded
+ * where `1` is fully expanded and `0` is fully collapsed.
+ */
+ fun setExpansion(expansion: Float)
+
+ /**
+ * Set quick settings expansion to a value from `0` to `1` representing the amount quick
+ * settings has expanded where `1` is fully expanded and `0` is fully collapsed.
+ */
fun setQsExpansion(qsExpansion: Float)
+
fun setUdfpsTransitionToFullShadeProgress(progress: Float)
}
@@ -78,9 +99,17 @@ constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepos
private val _qsExpansion = MutableStateFlow(0f)
override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
+ private val _expansion = MutableStateFlow(0f)
+ override val expansion: StateFlow<Float> = _expansion.asStateFlow()
+
private var _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
override val udfpsTransitionToFullShadeProgress: StateFlow<Float> =
_udfpsTransitionToFullShadeProgress.asStateFlow()
+
+ override fun setExpansion(expansion: Float) {
+ _expansion.value = expansion
+ }
+
override fun setQsExpansion(qsExpansion: Float) {
_qsExpansion.value = qsExpansion
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 6fde84a35fb1..efe3eb4db3ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -19,6 +19,7 @@ package com.android.systemui.shade.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -38,18 +39,29 @@ class ShadeInteractor
@Inject
constructor(
@Application scope: CoroutineScope,
+ private val shadeRepository: ShadeRepository,
disableFlagsRepository: DisableFlagsRepository,
keyguardRepository: KeyguardRepository,
userSetupRepository: UserSetupRepository,
deviceProvisionedController: DeviceProvisionedController,
userInteractor: UserInteractor,
) {
+ /**
+ * Value from `0` to `1` representing the amount the shade has expanded where `1` is fully
+ * expanded and `0` is fully collapsed.
+ */
+ val expansion = shadeRepository.expansion
+
/** Emits true if the shade is currently allowed and false otherwise. */
val isShadeEnabled: StateFlow<Boolean> =
disableFlagsRepository.disableFlags
.map { it.isShadeEnabled() }
.stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+ fun setExpansion(expansion: Float) {
+ shadeRepository.setExpansion(expansion)
+ }
+
/** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
val isExpandToQsEnabled: Flow<Boolean> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 5c2f9a8d28ec..62a0d138fd05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -39,10 +39,7 @@ class StackCoordinator @Inject internal constructor(
override fun attach(pipeline: NotifPipeline) {
pipeline.addOnAfterRenderListListener(::onAfterRenderList)
- // TODO(b/282865576): This has an issue where it makes changes to some groups without
- // notifying listeners. To be fixed in QPR, but for now let's comment it out to avoid the
- // group expansion bug.
- // groupExpansionManagerImpl.attach(pipeline)
+ groupExpansionManagerImpl.attach(pipeline)
}
fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 46af03a438f5..5d33804ab6a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -67,18 +67,29 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
* Cleanup entries from mExpandedGroups that no longer exist in the pipeline.
*/
private final OnBeforeRenderListListener mNotifTracker = (entries) -> {
+ if (mExpandedGroups.isEmpty()) {
+ return; // nothing to do
+ }
+
final Set<NotificationEntry> renderingSummaries = new HashSet<>();
for (ListEntry entry : entries) {
if (entry instanceof GroupEntry) {
renderingSummaries.add(entry.getRepresentativeEntry());
}
}
- mExpandedGroups.removeIf(expandedGroup -> !renderingSummaries.contains(expandedGroup));
+
+ // Create a copy of mExpandedGroups so we can modify it in a thread-safe way.
+ final var currentExpandedGroups = new HashSet<>(mExpandedGroups);
+ for (NotificationEntry entry : currentExpandedGroups) {
+ setExpanded(entry, renderingSummaries.contains(entry));
+ }
};
public void attach(NotifPipeline pipeline) {
- mDumpManager.registerDumpable(this);
- pipeline.addOnBeforeRenderListListener(mNotifTracker);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
+ mDumpManager.registerDumpable(this);
+ pipeline.addOnBeforeRenderListListener(mNotifTracker);
+ }
}
@Override
@@ -94,11 +105,24 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
@Override
public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
final NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
+ setExpanded(groupSummary, expanded);
+ }
+
+ /**
+ * Add or remove {@code entry} to/from {@code mExpandedGroups} and notify listeners if
+ * something changed. This assumes that {@code entry} is a group summary.
+ * <p>
+ * TODO(b/293434635): Currently, in spite of its docs,
+ * {@code mGroupMembershipManager.getGroupSummary(entry)} returns null if {@code entry} is
+ * already a summary. Instead of needing this helper method to bypass that, we probably want to
+ * move this code back to {@code setGroupExpanded} and use that everywhere.
+ */
+ private void setExpanded(NotificationEntry entry, boolean expanded) {
boolean changed;
if (expanded) {
- changed = mExpandedGroups.add(groupSummary);
+ changed = mExpandedGroups.add(entry);
} else {
- changed = mExpandedGroups.remove(groupSummary);
+ changed = mExpandedGroups.remove(entry);
}
// Only notify listeners if something changed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 6431ef958239..2bc7b996ffb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
@@ -33,12 +34,15 @@ import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.Log;
import android.util.Slog;
+import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.LetterboxDetails;
@@ -49,6 +53,7 @@ import com.android.systemui.assist.AssistManager;
import com.android.systemui.camera.CameraIntents;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
@@ -107,6 +112,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
private final Lazy<CameraLauncher> mCameraLauncherLazy;
private final QuickSettingsController mQsController;
private final QSHost mQSHost;
+ private final FeatureFlags mFeatureFlags;
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -144,7 +150,8 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
Lazy<CameraLauncher> cameraLauncherLazy,
UserTracker userTracker,
QSHost qsHost,
- ActivityStarter activityStarter) {
+ ActivityStarter activityStarter,
+ FeatureFlags featureFlags) {
mCentralSurfaces = centralSurfaces;
mQsController = quickSettingsController;
mContext = context;
@@ -171,6 +178,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
mCameraLauncherLazy = cameraLauncherLazy;
mUserTracker = userTracker;
mQSHost = qsHost;
+ mFeatureFlags = featureFlags;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -314,7 +322,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
if (mShadeViewController.isFullyCollapsed()) {
if (mVibrateOnOpening) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+ vibrateOnNavigationKeyDown();
}
mShadeViewController.expand(true /* animate */);
mNotificationStackScrollLayoutController.setWillExpand(true);
@@ -587,4 +595,15 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
}
return VibrationEffect.createWaveform(timings, /* repeat= */ -1);
}
+
+ @VisibleForTesting
+ void vibrateOnNavigationKeyDown() {
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ mShadeViewController.performHapticFeedback(
+ HapticFeedbackConstants.GESTURE_START
+ );
+ } else {
+ mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 127569d179fe..8ffd43a6eb89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1612,8 +1612,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mBackActionInteractor.setup(mQsController, mShadeSurface);
mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
- mHeadsUpManager.addListener(mCentralSurfacesComponent.getStatusBarHeadsUpChangeListener());
-
// Listen for demo mode changes
mDemoModeController.addCallback(mDemoModeCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 931aedd90542..4de669c0b34a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -28,8 +28,7 @@ import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.RemoteUserInput
+import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
@@ -56,7 +55,7 @@ class PhoneStatusBarViewController private constructor(
private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
- private val sceneInteractor: Provider<SceneInteractor>,
+ private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
private val userChipViewModel: StatusBarUserChipViewModel,
@@ -80,7 +79,8 @@ class PhoneStatusBarViewController private constructor(
statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer))
if (moveFromCenterAnimationController == null) return
- val statusBarLeftSide: View = mView.requireViewById(R.id.status_bar_start_side_except_heads_up)
+ val statusBarLeftSide: View =
+ mView.requireViewById(R.id.status_bar_start_side_except_heads_up)
val systemIconArea: ViewGroup = mView.requireViewById(R.id.status_bar_end_side_content)
val viewsToAnimate = arrayOf(
@@ -179,11 +179,8 @@ class PhoneStatusBarViewController private constructor(
// If scene framework is enabled, route the touch to it and
// ignore the rest of the gesture.
if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
- sceneInteractor.get()
- .onRemoteUserInput(RemoteUserInput.translateMotionEvent(event))
- // TODO(b/291965119): remove once view is expanded to cover the status bar
- sceneInteractor.get().setVisible(true, "swipe down from status bar")
- return false
+ windowRootView.get().dispatchTouchEvent(event)
+ return true
}
if (event.action == MotionEvent.ACTION_DOWN) {
@@ -247,7 +244,7 @@ class PhoneStatusBarViewController private constructor(
private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
- private val sceneInteractor: Provider<SceneInteractor>,
+ private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
private val configurationController: ConfigurationController,
@@ -269,7 +266,7 @@ class PhoneStatusBarViewController private constructor(
centralSurfaces,
shadeController,
shadeViewController,
- sceneInteractor,
+ windowRootView,
shadeLogger,
statusBarMoveFromCenterAnimationController,
userChipViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 9a295e63fb9e..4b39854e43ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -16,23 +16,24 @@
package com.android.systemui.statusbar.phone;
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import javax.inject.Inject;
/**
- * Ties the {@link CentralSurfaces} to {@link com.android.systemui.statusbar.policy.HeadsUpManager}.
+ * Ties the status bar to {@link com.android.systemui.statusbar.policy.HeadsUpManager}.
*/
-@CentralSurfacesComponent.CentralSurfacesScope
-public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener {
+@SysUISingleton
+public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, CoreStartable {
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarWindowController mStatusBarWindowController;
private final ShadeViewController mShadeViewController;
@@ -63,6 +64,11 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener
}
@Override
+ public void start() {
+ mHeadsUpManager.addListener(this);
+ }
+
+ @Override
public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
if (inPinnedMode) {
mNotificationShadeWindowController.setHeadsUpShowing(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index 3a3663d4d6c4..bbbe16f54734 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -25,7 +25,6 @@ import com.android.systemui.shade.ShadeHeaderController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarterModule;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
@@ -72,11 +71,6 @@ public interface CentralSurfacesComponent {
WindowRootView getWindowRootView();
/**
- * Creates a StatusBarHeadsUpChangeListener.
- */
- StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener();
-
- /**
* Creates a CentralSurfacesCommandQueueCallbacks.
*/
CentralSurfacesCommandQueueCallbacks getCentralSurfacesCommandQueueCallbacks();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index fe2481595ff4..275cfc58d581 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -221,36 +221,15 @@ constructor(
override val activityInVisible: Flow<Boolean> =
activity
.map { it?.hasActivityIn ?: false }
- .distinctUntilChanged()
- .logDiffsForTable(
- iconInteractor.tableLogBuffer,
- columnPrefix = "",
- columnName = "activityInVisible",
- initialValue = false,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val activityOutVisible: Flow<Boolean> =
activity
.map { it?.hasActivityOut ?: false }
- .distinctUntilChanged()
- .logDiffsForTable(
- iconInteractor.tableLogBuffer,
- columnPrefix = "",
- columnName = "activityOutVisible",
- initialValue = false,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val activityContainerVisible: Flow<Boolean> =
activity
.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
- .distinctUntilChanged()
- .logDiffsForTable(
- iconInteractor.tableLogBuffer,
- columnPrefix = "",
- columnName = "activityContainerVisible",
- initialValue = false,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
index 4a31b8687069..eaae0f0c4f75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
@@ -33,6 +33,7 @@ import androidx.annotation.VisibleForTesting
import com.android.systemui.Dependency
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.ShadeLogger
import com.android.systemui.util.ViewController
import com.android.systemui.util.time.SystemClock
import java.text.FieldPosition
@@ -83,6 +84,7 @@ class VariableDateViewController(
private val systemClock: SystemClock,
private val broadcastDispatcher: BroadcastDispatcher,
private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val shadeLogger: ShadeLogger,
private val timeTickHandler: Handler,
view: VariableDateView
) : ViewController<VariableDateView>(view) {
@@ -111,24 +113,29 @@ class VariableDateViewController(
private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.action
+ if (
+ Intent.ACTION_LOCALE_CHANGED == action ||
+ Intent.ACTION_TIMEZONE_CHANGED == action
+ ) {
+ // need to get a fresh date format
+ dateFormat = null
+ shadeLogger.d("VariableDateViewController received intent to refresh date format")
+ }
+
+ val handler = mView.handler
+
// If the handler is null, it means we received a broadcast while the view has not
// finished being attached or in the process of being detached.
// In that case, do not post anything.
- val handler = mView.handler ?: return
- val action = intent.action
- if (
+ if (handler == null) {
+ shadeLogger.d("VariableDateViewController received intent but handler was null")
+ } else if (
Intent.ACTION_TIME_TICK == action ||
Intent.ACTION_TIME_CHANGED == action ||
Intent.ACTION_TIMEZONE_CHANGED == action ||
Intent.ACTION_LOCALE_CHANGED == action
) {
- if (
- Intent.ACTION_LOCALE_CHANGED == action ||
- Intent.ACTION_TIMEZONE_CHANGED == action
- ) {
- // need to get a fresh date format
- handler.post { dateFormat = null }
- }
handler.post(::updateClock)
}
}
@@ -231,6 +238,7 @@ class VariableDateViewController(
private val systemClock: SystemClock,
private val broadcastDispatcher: BroadcastDispatcher,
private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val shadeLogger: ShadeLogger,
@Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
) {
fun create(view: VariableDateView): VariableDateViewController {
@@ -238,6 +246,7 @@ class VariableDateViewController(
systemClock,
broadcastDispatcher,
shadeExpansionStateManager,
+ shadeLogger,
handler,
view
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 53f4837cefcb..9cc3cdbf5c34 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -67,7 +67,6 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
@@ -84,6 +83,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
@@ -134,7 +134,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private final Receiver mReceiver = new Receiver();
private final RingerModeObservers mRingerModeObservers;
private final MediaSessions mMediaSessions;
- private CaptioningManager mCaptioningManager;
+ private final AtomicReference<CaptioningManager> mCaptioningManager = new AtomicReference<>();
private final KeyguardManager mKeyguardManager;
private final ActivityManager mActivityManager;
private final UserTracker mUserTracker;
@@ -158,16 +158,16 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private final WakefulnessLifecycle.Observer mWakefullnessLifecycleObserver =
new WakefulnessLifecycle.Observer() {
- @Override
- public void onStartedWakingUp() {
- mDeviceInteractive = true;
- }
+ @Override
+ public void onStartedWakingUp() {
+ mDeviceInteractive = true;
+ }
- @Override
- public void onFinishedGoingToSleep() {
- mDeviceInteractive = false;
- }
- };
+ @Override
+ public void onFinishedGoingToSleep() {
+ mDeviceInteractive = false;
+ }
+ };
@Inject
public VolumeDialogControllerImpl(
@@ -185,8 +185,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
KeyguardManager keyguardManager,
ActivityManager activityManager,
UserTracker userTracker,
- DumpManager dumpManager,
- @Main Handler mainHandler
+ DumpManager dumpManager
) {
mContext = context.getApplicationContext();
mPackageManager = packageManager;
@@ -215,7 +214,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
mKeyguardManager = keyguardManager;
mActivityManager = activityManager;
mUserTracker = userTracker;
- mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mainHandler));
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mWorker));
createCaptioningManagerServiceByUserContext(mUserTracker.getUserContext());
dumpManager.registerDumpable("VolumeDialogControllerImpl", this);
@@ -223,8 +222,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
boolean accessibilityVolumeStreamActive = accessibilityManager
.isAccessibilityVolumeStreamActive();
mVolumeController.setA11yMode(accessibilityVolumeStreamActive ?
- VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
- VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
+ VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
+ VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
mWakefulnessLifecycle.addObserver(mWakefullnessLifecycleObserver);
}
@@ -337,15 +336,15 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
};
private void createCaptioningManagerServiceByUserContext(@NonNull Context userContext) {
- mCaptioningManager = userContext.getSystemService(CaptioningManager.class);
+ mCaptioningManager.set(userContext.getSystemService(CaptioningManager.class));
}
- public boolean areCaptionsEnabled() {
- return mCaptioningManager.isSystemAudioCaptioningEnabled();
+ public void getCaptionsEnabledState(boolean checkForSwitchState) {
+ mWorker.obtainMessage(W.GET_CAPTIONS_ENABLED_STATE, checkForSwitchState).sendToTarget();
}
- public void setCaptionsEnabled(boolean isEnabled) {
- mCaptioningManager.setSystemAudioCaptioningEnabled(isEnabled);
+ public void setCaptionsEnabledState(boolean enabled) {
+ mWorker.obtainMessage(W.SET_CAPTIONS_ENABLED_STATE, enabled).sendToTarget();
}
public void getCaptionsComponentState(boolean fromTooltip) {
@@ -386,8 +385,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
public void setEnableDialogs(boolean volumeUi, boolean safetyWarning) {
- mShowVolumeDialog = volumeUi;
- mShowSafetyWarning = safetyWarning;
+ mShowVolumeDialog = volumeUi;
+ mShowSafetyWarning = safetyWarning;
}
@Override
@@ -438,12 +437,38 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
private void onShowCsdWarningW(@AudioManager.CsdWarning int csdWarning, int durationMs) {
- mCallbacks.onShowCsdWarning(csdWarning, durationMs);
+ mCallbacks.onShowCsdWarning(csdWarning, durationMs);
}
private void onGetCaptionsComponentStateW(boolean fromTooltip) {
- mCallbacks.onCaptionComponentStateChanged(
- mCaptioningManager.isSystemAudioCaptioningUiEnabled(), fromTooltip);
+ CaptioningManager captioningManager = mCaptioningManager.get();
+ if (null != captioningManager) {
+ mCallbacks.onCaptionComponentStateChanged(
+ captioningManager.isSystemAudioCaptioningUiEnabled(), fromTooltip);
+ } else {
+ Log.e(TAG, "onGetCaptionsComponentStateW(), null captioningManager");
+ }
+ }
+
+ private void onGetCaptionsEnabledStateW(boolean checkForSwitchState) {
+ CaptioningManager captioningManager = mCaptioningManager.get();
+ if (null != captioningManager) {
+ mCallbacks.onCaptionEnabledStateChanged(
+ captioningManager.isSystemAudioCaptioningEnabled(), checkForSwitchState);
+ } else {
+ Log.e(TAG, "onGetCaptionsEnabledStateW(), null captioningManager");
+ }
+ }
+
+ private void onSetCaptionsEnabledStateW(boolean enabled) {
+ CaptioningManager captioningManager = mCaptioningManager.get();
+ if (null != captioningManager) {
+ captioningManager.setSystemAudioCaptioningEnabled(enabled);
+ mCallbacks.onCaptionEnabledStateChanged(
+ captioningManager.isSystemAudioCaptioningEnabled(), false);
+ } else {
+ Log.e(TAG, "onGetCaptionsEnabledStateW(), null captioningManager");
+ }
}
private void onAccessibilityModeChanged(Boolean showA11yStream) {
@@ -822,6 +847,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private static final int ACCESSIBILITY_MODE_CHANGED = 15;
private static final int GET_CAPTIONS_COMPONENT_STATE = 16;
private static final int SHOW_CSD_WARNING = 17;
+ private static final int GET_CAPTIONS_ENABLED_STATE = 18;
+ private static final int SET_CAPTIONS_ENABLED_STATE = 19;
W(Looper looper) {
super(looper);
@@ -849,6 +876,10 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
break;
case SHOW_CSD_WARNING: onShowCsdWarningW(msg.arg1, msg.arg2); break;
+ case GET_CAPTIONS_ENABLED_STATE:
+ onGetCaptionsEnabledStateW((Boolean) msg.obj); break;
+ case SET_CAPTIONS_ENABLED_STATE:
+ onSetCaptionsEnabledStateW((Boolean) msg.obj); break;
}
}
}
@@ -1017,6 +1048,17 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
componentEnabled, fromTooltip));
}
}
+
+ @Override
+ public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch) {
+ boolean captionsEnabled = isEnabled != null && isEnabled;
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(
+ () -> entry.getKey().onCaptionEnabledStateChanged(
+ captionsEnabled, checkBeforeSwitch));
+ }
+ }
+
}
private final class RingerModeObservers {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0e97e2192cdc..dcc0525bb436 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1333,21 +1333,30 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
if (!isServiceComponentEnabled) return;
- updateCaptionsIcon();
+ checkEnabledStateForCaptionsIconUpdate();
if (fromTooltip) showCaptionsTooltip();
}
- private void updateCaptionsIcon() {
- boolean captionsEnabled = mController.areCaptionsEnabled();
- if (mODICaptionsIcon.getCaptionsEnabled() != captionsEnabled) {
- mHandler.post(mODICaptionsIcon.setCaptionsEnabled(captionsEnabled));
+ private void updateCaptionsEnabledH(boolean isCaptionsEnabled, boolean checkForSwitchState) {
+ if (checkForSwitchState) {
+ mController.setCaptionsEnabledState(!isCaptionsEnabled);
+ } else {
+ updateCaptionsIcon(isCaptionsEnabled);
+ }
+ }
+
+ private void checkEnabledStateForCaptionsIconUpdate() {
+ mController.getCaptionsEnabledState(false);
+ }
+
+ private void updateCaptionsIcon(boolean isCaptionsEnabled) {
+ if (mODICaptionsIcon.getCaptionsEnabled() != isCaptionsEnabled) {
+ mHandler.post(mODICaptionsIcon.setCaptionsEnabled(isCaptionsEnabled));
}
}
private void onCaptionIconClicked() {
- boolean isEnabled = mController.areCaptionsEnabled();
- mController.setCaptionsEnabled(!isEnabled);
- updateCaptionsIcon();
+ mController.getCaptionsEnabledState(true);
}
private void incrementManualToggleCount() {
@@ -2365,7 +2374,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
} else {
updateRowsH(activeRow);
}
-
}
@Override
@@ -2373,6 +2381,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
Boolean isComponentEnabled, Boolean fromTooltip) {
updateODICaptionsH(isComponentEnabled, fromTooltip);
}
+
+ @Override
+ public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkForSwitchState) {
+ updateCaptionsEnabledH(isEnabled, checkForSwitchState);
+ }
};
@VisibleForTesting void onPostureChanged(int posture) {
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 9dca013f8aa4..aea3030967d2 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -109,6 +109,7 @@ public class ImageWallpaper extends WallpaperService {
private WallpaperManager mWallpaperManager;
private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
private SurfaceHolder mSurfaceHolder;
+ private boolean mDrawn = false;
@VisibleForTesting
static final int MIN_SURFACE_WIDTH = 128;
@VisibleForTesting
@@ -239,6 +240,7 @@ public class ImageWallpaper extends WallpaperService {
private void drawFrameSynchronized() {
synchronized (mLock) {
+ if (mDrawn) return;
drawFrameInternal();
}
}
@@ -276,6 +278,7 @@ public class ImageWallpaper extends WallpaperService {
Rect dest = mSurfaceHolder.getSurfaceFrame();
try {
canvas.drawBitmap(bitmap, null, dest, null);
+ mDrawn = true;
} finally {
surface.unlockCanvasAndPost(canvas);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 677d3ff3df69..c894d914bfa3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -180,8 +180,9 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
}
@Test
- public void testResume() {
- mKeyguardAbsKeyInputViewController.onResume(KeyguardSecurityView.VIEW_REVEALED);
+ public void testOnViewAttached() {
+ reset(mLockPatternUtils);
+ mKeyguardAbsKeyInputViewController.onViewAttached();
verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index b3496967f525..438f0f43acb6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -18,6 +18,8 @@ package com.android.keyguard;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static com.android.systemui.flags.Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
@@ -37,6 +39,7 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -59,8 +62,13 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase {
@Mock
private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
@Mock
+ private ConnectedDisplayKeyguardPresentation.Factory
+ mConnectedDisplayKeyguardPresentationFactory;
+ @Mock
private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
@Mock
+ private ConnectedDisplayKeyguardPresentation mConnectedDisplayKeyguardPresentation;
+ @Mock
private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper;
@Mock
private KeyguardStateController mKeyguardStateController;
@@ -69,7 +77,7 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase {
private Executor mBackgroundExecutor = Runnable::run;
private KeyguardDisplayManager mManager;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
-
+ private FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
// The default and secondary displays are both in the default group
private Display mDefaultDisplay;
private Display mSecondaryDisplay;
@@ -80,10 +88,14 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, false);
mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
mKeyguardStatusViewComponentFactory, mDisplayTracker, mMainExecutor,
- mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController));
+ mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController,
+ mConnectedDisplayKeyguardPresentationFactory, mFakeFeatureFlags));
doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
+ doReturn(mConnectedDisplayKeyguardPresentation).when(
+ mConnectedDisplayKeyguardPresentationFactory).create(any());
mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
@@ -138,4 +150,15 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase {
when(mKeyguardStateController.isOccluded()).thenReturn(true);
verify(mManager, never()).createPresentation(eq(mSecondaryDisplay));
}
+
+ @Test
+ public void testShow_withClockPresentationFlagEnabled_presentationCreated() {
+ when(mManager.createPresentation(any())).thenCallRealMethod();
+ mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, true);
+ mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
+
+ mManager.show();
+
+ verify(mConnectedDisplayKeyguardPresentationFactory).create(eq(mSecondaryDisplay));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 1a9260c2ede6..3a9473085583 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -20,6 +20,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
+import android.widget.ImageView
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
@@ -29,6 +30,7 @@ import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.whenever
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,6 +38,7 @@ import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
@@ -45,104 +48,109 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPasswordViewControllerTest : SysuiTestCase() {
- @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
- @Mock private lateinit var passwordEntry: EditText
- @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
- @Mock lateinit var lockPatternUtils: LockPatternUtils
- @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
- @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
- @Mock lateinit var latencyTracker: LatencyTracker
- @Mock lateinit var inputMethodManager: InputMethodManager
- @Mock lateinit var emergencyButtonController: EmergencyButtonController
- @Mock lateinit var mainExecutor: DelayableExecutor
- @Mock lateinit var falsingCollector: FalsingCollector
- @Mock lateinit var keyguardViewController: KeyguardViewController
- @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
- @Mock
- private lateinit var mKeyguardMessageAreaController:
- KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
+ @Mock private lateinit var passwordEntry: EditText
+ @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+ @Mock lateinit var lockPatternUtils: LockPatternUtils
+ @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock lateinit var latencyTracker: LatencyTracker
+ @Mock lateinit var inputMethodManager: InputMethodManager
+ @Mock lateinit var emergencyButtonController: EmergencyButtonController
+ @Mock lateinit var mainExecutor: DelayableExecutor
+ @Mock lateinit var falsingCollector: FalsingCollector
+ @Mock lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+ @Mock
+ private lateinit var mKeyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+ private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- Mockito.`when`(
- keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>(
- R.id.bouncer_message_area))
- .thenReturn(mKeyguardMessageArea)
- Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
- .thenReturn(mKeyguardMessageAreaController)
- Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
- Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
- .thenReturn(passwordEntry)
- `when`(keyguardPasswordView.resources).thenReturn(context.resources)
- val fakeFeatureFlags = FakeFeatureFlags()
- fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
- keyguardPasswordViewController =
- KeyguardPasswordViewController(
- keyguardPasswordView,
- keyguardUpdateMonitor,
- securityMode,
- lockPatternUtils,
- keyguardSecurityCallback,
- messageAreaControllerFactory,
- latencyTracker,
- inputMethodManager,
- emergencyButtonController,
- mainExecutor,
- mContext.resources,
- falsingCollector,
- keyguardViewController,
- fakeFeatureFlags)
- }
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ Mockito.`when`(
+ keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>(
+ R.id.bouncer_message_area
+ )
+ )
+ .thenReturn(mKeyguardMessageArea)
+ Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
+ .thenReturn(mKeyguardMessageAreaController)
+ Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+ Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
+ .thenReturn(passwordEntry)
+ whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
+ .thenReturn(mock(ImageView::class.java))
+ `when`(keyguardPasswordView.resources).thenReturn(context.resources)
+ val fakeFeatureFlags = FakeFeatureFlags()
+ fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ keyguardPasswordViewController =
+ KeyguardPasswordViewController(
+ keyguardPasswordView,
+ keyguardUpdateMonitor,
+ securityMode,
+ lockPatternUtils,
+ keyguardSecurityCallback,
+ messageAreaControllerFactory,
+ latencyTracker,
+ inputMethodManager,
+ emergencyButtonController,
+ mainExecutor,
+ mContext.resources,
+ falsingCollector,
+ keyguardViewController,
+ fakeFeatureFlags
+ )
+ }
- @Test
- fun testFocusWhenBouncerIsShown() {
- Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
- Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
- keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- keyguardPasswordView.post {
- verify(keyguardPasswordView).requestFocus()
- verify(keyguardPasswordView).showKeyboard()
+ @Test
+ fun testFocusWhenBouncerIsShown() {
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
+ Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+ keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).requestFocus()
+ verify(keyguardPasswordView).showKeyboard()
+ }
}
- }
- @Test
- fun testDoNotFocusWhenBouncerIsHidden() {
- Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
- Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
- keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- verify(keyguardPasswordView, never()).requestFocus()
- }
+ @Test
+ fun testDoNotFocusWhenBouncerIsHidden() {
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
+ Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+ keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ verify(keyguardPasswordView, never()).requestFocus()
+ }
- @Test
- fun testHideKeyboardWhenOnPause() {
- keyguardPasswordViewController.onPause()
- keyguardPasswordView.post {
- verify(keyguardPasswordView).clearFocus()
- verify(keyguardPasswordView).hideKeyboard()
+ @Test
+ fun testHideKeyboardWhenOnPause() {
+ keyguardPasswordViewController.onPause()
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).clearFocus()
+ verify(keyguardPasswordView).hideKeyboard()
+ }
}
- }
- @Test
- fun startAppearAnimation() {
- keyguardPasswordViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController)
- .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false)
- }
+ @Test
+ fun testOnViewAttached() {
+ keyguardPasswordViewController.onViewAttached()
+ verify(mKeyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false)
+ }
- @Test
- fun startAppearAnimation_withExistingMessage() {
- `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- keyguardPasswordViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
- }
+ @Test
+ fun testOnViewAttached_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ keyguardPasswordViewController.onViewAttached()
+ verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+ }
- @Test
- fun testMessageIsSetWhenReset() {
- keyguardPasswordViewController.resetState()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
- }
+ @Test
+ fun testMessageIsSetWhenReset() {
+ keyguardPasswordViewController.resetState()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 9f7ab7b30d19..1acd676f02cd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -46,6 +46,7 @@ import org.mockito.ArgumentMatchers.anyString
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -54,35 +55,35 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPatternViewControllerTest : SysuiTestCase() {
- private lateinit var mKeyguardPatternView: KeyguardPatternView
+ private lateinit var mKeyguardPatternView: KeyguardPatternView
- @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
+ @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
- @Mock private lateinit var mLockPatternUtils: LockPatternUtils
+ @Mock private lateinit var mLockPatternUtils: LockPatternUtils
- @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
- @Mock private lateinit var mLatencyTracker: LatencyTracker
- private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
+ @Mock private lateinit var mLatencyTracker: LatencyTracker
+ private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
- @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController
+ @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController
- @Mock
- private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock
+ private lateinit var mKeyguardMessageAreaControllerFactory:
+ KeyguardMessageAreaController.Factory
- @Mock
- private lateinit var mKeyguardMessageAreaController:
- KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock
+ private lateinit var mKeyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- @Mock private lateinit var mPostureController: DevicePostureController
+ @Mock private lateinit var mPostureController: DevicePostureController
- private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
- private lateinit var fakeFeatureFlags: FakeFeatureFlags
+ private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
+ private lateinit var fakeFeatureFlags: FakeFeatureFlags
- @Captor
- lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
+ @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
@Before
fun setup() {
@@ -91,9 +92,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
.thenReturn(mKeyguardMessageAreaController)
fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
- mKeyguardPatternView = View.inflate(mContext, R.layout.keyguard_pattern_view, null)
- as KeyguardPatternView
-
+ mKeyguardPatternView =
+ View.inflate(mContext, R.layout.keyguard_pattern_view, null) as KeyguardPatternView
mKeyguardPatternViewController =
KeyguardPatternViewController(
@@ -125,8 +125,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
@Test
fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
- whenever(mPostureController.devicePosture)
- .thenReturn(DEVICE_POSTURE_HALF_OPENED)
+ whenever(mPostureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
mKeyguardPatternViewController.onViewAttached()
@@ -159,39 +158,37 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
}
- @Test
- fun withFeatureFlagOn_oldMessage_isHidden() {
- fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
-
- mKeyguardPatternViewController.onViewAttached()
-
- verify<KeyguardMessageAreaController<*>>(mKeyguardMessageAreaController).disable()
- }
-
- @Test
- fun onPause_resetsText() {
- mKeyguardPatternViewController.init()
- mKeyguardPatternViewController.onPause()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
- }
-
- @Test
- fun startAppearAnimation() {
- mKeyguardPatternViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController)
- .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false)
- }
-
- @Test
- fun startAppearAnimation_withExistingMessage() {
- `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- mKeyguardPatternViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
- }
-
- @Test
- fun resume() {
- mKeyguardPatternViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
- }
+ @Test
+ fun withFeatureFlagOn_oldMessage_isHidden() {
+ fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+
+ mKeyguardPatternViewController.onViewAttached()
+
+ verify<KeyguardMessageAreaController<*>>(mKeyguardMessageAreaController).disable()
+ }
+
+ @Test
+ fun onPause_resetsText() {
+ mKeyguardPatternViewController.init()
+ mKeyguardPatternViewController.onPause()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
+ }
+
+ @Test
+ fun testOnViewAttached() {
+ reset(mKeyguardMessageAreaController)
+ reset(mLockPatternUtils)
+ mKeyguardPatternViewController.onViewAttached()
+ verify(mKeyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false)
+ verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
+ }
+
+ @Test
+ fun testOnViewAttached_withExistingMessage() {
+ reset(mKeyguardMessageAreaController)
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ mKeyguardPatternViewController.onViewAttached()
+ verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index cf86c2192352..efe1955595ca 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -100,6 +100,8 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
.thenReturn(mDeleteButton);
when(mPinBasedInputView.findViewById(R.id.key_enter))
.thenReturn(mOkButton);
+
+ when(mPinBasedInputView.getResources()).thenReturn(getContext().getResources());
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 309d9e084ac8..80fd7213778e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -185,27 +185,27 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
}
@Test
- fun startAppearAnimation() {
+ fun testOnViewAttached() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
- pinViewController.startAppearAnimation()
+ pinViewController.onViewAttached()
verify(keyguardMessageAreaController)
.setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
- fun startAppearAnimation_withExistingMessage() {
+ fun testOnViewAttached_withExistingMessage() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- pinViewController.startAppearAnimation()
+ pinViewController.onViewAttached()
verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
}
@Test
- fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
+ fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
@@ -213,7 +213,7 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
`when`(passwordTextView.text).thenReturn("")
- pinViewController.startAppearAnimation()
+ pinViewController.onViewAttached()
verify(deleteButton).visibility = View.INVISIBLE
verify(enterButton).visibility = View.INVISIBLE
@@ -222,7 +222,7 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
}
@Test
- fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+ fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
@@ -230,7 +230,7 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
`when`(passwordTextView.text).thenReturn("")
- pinViewController.startAppearAnimation()
+ pinViewController.onViewAttached()
verify(deleteButton).visibility = View.VISIBLE
verify(enterButton).visibility = View.VISIBLE
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 0192e78dc09d..ad4bd584b5fc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -58,12 +58,15 @@ import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.GlobalSettings
@@ -133,6 +136,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
@Mock private lateinit var audioManager: AudioManager
@Mock private lateinit var userInteractor: UserInteractor
@Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Captor
private lateinit var swipeListenerArgumentCaptor:
@@ -182,6 +186,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
whenever(keyguardPasswordView.windowInsetsController).thenReturn(windowInsetsController)
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN)
whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
+ whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
featureFlags = FakeFeatureFlags()
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
@@ -249,6 +254,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
mock(),
{ JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
userInteractor,
+ deviceProvisionedController,
faceAuthAccessibilityDelegate,
keyguardTransitionInteractor
) {
@@ -506,6 +512,30 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
// THEN the next security method of None will dismiss keyguard.
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
}
+ @Test
+ fun showNextSecurityScreenOrFinish_SimPin_Swipe_userNotSetup() {
+ // GIVEN the current security method is SimPin
+ whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+ .thenReturn(false)
+ underTest.showSecurityScreen(SecurityMode.SimPin)
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+ .thenReturn(SecurityMode.None)
+ // WHEN security method is SWIPE
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+ whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false)
+ underTest.showNextSecurityScreenOrFinish(
+ /* authenticated= */ true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */ true,
+ SecurityMode.SimPin
+ )
+
+ // THEN the next security method of None will dismiss keyguard.
+ verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+ }
@Test
fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
@@ -578,18 +608,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
underTest.onViewAttached()
verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
- clearInvocations(viewFlipperController)
configurationListenerArgumentCaptor.value.onThemeChanged()
- verify(viewFlipperController).clearViews()
- verify(viewFlipperController)
- .asynchronouslyInflateView(
- eq(SecurityMode.PIN),
- any(),
- onViewInflatedCallbackArgumentCaptor.capture()
- )
- onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
- verify(view).reset()
- verify(viewFlipperController).reset()
verify(view).reloadColors()
}
@@ -599,16 +618,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
underTest.onViewAttached()
verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
- clearInvocations(viewFlipperController)
configurationListenerArgumentCaptor.value.onUiModeChanged()
- verify(viewFlipperController).clearViews()
- verify(viewFlipperController)
- .asynchronouslyInflateView(
- eq(SecurityMode.PIN),
- any(),
- onViewInflatedCallbackArgumentCaptor.capture()
- )
- onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
verify(view).reloadColors()
}
@@ -847,6 +857,17 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
verify(userSwitcher).setAlpha(0f)
}
+ @Test
+ fun testOnUserSwitched() {
+ val userSwitchCallbackArgumentCaptor =
+ argumentCaptor<UserSwitcherController.UserSwitchCallback>()
+ underTest.onViewAttached()
+ verify(userSwitcherController)
+ .addUserSwitchCallback(capture(userSwitchCallbackArgumentCaptor))
+ userSwitchCallbackArgumentCaptor.value.onUserSwitched()
+ verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any())
+ }
+
private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
get() {
underTest.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index a3acc781f2a7..291dda256c4f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -97,6 +97,8 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
@Test
fun onViewAttached() {
underTest.onViewAttached()
+ verify(keyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
@@ -120,8 +122,6 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
@Test
fun startAppearAnimation() {
underTest.startAppearAnimation()
- verify(keyguardMessageAreaController)
- .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index efcf4ddb5c71..626faa601970 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -98,6 +98,8 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
underTest.onViewAttached()
Mockito.verify(keyguardUpdateMonitor)
.registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
+ Mockito.verify(keyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
@@ -120,8 +122,6 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
@Test
fun startAppearAnimation() {
underTest.startAppearAnimation()
- Mockito.verify(keyguardMessageAreaController)
- .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
index 362d26b040e8..cf4e2c319ac9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.biometrics;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
@@ -28,6 +29,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
@@ -86,6 +88,9 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase {
@Test
public void testFingerprintReEnrollDialog_onRemovalSucceeded() {
+ assumeTrue(getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
+
mDialogFactory.createReenrollDialog(mContextSpy, mDialog,
BiometricSourceType.FINGERPRINT);
@@ -109,6 +114,9 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase {
@Test
public void testFingerprintReEnrollDialog_onRemovalError() {
+ assumeTrue(getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
+
mDialogFactory.createReenrollDialog(mContextSpy, mDialog,
BiometricSourceType.FINGERPRINT);
@@ -130,6 +138,9 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase {
@Test
public void testFaceReEnrollDialog_onRemovalSucceeded() {
+ assumeTrue(getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_FACE));
+
mDialogFactory.createReenrollDialog(mContextSpy, mDialog,
BiometricSourceType.FACE);
@@ -153,6 +164,9 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase {
@Test
public void testFaceReEnrollDialog_onRemovalError() {
+ assumeTrue(getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_FACE));
+
mDialogFactory.createReenrollDialog(mContextSpy, mDialog,
BiometricSourceType.FACE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt
index efae3fe1af2c..8eb274a65bc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt
@@ -24,13 +24,12 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.StringSubject
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -39,13 +38,12 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class BouncerMessageFactoryTest : SysuiTestCase() {
private lateinit var underTest: BouncerMessageFactory
- @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
@Mock private lateinit var securityModel: KeyguardSecurityModel
@@ -55,7 +53,8 @@ class BouncerMessageFactoryTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
testScope = TestScope()
- underTest = BouncerMessageFactory(updateMonitor, securityModel)
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+ underTest = BouncerMessageFactory(biometricSettingsRepository, securityModel)
}
@Test
@@ -167,7 +166,7 @@ class BouncerMessageFactoryTest : SysuiTestCase() {
secondaryMessageOverride: String? = null,
): BouncerMessageModel? {
whenever(securityModel.getSecurityMode(0)).thenReturn(mode)
- whenever(updateMonitor.isUnlockingWithFingerprintAllowed).thenReturn(fpAuthAllowed)
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(fpAuthAllowed)
return underTest.createFromPromptReason(
reason,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt
index 2be7d8a43a18..562a8ef512d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt
@@ -101,14 +101,15 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
fingerprintRepository = FakeDeviceEntryFingerprintAuthRepository()
testScope = TestScope()
- whenever(updateMonitor.isUnlockingWithFingerprintAllowed).thenReturn(false)
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
underTest =
BouncerMessageRepositoryImpl(
trustRepository = trustRepository,
biometricSettingsRepository = biometricSettingsRepository,
updateMonitor = updateMonitor,
- bouncerMessageFactory = BouncerMessageFactory(updateMonitor, securityModel),
+ bouncerMessageFactory =
+ BouncerMessageFactory(biometricSettingsRepository, securityModel),
userRepository = userRepository,
fingerprintAuthRepository = fingerprintRepository,
systemPropertiesHelper = systemPropertiesHelper
@@ -222,8 +223,7 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
whenever(systemPropertiesHelper.get("sys.boot.reason.last"))
.thenReturn("reboot,mainline_update")
userRepository.setSelectedUserInfo(PRIMARY_USER)
- biometricSettingsRepository.setFaceEnrolled(true)
- biometricSettingsRepository.setIsFaceAuthEnabled(true)
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
verifyMessagesForAuthFlag(
STRONG_AUTH_REQUIRED_AFTER_BOOT to
@@ -236,8 +236,8 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
trustRepository.setCurrentUserTrustManaged(false)
- biometricSettingsRepository.setFaceEnrolled(false)
- biometricSettingsRepository.setFingerprintEnrolled(false)
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
verifyMessagesForAuthFlag(
STRONG_AUTH_NOT_REQUIRED to null,
@@ -258,8 +258,8 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
fun authFlagsChanges_withTrustManaged_providesDifferentMessages() =
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
- biometricSettingsRepository.setFaceEnrolled(false)
- biometricSettingsRepository.setFingerprintEnrolled(false)
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
trustRepository.setCurrentUserTrustManaged(true)
@@ -290,10 +290,9 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
trustRepository.setCurrentUserTrustManaged(false)
- biometricSettingsRepository.setFingerprintEnrolled(false)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- biometricSettingsRepository.setIsFaceAuthEnabled(true)
- biometricSettingsRepository.setFaceEnrolled(true)
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
verifyMessagesForAuthFlag(
STRONG_AUTH_NOT_REQUIRED to null,
@@ -320,11 +319,9 @@ class BouncerMessageRepositoryTest : SysuiTestCase() {
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
trustRepository.setCurrentUserTrustManaged(false)
- biometricSettingsRepository.setIsFaceAuthEnabled(false)
- biometricSettingsRepository.setFaceEnrolled(false)
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- biometricSettingsRepository.setFingerprintEnrolled(true)
- biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
verifyMessagesForAuthFlag(
STRONG_AUTH_NOT_REQUIRED to null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index 38e5728f6e70..0d172706b127 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -94,9 +94,9 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
}
@Test
- fun canShowAlternateBouncerForFingerprint_noFingerprintsEnrolled() {
+ fun canShowAlternateBouncerForFingerprint_ifFingerprintIsNotUsuallyAllowed() {
givenCanShowAlternateBouncer()
- biometricSettingsRepository.setFingerprintEnrolled(false)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -104,15 +104,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
@Test
fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
givenCanShowAlternateBouncer()
- biometricSettingsRepository.setStrongBiometricAllowed(false)
-
- assertFalse(underTest.canShowAlternateBouncerForFingerprint())
- }
-
- @Test
- fun canShowAlternateBouncerForFingerprint_devicePolicyDoesNotAllowFingerprint() {
- givenCanShowAlternateBouncer()
- biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(false)
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -189,14 +181,13 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
private fun givenCanShowAlternateBouncer() {
bouncerRepository.setAlternateBouncerUIAvailable(true)
- biometricSettingsRepository.setFingerprintEnrolled(true)
- biometricSettingsRepository.setStrongBiometricAllowed(true)
- biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
whenever(keyguardStateController.isUnlocked).thenReturn(false)
}
private fun givenCannotShowAlternateBouncer() {
- biometricSettingsRepository.setFingerprintEnrolled(false)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
}
}
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 3ca94aa8e7af..4089abef399b 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
@@ -22,9 +22,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.R.string.keyguard_enter_pin
import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
+import com.android.systemui.R.string.kg_unlock_with_pin_or_fp
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
@@ -34,11 +33,11 @@ import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -49,14 +48,13 @@ import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
class BouncerMessageInteractorTest : SysuiTestCase() {
@Mock private lateinit var securityModel: KeyguardSecurityModel
- @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
@Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
private lateinit var countDownTimerCallback: KotlinArgumentCaptor<CountDownTimerCallback>
private lateinit var underTest: BouncerMessageInteractor
@@ -73,10 +71,11 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
userRepository.setUserInfos(listOf(PRIMARY_USER))
testScope = TestScope()
countDownTimerCallback = KotlinArgumentCaptor(CountDownTimerCallback::class.java)
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
allowTestableLooperAsMainThread()
whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
- whenever(updateMonitor.isUnlockingWithFingerprintAllowed).thenReturn(false)
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
}
suspend fun TestScope.init() {
@@ -86,7 +85,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
underTest =
BouncerMessageInteractor(
repository = repository,
- factory = BouncerMessageFactory(updateMonitor, securityModel),
+ factory = BouncerMessageFactory(biometricSettingsRepository, securityModel),
userRepository = userRepository,
countDownTimerUtil = countDownTimerUtil,
featureFlags = featureFlags
@@ -151,7 +150,8 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
underTest.setCustomMessage("not empty")
val customMessage = repository.customMessage
- assertThat(customMessage.value!!.message!!.messageResId).isEqualTo(keyguard_enter_pin)
+ assertThat(customMessage.value!!.message!!.messageResId)
+ .isEqualTo(kg_unlock_with_pin_or_fp)
assertThat(customMessage.value!!.secondaryMessage!!.message).isEqualTo("not empty")
underTest.setCustomMessage(null)
@@ -168,7 +168,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
val faceAcquisitionMessage = repository.faceAcquisitionMessage
assertThat(faceAcquisitionMessage.value!!.message!!.messageResId)
- .isEqualTo(keyguard_enter_pin)
+ .isEqualTo(kg_unlock_with_pin_or_fp)
assertThat(faceAcquisitionMessage.value!!.secondaryMessage!!.message)
.isEqualTo("not empty")
@@ -186,7 +186,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
val fingerprintAcquisitionMessage = repository.fingerprintAcquisitionMessage
assertThat(fingerprintAcquisitionMessage.value!!.message!!.messageResId)
- .isEqualTo(keyguard_enter_pin)
+ .isEqualTo(kg_unlock_with_pin_or_fp)
assertThat(fingerprintAcquisitionMessage.value!!.secondaryMessage!!.message)
.isEqualTo("not empty")
@@ -275,7 +275,8 @@ class BouncerMessageInteractorTest : SysuiTestCase() {
repository.setBiometricLockedOutMessage(null)
// sets the default message if everything else is null
- assertThat(bouncerMessage()!!.message!!.messageResId).isEqualTo(keyguard_enter_pin)
+ assertThat(bouncerMessage()!!.message!!.messageResId)
+ .isEqualTo(kg_unlock_with_pin_or_fp)
}
private fun message(value: String): BouncerMessageModel {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
index 7628be44755d..662c89c268e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
@@ -80,6 +80,7 @@ public class IntentCreatorTest extends SysuiTestCase {
assertEquals(Intent.ACTION_EDIT, intent.getAction());
assertEquals("image/*", intent.getType());
assertEquals(null, intent.getComponent());
+ assertEquals("clipboard", intent.getStringExtra("edit_source"));
assertFlags(intent, EXTERNAL_INTENT_FLAGS);
// try again with an editor component
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 692d794f21f0..8416c46a3f38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -18,12 +18,14 @@ package com.android.systemui.controls.ui
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import android.view.HapticFeedbackConstants
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -33,6 +35,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyBoolean
@@ -68,8 +71,6 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
@Mock
private lateinit var metricsLogger: ControlsMetricsLogger
@Mock
- private lateinit var featureFlags: FeatureFlags
- @Mock
private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
companion object {
@@ -82,6 +83,8 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
private lateinit var action: ControlActionCoordinatorImpl.Action
private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
+ private val featureFlags = FakeFeatureFlags()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -101,6 +104,7 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
metricsLogger,
vibratorHelper,
controlsSettingsRepository,
+ featureFlags
))
coordinator.activityContext = mContext
@@ -194,4 +198,50 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
verify(coordinator).bouncerOrRun(action)
verify(action, never()).invoke()
}
+
+ @Test
+ fun drag_isEdge_oneWayHapticsDisabled_usesVibrate() {
+ featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
+
+ coordinator.drag(cvh, true)
+
+ verify(vibratorHelper).vibrate(
+ Vibrations.rangeEdgeEffect
+ )
+ }
+
+ @Test
+ fun drag_isNotEdge_oneWayHapticsDisabled_usesVibrate() {
+ featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
+
+ coordinator.drag(cvh, false)
+
+ verify(vibratorHelper).vibrate(
+ Vibrations.rangeMiddleEffect
+ )
+ }
+
+ @Test
+ fun drag_isEdge_oneWayHapticsEnabled_usesPerformHapticFeedback() {
+ featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+
+ coordinator.drag(cvh, true)
+
+ verify(vibratorHelper).performHapticFeedback(
+ any(),
+ eq(HapticFeedbackConstants.SEGMENT_TICK)
+ )
+ }
+
+ @Test
+ fun drag_isNotEdge_oneWayHapticsEnabled_usesPerformHapticFeedback() {
+ featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+
+ coordinator.drag(cvh, false)
+
+ verify(vibratorHelper).performHapticFeedback(
+ any(),
+ eq(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK)
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
index 07cb5d88a515..6a178895839b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -22,17 +22,14 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
-import com.android.internal.app.AssistUtils;
-import com.android.internal.app.IVisualQueryDetectionAttentionListener;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener;
import com.android.systemui.shared.condition.Condition;
import org.junit.Before;
@@ -50,9 +47,7 @@ public class AssistantAttentionConditionTest extends SysuiTestCase {
@Mock
Condition.Callback mCallback;
@Mock
- AssistUtils mAssistUtils;
- @Mock
- DreamOverlayStateController mDreamOverlayStateController;
+ AssistManager mAssistManager;
@Mock
CoroutineScope mScope;
@@ -62,55 +57,34 @@ public class AssistantAttentionConditionTest extends SysuiTestCase {
public void setup() {
MockitoAnnotations.initMocks(this);
- mAssistantAttentionCondition =
- new AssistantAttentionCondition(mScope, mDreamOverlayStateController, mAssistUtils);
+ mAssistantAttentionCondition = new AssistantAttentionCondition(mScope, mAssistManager);
// Adding a callback also starts the condition.
mAssistantAttentionCondition.addCallback(mCallback);
}
@Test
public void testEnableVisualQueryDetection() {
- final ArgumentCaptor<DreamOverlayStateController.Callback> argumentCaptor =
- ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
- verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
-
- when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
- argumentCaptor.getValue().onStateChanged();
-
- verify(mAssistUtils).enableVisualQueryDetection(any());
+ verify(mAssistManager).addVisualQueryAttentionListener(
+ any(VisualQueryAttentionListener.class));
}
@Test
public void testDisableVisualQueryDetection() {
- final ArgumentCaptor<DreamOverlayStateController.Callback> argumentCaptor =
- ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
- verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
-
- when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
- argumentCaptor.getValue().onStateChanged();
- when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(false);
- argumentCaptor.getValue().onStateChanged();
-
- verify(mAssistUtils).disableVisualQueryDetection();
+ mAssistantAttentionCondition.stop();
+ verify(mAssistManager).removeVisualQueryAttentionListener(
+ any(VisualQueryAttentionListener.class));
}
@Test
- public void testAttentionChangedTriggersCondition() throws RemoteException {
- final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
- ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
- verify(mDreamOverlayStateController).addCallback(callbackCaptor.capture());
-
- when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
- callbackCaptor.getValue().onStateChanged();
-
- final ArgumentCaptor<IVisualQueryDetectionAttentionListener> listenerCaptor =
- ArgumentCaptor.forClass(IVisualQueryDetectionAttentionListener.class);
- verify(mAssistUtils).enableVisualQueryDetection(listenerCaptor.capture());
+ public void testAttentionChangedTriggersCondition() {
+ final ArgumentCaptor<VisualQueryAttentionListener> argumentCaptor =
+ ArgumentCaptor.forClass(VisualQueryAttentionListener.class);
+ verify(mAssistManager).addVisualQueryAttentionListener(argumentCaptor.capture());
- listenerCaptor.getValue().onAttentionGained();
+ argumentCaptor.getValue().onAttentionGained();
assertThat(mAssistantAttentionCondition.isConditionMet()).isTrue();
- listenerCaptor.getValue().onAttentionLost();
+ argumentCaptor.getValue().onAttentionLost();
assertThat(mAssistantAttentionCondition.isConditionMet()).isFalse();
verify(mCallback, times(2)).onConditionChanged(eq(mAssistantAttentionCondition));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
new file mode 100644
index 000000000000..632d149c9520
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyEventInteractorTest : SysuiTestCase() {
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ private lateinit var keyguardInteractorWithDependencies:
+ KeyguardInteractorFactory.WithDependencies
+ @Mock private lateinit var keyguardKeyEventInteractor: KeyguardKeyEventInteractor
+ @Mock private lateinit var backActionInteractor: BackActionInteractor
+
+ private lateinit var underTest: KeyEventInteractor
+
+ @Before
+ fun setup() {
+ keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+ underTest =
+ KeyEventInteractor(
+ backActionInteractor,
+ keyguardKeyEventInteractor,
+ )
+ }
+
+ @Test
+ fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() {
+ val backKeyEventActionDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)
+ val backKeyEventActionUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+
+ // GIVEN back key ACTION_DOWN and ACTION_UP aren't handled by the keyguardKeyEventInteractor
+ whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionDown))
+ .thenReturn(false)
+ whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionUp))
+ .thenReturn(false)
+
+ // WHEN back key event ACTION_DOWN, the event is handled even though back isn't requested
+ assertThat(underTest.dispatchKeyEvent(backKeyEventActionDown)).isTrue()
+ // THEN back event isn't handled on ACTION_DOWN
+ verify(backActionInteractor, never()).onBackRequested()
+
+ // WHEN back key event ACTION_UP
+ assertThat(underTest.dispatchKeyEvent(backKeyEventActionUp)).isTrue()
+ // THEN back event is handled on ACTION_UP
+ verify(backActionInteractor).onBackRequested()
+ }
+
+ @Test
+ fun dispatchKeyEvent_isNotHandledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(false)
+ assertThat(underTest.dispatchKeyEvent(keyEvent)).isFalse()
+ }
+
+ @Test
+ fun dispatchKeyEvent_handledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(true)
+ assertThat(underTest.dispatchKeyEvent(keyEvent)).isTrue()
+ }
+
+ @Test
+ fun interceptMediaKey_isNotHandledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(false)
+ assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
+ }
+
+ @Test
+ fun interceptMediaKey_handledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(true)
+ assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_isNotHandledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(false)
+ assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isFalse()
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_handledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(true)
+ assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index c6a2fa50b446..a6930d595326 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -36,6 +36,11 @@ import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FaceSensorInfo
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.BiometricType.FACE
@@ -63,6 +68,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -89,6 +95,8 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
ArgumentCaptor<IBiometricEnabledOnKeyguardCallback.Stub>
private lateinit var userRepository: FakeUserRepository
private lateinit var devicePostureRepository: FakeDevicePostureRepository
+ private lateinit var facePropertyRepository: FakeFacePropertyRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
@@ -102,6 +110,8 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
testScope = TestScope(testDispatcher)
userRepository = FakeUserRepository()
devicePostureRepository = FakeDevicePostureRepository()
+ facePropertyRepository = FakeFacePropertyRepository()
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
}
private suspend fun createBiometricSettingsRepository() {
@@ -120,74 +130,110 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
biometricManager = biometricManager,
devicePostureRepository = devicePostureRepository,
dumpManager = dumpManager,
+ facePropertyRepository = facePropertyRepository,
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
)
testScope.runCurrent()
+ fingerprintPropertyRepository.setProperties(
+ 1,
+ SensorStrength.STRONG,
+ FingerprintSensorType.UDFPS_OPTICAL,
+ emptyMap()
+ )
verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
+ verify(authController, atLeastOnce()).addCallback(authControllerCallback.capture())
}
@Test
fun fingerprintEnrollmentChange() =
testScope.runTest {
createBiometricSettingsRepository()
- val fingerprintEnrolled = collectLastValue(underTest.isFingerprintEnrolled)
+ val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
runCurrent()
- verify(authController).addCallback(authControllerCallback.capture())
whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(true)
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
- assertThat(fingerprintEnrolled()).isTrue()
+ assertThat(fingerprintAllowed()).isTrue()
whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(false)
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, ANOTHER_USER_ID, false)
- assertThat(fingerprintEnrolled()).isTrue()
+ assertThat(fingerprintAllowed()).isTrue()
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, false)
- assertThat(fingerprintEnrolled()).isFalse()
+ assertThat(fingerprintAllowed()).isFalse()
}
@Test
fun strongBiometricAllowedChange() =
testScope.runTest {
+ fingerprintIsEnrolled()
+ doNotDisableKeyguardAuthFeatures()
createBiometricSettingsRepository()
- val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed)
+
+ val strongBiometricAllowed by
+ collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
runCurrent()
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
- assertThat(strongBiometricAllowed()).isTrue()
+ assertThat(strongBiometricAllowed).isTrue()
onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID)
- assertThat(strongBiometricAllowed()).isFalse()
+ assertThat(strongBiometricAllowed).isFalse()
}
@Test
fun convenienceBiometricAllowedChange() =
testScope.runTest {
overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false)
+ deviceIsInPostureThatSupportsFaceAuth()
+ faceAuthIsEnrolled()
+ faceAuthIsNonStrongBiometric()
createBiometricSettingsRepository()
- val convenienceBiometricAllowed =
- collectLastValue(underTest.isNonStrongBiometricAllowed)
- runCurrent()
+ val convenienceFaceAuthAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
+ doNotDisableKeyguardAuthFeatures()
+ faceAuthIsEnabledByBiometricManager()
+
+ onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
- assertThat(convenienceBiometricAllowed()).isTrue()
+ runCurrent()
+ assertThat(convenienceFaceAuthAllowed).isTrue()
onNonStrongAuthChanged(false, ANOTHER_USER_ID)
- assertThat(convenienceBiometricAllowed()).isTrue()
+ assertThat(convenienceFaceAuthAllowed).isTrue()
onNonStrongAuthChanged(false, PRIMARY_USER_ID)
- assertThat(convenienceBiometricAllowed()).isFalse()
+ assertThat(convenienceFaceAuthAllowed).isFalse()
mContext.orCreateTestableResources.removeOverride(
com.android.internal.R.bool.config_strongAuthRequiredOnBoot
)
}
+ private fun faceAuthIsNonStrongBiometric() {
+ facePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.CONVENIENCE))
+ }
+
+ private fun faceAuthIsStrongBiometric() {
+ facePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.STRONG))
+ }
+
+ private fun deviceIsInPostureThatSupportsFaceAuth() {
+ overrideResource(
+ R.integer.config_face_auth_supported_posture,
+ DevicePostureController.DEVICE_POSTURE_FLIPPED
+ )
+ devicePostureRepository.setCurrentPosture(DevicePosture.FLIPPED)
+ }
+
@Test
fun whenStrongAuthRequiredAfterBoot_nonStrongBiometricNotAllowed() =
testScope.runTest {
overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, true)
createBiometricSettingsRepository()
+ faceAuthIsNonStrongBiometric()
+ faceAuthIsEnrolled()
+ doNotDisableKeyguardAuthFeatures()
- val convenienceBiometricAllowed =
- collectLastValue(underTest.isNonStrongBiometricAllowed)
+ val convenienceBiometricAllowed = collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
runCurrent()
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
@@ -201,16 +247,24 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
fun whenStrongBiometricAuthIsNotAllowed_nonStrongBiometrics_alsoNotAllowed() =
testScope.runTest {
overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false)
+ faceAuthIsNonStrongBiometric()
+ deviceIsInPostureThatSupportsFaceAuth()
+ faceAuthIsEnrolled()
createBiometricSettingsRepository()
-
- val convenienceBiometricAllowed =
- collectLastValue(underTest.isNonStrongBiometricAllowed)
+ doNotDisableKeyguardAuthFeatures()
+ faceAuthIsEnabledByBiometricManager()
runCurrent()
+
+ val convenienceBiometricAllowed by
+ collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
+
+ onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
- assertThat(convenienceBiometricAllowed()).isTrue()
+ runCurrent()
+ assertThat(convenienceBiometricAllowed).isTrue()
onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, PRIMARY_USER_ID)
- assertThat(convenienceBiometricAllowed()).isFalse()
+ assertThat(convenienceBiometricAllowed).isFalse()
mContext.orCreateTestableResources.removeOverride(
com.android.internal.R.bool.config_strongAuthRequiredOnBoot
)
@@ -229,9 +283,11 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
@Test
fun fingerprintDisabledByDpmChange() =
testScope.runTest {
+ fingerprintIsEnrolled(PRIMARY_USER_ID)
createBiometricSettingsRepository()
+
val fingerprintEnabledByDevicePolicy =
- collectLastValue(underTest.isFingerprintEnabledByDevicePolicy)
+ collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
runCurrent()
whenever(devicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
@@ -244,43 +300,57 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
assertThat(fingerprintEnabledByDevicePolicy()).isTrue()
}
+ private fun fingerprintIsEnrolled(userId: Int = PRIMARY_USER_ID) {
+ whenever(authController.isFingerprintEnrolled(userId)).thenReturn(true)
+ }
+
@Test
fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() =
testScope.runTest {
createBiometricSettingsRepository()
+ faceAuthIsEnabledByBiometricManager()
+
+ doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
+
runCurrent()
clearInvocations(authController)
whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
- val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
+ val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
- assertThat(faceEnrolled()).isFalse()
+ assertThat(faceAuthAllowed()).isFalse()
verify(authController).addCallback(authControllerCallback.capture())
enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true)
- assertThat(faceEnrolled()).isFalse()
+ assertThat(faceAuthAllowed()).isFalse()
enrollmentChange(SIDE_FINGERPRINT, PRIMARY_USER_ID, true)
- assertThat(faceEnrolled()).isFalse()
+ assertThat(faceAuthAllowed()).isFalse()
enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
- assertThat(faceEnrolled()).isFalse()
+ assertThat(faceAuthAllowed()).isFalse()
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
enrollmentChange(FACE, ANOTHER_USER_ID, true)
- assertThat(faceEnrolled()).isFalse()
+ assertThat(faceAuthAllowed()).isFalse()
- whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(true)
+ faceAuthIsEnrolled()
enrollmentChange(FACE, PRIMARY_USER_ID, true)
- assertThat(faceEnrolled()).isTrue()
+ assertThat(faceAuthAllowed()).isTrue()
}
+ private fun faceAuthIsEnabledByBiometricManager(userId: Int = PRIMARY_USER_ID) {
+ verify(biometricManager, atLeastOnce())
+ .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
+ biometricManagerCallback.value.onChanged(true, userId)
+ }
+
@Test
fun faceEnrollmentStatusOfNewUserUponUserSwitch() =
testScope.runTest {
@@ -290,21 +360,26 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
- val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
+ val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
- assertThat(faceEnrolled()).isFalse()
+ assertThat(faceAuthAllowed()).isFalse()
}
@Test
fun faceEnrollmentChangesArePropagatedAfterUserSwitch() =
testScope.runTest {
createBiometricSettingsRepository()
+ verify(biometricManager)
+ .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
userRepository.setSelectedUserInfo(ANOTHER_USER)
+ doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID)
+ biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
+
runCurrent()
clearInvocations(authController)
- val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
+ val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
runCurrent()
verify(authController).addCallback(authControllerCallback.capture())
@@ -312,12 +387,14 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
enrollmentChange(FACE, ANOTHER_USER_ID, true)
- assertThat(faceEnrolled()).isTrue()
+ assertThat(faceAuthAllowed()).isTrue()
}
@Test
fun devicePolicyControlsFaceAuthenticationEnabledState() =
testScope.runTest {
+ faceAuthIsEnrolled()
+
createBiometricSettingsRepository()
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
@@ -325,62 +402,70 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
.thenReturn(KEYGUARD_DISABLE_FINGERPRINT or KEYGUARD_DISABLE_FACE)
- val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
+ val isFaceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
runCurrent()
broadcastDPMStateChange()
- assertThat(isFaceAuthEnabled()).isFalse()
+ assertThat(isFaceAuthAllowed()).isFalse()
biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
runCurrent()
- assertThat(isFaceAuthEnabled()).isFalse()
+ assertThat(isFaceAuthAllowed()).isFalse()
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
.thenReturn(KEYGUARD_DISABLE_FINGERPRINT)
broadcastDPMStateChange()
- assertThat(isFaceAuthEnabled()).isTrue()
+ assertThat(isFaceAuthAllowed()).isTrue()
}
@Test
fun biometricManagerControlsFaceAuthenticationEnabledStatus() =
testScope.runTest {
+ faceAuthIsEnrolled()
+
createBiometricSettingsRepository()
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
.thenReturn(0)
broadcastDPMStateChange()
- val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
+ val isFaceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
- assertThat(isFaceAuthEnabled()).isFalse()
+ assertThat(isFaceAuthAllowed()).isFalse()
// Value changes for another user
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
- assertThat(isFaceAuthEnabled()).isFalse()
+ assertThat(isFaceAuthAllowed()).isFalse()
// Value changes for current user.
biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
- assertThat(isFaceAuthEnabled()).isTrue()
+ assertThat(isFaceAuthAllowed()).isTrue()
}
+ private fun faceAuthIsEnrolled(userId: Int = PRIMARY_USER_ID) {
+ whenever(authController.isFaceAuthEnrolled(userId)).thenReturn(true)
+ }
+
@Test
fun userChange_biometricEnabledChange_handlesRaceCondition() =
testScope.runTest {
createBiometricSettingsRepository()
+ whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
+
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
- val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
+ val isFaceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
runCurrent()
userRepository.setSelectedUserInfo(ANOTHER_USER)
runCurrent()
- assertThat(isFaceAuthEnabled()).isTrue()
+ assertThat(isFaceAuthAllowed()).isTrue()
}
@Test
@@ -388,9 +473,9 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
testScope.runTest {
createBiometricSettingsRepository()
- collectLastValue(underTest.isFaceAuthenticationEnabled)()
- collectLastValue(underTest.isFaceAuthenticationEnabled)()
- collectLastValue(underTest.isFaceAuthenticationEnabled)()
+ collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)()
+ collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)()
+ collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)()
verify(biometricManager, times(1)).registerEnabledOnKeyguardCallback(any())
}
@@ -495,10 +580,138 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
assertThat(authFlags()!!.flag).isEqualTo(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
}
+ @Test
+ fun faceAuthCurrentlyAllowed_dependsOnStrongAuthBiometricSetting_ifFaceIsClass3() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
+
+ faceAuthIsEnrolled()
+ deviceIsInPostureThatSupportsFaceAuth()
+ doNotDisableKeyguardAuthFeatures()
+ faceAuthIsStrongBiometric()
+ faceAuthIsEnabledByBiometricManager()
+
+ onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+ onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+
+ assertThat(isFaceAuthCurrentlyAllowed).isTrue()
+
+ onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID)
+ onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+
+ assertThat(isFaceAuthCurrentlyAllowed).isFalse()
+ }
+
+ @Test
+ fun faceAuthCurrentlyAllowed_dependsOnNonStrongAuthBiometricSetting_ifFaceIsNotStrong() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
+
+ faceAuthIsEnrolled()
+ deviceIsInPostureThatSupportsFaceAuth()
+ doNotDisableKeyguardAuthFeatures()
+ faceAuthIsNonStrongBiometric()
+ faceAuthIsEnabledByBiometricManager()
+
+ onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+ onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+
+ assertThat(isFaceAuthCurrentlyAllowed).isFalse()
+
+ onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID)
+ onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+
+ assertThat(isFaceAuthCurrentlyAllowed).isFalse()
+
+ onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+ onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+
+ assertThat(isFaceAuthCurrentlyAllowed).isTrue()
+ }
+
+ @Test
+ fun fpAuthCurrentlyAllowed_dependsOnNonStrongAuthBiometricSetting_ifFpIsNotStrong() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ val isFingerprintCurrentlyAllowed by
+ collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+
+ fingerprintIsEnrolled(PRIMARY_USER_ID)
+ enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
+ doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
+ runCurrent()
+
+ fingerprintPropertyRepository.setProperties(
+ 1,
+ SensorStrength.STRONG,
+ FingerprintSensorType.UDFPS_OPTICAL,
+ emptyMap()
+ )
+ // Non strong auth is not allowed now, FP is marked strong
+ onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+ onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+
+ assertThat(isFingerprintCurrentlyAllowed).isTrue()
+
+ fingerprintPropertyRepository.setProperties(
+ 1,
+ SensorStrength.CONVENIENCE,
+ FingerprintSensorType.UDFPS_OPTICAL,
+ emptyMap()
+ )
+ assertThat(isFingerprintCurrentlyAllowed).isFalse()
+
+ fingerprintPropertyRepository.setProperties(
+ 1,
+ SensorStrength.WEAK,
+ FingerprintSensorType.UDFPS_OPTICAL,
+ emptyMap()
+ )
+ assertThat(isFingerprintCurrentlyAllowed).isFalse()
+ }
+
+ @Test
+ fun fpAuthCurrentlyAllowed_dependsOnStrongAuthBiometricSetting_ifFpIsStrong() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ val isFingerprintCurrentlyAllowed by
+ collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+
+ fingerprintIsEnrolled(PRIMARY_USER_ID)
+ enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
+ doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
+ runCurrent()
+
+ fingerprintPropertyRepository.setProperties(
+ 1,
+ SensorStrength.STRONG,
+ FingerprintSensorType.UDFPS_OPTICAL,
+ emptyMap()
+ )
+ // Non strong auth is not allowed now, FP is marked strong
+ onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID)
+ onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+
+ assertThat(isFingerprintCurrentlyAllowed).isFalse()
+
+ onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+ onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+
+ assertThat(isFingerprintCurrentlyAllowed).isTrue()
+ }
+
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
}
+ private fun doNotDisableKeyguardAuthFeatures(userId: Int = PRIMARY_USER_ID) {
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(userId)))
+ .thenReturn(0)
+ broadcastDPMStateChange()
+ }
+
private fun broadcastDPMStateChange() {
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index fe5b8120428d..64b94707da57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -40,9 +40,7 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FaceSensorInfo
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.coroutines.FlowValue
@@ -255,7 +253,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthBuffer,
keyguardTransitionInteractor,
featureFlags,
- fakeFacePropertyRepository,
dumpManager,
)
}
@@ -525,15 +522,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
- fun authenticateDoesNotRunIfFaceIsNotEnrolled() =
+ fun authenticateDoesNotRunIfFaceIsNotUsuallyAllowed() =
testScope.runTest {
- testGatingCheckForFaceAuth { biometricSettingsRepository.setFaceEnrolled(false) }
- }
-
- @Test
- fun authenticateDoesNotRunIfFaceIsNotEnabled() =
- testScope.runTest {
- testGatingCheckForFaceAuth { biometricSettingsRepository.setIsFaceAuthEnabled(false) }
+ testGatingCheckForFaceAuth {
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ }
}
@Test
@@ -589,21 +582,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
- fun authenticateDoesNotRunWhenNonStrongBiometricIsNotAllowed() =
+ fun authenticateDoesNotRunWhenFaceAuthIsNotCurrentlyAllowedToRun() =
testScope.runTest {
testGatingCheckForFaceAuth {
- biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
- }
- }
-
- @Test
- fun authenticateDoesNotRunWhenStrongBiometricIsNotAllowedAndFaceSensorIsStrong() =
- testScope.runTest {
- fakeFacePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.STRONG))
- runCurrent()
-
- testGatingCheckForFaceAuth(isFaceStrong = true) {
- biometricSettingsRepository.setIsStrongBiometricAllowed(false)
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
}
}
@@ -662,7 +644,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
allPreconditionsToRunFaceAuthAreTrue()
// Flip one precondition to false.
- biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
assertThat(canFaceAuthRun()).isFalse()
underTest.authenticate(
FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
@@ -827,15 +809,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
- fun detectDoesNotRunWhenFaceIsNotEnrolled() =
- testScope.runTest {
- testGatingCheckForDetect { biometricSettingsRepository.setFaceEnrolled(false) }
- }
-
- @Test
- fun detectDoesNotRunWhenFaceIsNotEnabled() =
+ fun detectDoesNotRunWhenFaceIsNotUsuallyAllowed() =
testScope.runTest {
- testGatingCheckForDetect { biometricSettingsRepository.setIsFaceAuthEnabled(false) }
+ testGatingCheckForDetect {
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ }
}
@Test
@@ -932,23 +910,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
- fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
+ fun detectDoesNotRunWhenFaceAuthIsCurrentlyAllowedToRun() =
testScope.runTest {
testGatingCheckForDetect {
- biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
- }
- }
-
- @Test
- fun detectDoesNotRunWhenStrongBiometricIsAllowedAndFaceAuthSensorStrengthIsStrong() =
- testScope.runTest {
- fakeFacePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.STRONG))
- runCurrent()
-
- testGatingCheckForDetect(isFaceStrong = true) {
- biometricSettingsRepository.setIsStrongBiometricAllowed(true)
- // this shouldn't matter as face is set as a strong sensor
- biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
}
}
@@ -1043,12 +1008,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthenticateIsCalled()
}
- private suspend fun TestScope.testGatingCheckForFaceAuth(
- isFaceStrong: Boolean = false,
- gatingCheckModifier: () -> Unit
- ) {
+ private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) {
initCollectors()
- allPreconditionsToRunFaceAuthAreTrue(isFaceStrong)
+ allPreconditionsToRunFaceAuthAreTrue()
gatingCheckModifier()
runCurrent()
@@ -1057,7 +1019,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
assertThat(underTest.canRunFaceAuth.value).isFalse()
// flip the gating check back on.
- allPreconditionsToRunFaceAuthAreTrue(isFaceStrong)
+ allPreconditionsToRunFaceAuthAreTrue()
triggerFaceAuth(false)
@@ -1076,19 +1038,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthenticateIsNotCalled()
}
- private suspend fun TestScope.testGatingCheckForDetect(
- isFaceStrong: Boolean = false,
- gatingCheckModifier: () -> Unit
- ) {
+ private suspend fun TestScope.testGatingCheckForDetect(gatingCheckModifier: () -> Unit) {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
- if (isFaceStrong) {
- biometricSettingsRepository.setStrongBiometricAllowed(false)
- } else {
- // This will stop face auth from running but is required to be false for detect.
- biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
- }
+ // This will stop face auth from running but is required to be false for detect.
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
runCurrent()
assertThat(canFaceAuthRun()).isFalse()
@@ -1123,13 +1078,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
}
- private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue(
- isFaceStrong: Boolean = false
- ) {
+ private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
verify(faceManager, atLeastOnce())
.addLockoutResetCallback(faceLockoutResetCallback.capture())
- biometricSettingsRepository.setFaceEnrolled(true)
- biometricSettingsRepository.setIsFaceAuthEnabled(true)
underTest.resumeFaceAuth()
trustRepository.setCurrentUserTrusted(false)
keyguardRepository.setKeyguardGoingAway(false)
@@ -1140,14 +1091,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
WakeSleepReason.OTHER
)
)
- if (isFaceStrong) {
- biometricSettingsRepository.setStrongBiometricAllowed(true)
- } else {
- biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
- }
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
biometricSettingsRepository.setIsUserInLockdown(false)
fakeUserRepository.setSelectedUserInfo(primaryUser)
- biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
faceLockoutResetCallback.value.onLockoutReset(0)
bouncerRepository.setAlternateVisible(true)
keyguardRepository.setKeyguardShowing(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
new file mode 100644
index 000000000000..a3f7fc5fc8cf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -0,0 +1,255 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.media.AudioManager
+import android.media.session.MediaSessionLegacyHelper
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardKeyEventInteractorTest : SysuiTestCase() {
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ private val actionDownVolumeDownKeyEvent =
+ KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN)
+ private val actionDownVolumeUpKeyEvent =
+ KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
+ private val backKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+ private val awakeWakefulnessMode =
+ WakefulnessModel(WakefulnessState.AWAKE, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+ private val asleepWakefulnessMode =
+ WakefulnessModel(WakefulnessState.ASLEEP, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+
+ private lateinit var keyguardInteractorWithDependencies:
+ KeyguardInteractorFactory.WithDependencies
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock private lateinit var shadeController: ShadeController
+ @Mock private lateinit var mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper
+ @Mock private lateinit var mediaSessionLegacyHelper: MediaSessionLegacyHelper
+ @Mock private lateinit var backActionInteractor: BackActionInteractor
+
+ private lateinit var underTest: KeyguardKeyEventInteractor
+
+ @Before
+ fun setup() {
+ whenever(mediaSessionLegacyHelperWrapper.getHelper(any()))
+ .thenReturn(mediaSessionLegacyHelper)
+ keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+ underTest =
+ KeyguardKeyEventInteractor(
+ context,
+ statusBarStateController,
+ keyguardInteractorWithDependencies.keyguardInteractor,
+ statusBarKeyguardViewManager,
+ shadeController,
+ mediaSessionLegacyHelperWrapper,
+ backActionInteractor,
+ )
+ }
+
+ @Test
+ fun dispatchKeyEvent_volumeKey_dozing_handlesEvents() {
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+
+ assertThat(underTest.dispatchKeyEvent(actionDownVolumeDownKeyEvent)).isTrue()
+ verify(mediaSessionLegacyHelper)
+ .sendVolumeKeyEvent(
+ eq(actionDownVolumeDownKeyEvent),
+ eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+ eq(true)
+ )
+
+ assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isTrue()
+ verify(mediaSessionLegacyHelper)
+ .sendVolumeKeyEvent(
+ eq(actionDownVolumeUpKeyEvent),
+ eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+ eq(true)
+ )
+ }
+
+ @Test
+ fun dispatchKeyEvent_volumeKey_notDozing_doesNotHandleEvents() {
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+
+ assertThat(underTest.dispatchKeyEvent(actionDownVolumeDownKeyEvent)).isFalse()
+ verify(mediaSessionLegacyHelper, never())
+ .sendVolumeKeyEvent(
+ eq(actionDownVolumeDownKeyEvent),
+ eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+ eq(true)
+ )
+
+ assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isFalse()
+ verify(mediaSessionLegacyHelper, never())
+ .sendVolumeKeyEvent(
+ eq(actionDownVolumeUpKeyEvent),
+ eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+ eq(true)
+ )
+ }
+
+ @Test
+ fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+ verify(shadeController).animateCollapseShadeForced()
+ }
+
+ @Test
+ fun dispatchKeyEvent_menuActionUp_interactiveShadeLocked_collapsesShade() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+ whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+ // action down: does NOT collapse the shade
+ val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)
+ assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+
+ // action up: collapses the shade
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+ verify(shadeController).animateCollapseShadeForced()
+ }
+
+ @Test
+ fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(asleepWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+ }
+
+ @Test
+ fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ // action down: does NOT collapse the shade
+ val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE)
+ assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+
+ // action up: collapses the shade
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+ verify(shadeController).animateCollapseShadeForced()
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_back_keyguard_onBackRequested() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(true)
+
+ whenever(backActionInteractor.onBackRequested()).thenReturn(false)
+ assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+ verify(backActionInteractor).onBackRequested()
+ clearInvocations(backActionInteractor)
+
+ whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+ assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isTrue()
+ verify(backActionInteractor).onBackRequested()
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_back_keyguard_SBKVMdoesNotHandle_neverOnBackRequested() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(false)
+ whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+
+ assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+ verify(backActionInteractor, never()).onBackRequested()
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_back_shade_neverOnBackRequested() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+ whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(true)
+ whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+
+ assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+ verify(backActionInteractor, never()).onBackRequested()
+ }
+
+ @Test
+ fun interceptMediaKey_keyguard_SBKVMdoesNotHandle_doesNotHandleMediaKey() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.interceptMediaKey(eq(keyEvent))).thenReturn(false)
+
+ assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
+ verify(statusBarKeyguardViewManager).interceptMediaKey(eq(keyEvent))
+ }
+
+ @Test
+ fun interceptMediaKey_keyguard_handleMediaKey() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.interceptMediaKey(eq(keyEvent))).thenReturn(true)
+
+ assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
+ verify(statusBarKeyguardViewManager).interceptMediaKey(eq(keyEvent))
+ }
+
+ @Test
+ fun interceptMediaKey_shade_doesNotHandleMediaKey() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+ assertThat(
+ underTest.interceptMediaKey(
+ KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+ )
+ )
+ .isFalse()
+ verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 16cc924b5754..713c6027d642 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -147,16 +147,4 @@ class SceneInteractorTest : SysuiTestCase() {
underTest.setVisible(true, "reason")
assertThat(isVisible).isTrue()
}
-
- @Test
- fun remoteUserInput() =
- testScope.runTest {
- val remoteUserInput by collectLastValue(underTest.remoteUserInput)
- assertThat(remoteUserInput).isNull()
-
- for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) {
- underTest.onRemoteUserInput(input)
- assertThat(remoteUserInput).isEqualTo(input)
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index da6c42694666..88abb642f7c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -18,19 +18,14 @@
package com.android.systemui.scene.ui.viewmodel
-import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.shared.model.RemoteUserInput
-import com.android.systemui.scene.shared.model.RemoteUserInputAction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.currentTime
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -73,35 +68,4 @@ class SceneContainerViewModelTest : SysuiTestCase() {
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
-
- @Test
- fun onRemoteUserInput() = runTest {
- val remoteUserInput by collectLastValue(underTest.remoteUserInput)
- assertThat(remoteUserInput).isNull()
-
- val inputs =
- SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE.map { remoteUserInputToMotionEvent(it) }
-
- inputs.forEachIndexed { index, input ->
- underTest.onRemoteUserInput(input)
- assertThat(remoteUserInput).isEqualTo(SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE[index])
- }
- }
-
- private fun TestScope.remoteUserInputToMotionEvent(input: RemoteUserInput): MotionEvent {
- return MotionEvent.obtain(
- currentTime,
- currentTime,
- when (input.action) {
- RemoteUserInputAction.DOWN -> MotionEvent.ACTION_DOWN
- RemoteUserInputAction.MOVE -> MotionEvent.ACTION_MOVE
- RemoteUserInputAction.UP -> MotionEvent.ACTION_UP
- RemoteUserInputAction.CANCEL -> MotionEvent.ACTION_CANCEL
- RemoteUserInputAction.UNKNOWN -> MotionEvent.ACTION_OUTSIDE
- },
- input.x,
- input.y,
- 0
- )
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
index 2d3ee0e5cff9..ca4486b533ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -20,12 +20,13 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.Uri
-import androidx.test.ext.truth.content.IntentSubject.assertThat
+import androidx.test.ext.truth.content.IntentSubject.assertThat as assertThatIntent
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
import org.mockito.Mockito.`when` as whenever
@@ -39,23 +40,23 @@ class ActionIntentCreatorTest : SysuiTestCase() {
val output = ActionIntentCreator.createShare(uri)
- assertThat(output).hasAction(Intent.ACTION_CHOOSER)
- assertThat(output)
+ assertThatIntent(output).hasAction(Intent.ACTION_CHOOSER)
+ assertThatIntent(output)
.hasFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK or
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
- assertThat(output).extras().parcelable<Intent>(Intent.EXTRA_INTENT).isNotNull()
+ assertThatIntent(output).extras().parcelable<Intent>(Intent.EXTRA_INTENT).isNotNull()
val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
- assertThat(wrappedIntent).hasAction(Intent.ACTION_SEND)
- assertThat(wrappedIntent).hasData(uri)
- assertThat(wrappedIntent).hasType("image/png")
- assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT)
- assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT)
- assertThat(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
+ assertThatIntent(wrappedIntent).hasAction(Intent.ACTION_SEND)
+ assertThatIntent(wrappedIntent).hasData(uri)
+ assertThatIntent(wrappedIntent).hasType("image/png")
+ assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT)
+ assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT)
+ assertThatIntent(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
}
@Test
@@ -64,7 +65,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
val output = ActionIntentCreator.createShare(uri)
- assertThat(output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java))
+ assertThatIntent(output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java))
.hasData(Uri.parse("content://fake"))
}
@@ -75,8 +76,8 @@ class ActionIntentCreatorTest : SysuiTestCase() {
val output = ActionIntentCreator.createShareWithSubject(uri, subject)
- assertThat(output).hasAction(Intent.ACTION_CHOOSER)
- assertThat(output)
+ assertThatIntent(output).hasAction(Intent.ACTION_CHOOSER)
+ assertThatIntent(output)
.hasFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK or
@@ -84,12 +85,12 @@ class ActionIntentCreatorTest : SysuiTestCase() {
)
val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
- assertThat(wrappedIntent).hasAction(Intent.ACTION_SEND)
- assertThat(wrappedIntent).hasData(uri)
- assertThat(wrappedIntent).hasType("image/png")
- assertThat(wrappedIntent).extras().string(Intent.EXTRA_SUBJECT).isEqualTo(subject)
- assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT)
- assertThat(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
+ assertThatIntent(wrappedIntent).hasAction(Intent.ACTION_SEND)
+ assertThatIntent(wrappedIntent).hasData(uri)
+ assertThatIntent(wrappedIntent).hasType("image/png")
+ assertThatIntent(wrappedIntent).extras().string(Intent.EXTRA_SUBJECT).isEqualTo(subject)
+ assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT)
+ assertThatIntent(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
}
@Test
@@ -99,8 +100,8 @@ class ActionIntentCreatorTest : SysuiTestCase() {
val output = ActionIntentCreator.createShareWithText(uri, extraText)
- assertThat(output).hasAction(Intent.ACTION_CHOOSER)
- assertThat(output)
+ assertThatIntent(output).hasAction(Intent.ACTION_CHOOSER)
+ assertThatIntent(output)
.hasFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK or
@@ -108,12 +109,12 @@ class ActionIntentCreatorTest : SysuiTestCase() {
)
val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
- assertThat(wrappedIntent).hasAction(Intent.ACTION_SEND)
- assertThat(wrappedIntent).hasData(uri)
- assertThat(wrappedIntent).hasType("image/png")
- assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT)
- assertThat(wrappedIntent).extras().string(Intent.EXTRA_TEXT).isEqualTo(extraText)
- assertThat(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
+ assertThatIntent(wrappedIntent).hasAction(Intent.ACTION_SEND)
+ assertThatIntent(wrappedIntent).hasData(uri)
+ assertThatIntent(wrappedIntent).hasType("image/png")
+ assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT)
+ assertThatIntent(wrappedIntent).extras().string(Intent.EXTRA_TEXT).isEqualTo(extraText)
+ assertThatIntent(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
}
@Test
@@ -125,11 +126,12 @@ class ActionIntentCreatorTest : SysuiTestCase() {
val output = ActionIntentCreator.createEdit(uri, context)
- assertThat(output).hasAction(Intent.ACTION_EDIT)
- assertThat(output).hasData(uri)
- assertThat(output).hasType("image/png")
+ assertThatIntent(output).hasAction(Intent.ACTION_EDIT)
+ assertThatIntent(output).hasData(uri)
+ assertThatIntent(output).hasType("image/png")
assertWithMessage("getComponent()").that(output.component).isNull()
- assertThat(output)
+ assertThat(output.getStringExtra("edit_source")).isEqualTo("screenshot")
+ assertThatIntent(output)
.hasFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
@@ -146,7 +148,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
val output = ActionIntentCreator.createEdit(uri, context)
- assertThat(output).hasData(Uri.parse("content://fake"))
+ assertThatIntent(output).hasData(Uri.parse("content://fake"))
}
@Test
@@ -160,6 +162,6 @@ class ActionIntentCreatorTest : SysuiTestCase() {
val output = ActionIntentCreator.createEdit(uri, context)
- assertThat(output).hasComponent(component)
+ assertThatIntent(output).hasComponent(component)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 981e44bea846..5cad9f821c96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -73,6 +73,7 @@ import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.LockIconViewController;
+import com.android.keyguard.TestScopeProvider;
import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
@@ -119,6 +120,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shade.transition.ShadeTransitionController;
@@ -134,6 +136,7 @@ import com.android.systemui.statusbar.QsFrameTranslateController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -164,14 +167,17 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.phone.TapAgainViewController;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.user.domain.interactor.UserInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
@@ -358,6 +364,16 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository);
mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
+ mShadeRepository = new FakeShadeRepository();
+ mShadeInteractor = new ShadeInteractor(
+ TestScopeProvider.getTestScope(),
+ mShadeRepository,
+ new FakeDisableFlagsRepository(),
+ new FakeKeyguardRepository(),
+ new FakeUserSetupRepository(),
+ mock(DeviceProvisionedController.class),
+ mock(UserInteractor.class)
+ );
SystemClock systemClock = new FakeSystemClock();
mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
@@ -584,19 +600,33 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mMainHandler,
mLayoutInflater,
mFeatureFlags,
- coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
- mFalsingManager, new FalsingCollectorFake(),
+ coordinator,
+ expansionHandler,
+ mDynamicPrivacyController,
+ mKeyguardBypassController,
+ mFalsingManager,
+ new FalsingCollectorFake(),
mKeyguardStateController,
mStatusBarStateController,
mStatusBarWindowStateController,
mNotificationShadeWindowController,
- mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
- mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
+ mDozeLog,
+ mDozeParameters,
+ mCommandQueue,
+ mVibratorHelper,
+ mLatencyTracker,
+ mPowerManager,
+ mAccessibilityManager,
+ 0,
+ mUpdateMonitor,
mMetricsLogger,
mShadeLog,
+ mShadeInteractor,
mConfigurationController,
- () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
- mConversationNotificationManager, mMediaHierarchyManager,
+ () -> flingAnimationUtilsBuilder,
+ mStatusBarTouchableRegionManager,
+ mConversationNotificationManager,
+ mMediaHierarchyManager,
mStatusBarKeyguardViewManager,
mGutsManager,
mNotificationsQSContainerController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index a2c291281fdf..35a54d14d1bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -49,6 +49,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shade.transition.ShadeTransitionController;
@@ -170,6 +171,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
mShadeInteractor =
new ShadeInteractor(
mTestScope.getBackgroundScope(),
+ new FakeShadeRepository(),
mDisableFlagsRepository,
mKeyguardRepository,
new FakeUserSetupRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index cdcd1a257671..9e6c12f65d43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -67,6 +67,7 @@ class ShadeInteractorTest : SysuiTestCase() {
private val featureFlags = FakeFeatureFlags()
private val userSetupRepository = FakeUserSetupRepository()
private val userRepository = FakeUserRepository()
+ private val shadeRepository = FakeShadeRepository()
private val disableFlagsRepository = FakeDisableFlagsRepository()
private val keyguardRepository = FakeKeyguardRepository()
@@ -138,6 +139,7 @@ class ShadeInteractorTest : SysuiTestCase() {
underTest =
ShadeInteractor(
testScope.backgroundScope,
+ shadeRepository,
disableFlagsRepository,
keyguardRepository,
userSetupRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 4a2518ae6f7d..ec4367c3f794 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -102,8 +102,10 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
@Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
private val disableFlagsRepository = FakeDisableFlagsRepository()
private val keyguardRepository = FakeKeyguardRepository()
+ private val shadeRepository = FakeShadeRepository()
private val shadeInteractor = ShadeInteractor(
testScope.backgroundScope,
+ shadeRepository,
disableFlagsRepository,
keyguardRepository,
userSetupRepository = FakeUserSetupRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 4a94dc819a9e..38a8f414b0fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -21,11 +21,21 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
import org.junit.Assert
import org.junit.Before
import org.junit.Test
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
@SmallTest
@@ -36,13 +46,43 @@ class GroupExpansionManagerTest : SysuiTestCase() {
private val groupMembershipManager: GroupMembershipManager = mock()
private val featureFlags = FakeFeatureFlags()
- private val entry1 = NotificationEntryBuilder().build()
- private val entry2 = NotificationEntryBuilder().build()
+ private val pipeline: NotifPipeline = mock()
+ private lateinit var beforeRenderListListener: OnBeforeRenderListListener
+
+ private val summary1 = notificationEntry("foo", 1)
+ private val summary2 = notificationEntry("bar", 1)
+ private val entries =
+ listOf<ListEntry>(
+ GroupEntryBuilder()
+ .setSummary(summary1)
+ .setChildren(
+ listOf(
+ notificationEntry("foo", 2),
+ notificationEntry("foo", 3),
+ notificationEntry("foo", 4)
+ )
+ )
+ .build(),
+ GroupEntryBuilder()
+ .setSummary(summary2)
+ .setChildren(
+ listOf(
+ notificationEntry("bar", 2),
+ notificationEntry("bar", 3),
+ notificationEntry("bar", 4)
+ )
+ )
+ .build(),
+ notificationEntry("baz", 1)
+ )
+
+ private fun notificationEntry(pkg: String, id: Int) =
+ NotificationEntryBuilder().setPkg(pkg).setId(id).build().apply { row = mock() }
@Before
fun setUp() {
- whenever(groupMembershipManager.getGroupSummary(entry1)).thenReturn(entry1)
- whenever(groupMembershipManager.getGroupSummary(entry2)).thenReturn(entry2)
+ whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
+ whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags)
}
@@ -54,15 +94,15 @@ class GroupExpansionManagerTest : SysuiTestCase() {
var listenerCalledCount = 0
gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
- gem.setGroupExpanded(entry1, false)
+ gem.setGroupExpanded(summary1, false)
Assert.assertEquals(0, listenerCalledCount)
- gem.setGroupExpanded(entry1, true)
+ gem.setGroupExpanded(summary1, true)
Assert.assertEquals(1, listenerCalledCount)
- gem.setGroupExpanded(entry2, true)
+ gem.setGroupExpanded(summary2, true)
Assert.assertEquals(2, listenerCalledCount)
- gem.setGroupExpanded(entry1, true)
+ gem.setGroupExpanded(summary1, true)
Assert.assertEquals(2, listenerCalledCount)
- gem.setGroupExpanded(entry2, false)
+ gem.setGroupExpanded(summary2, false)
Assert.assertEquals(3, listenerCalledCount)
}
@@ -73,15 +113,39 @@ class GroupExpansionManagerTest : SysuiTestCase() {
var listenerCalledCount = 0
gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
- gem.setGroupExpanded(entry1, false)
+ gem.setGroupExpanded(summary1, false)
Assert.assertEquals(1, listenerCalledCount)
- gem.setGroupExpanded(entry1, true)
+ gem.setGroupExpanded(summary1, true)
Assert.assertEquals(2, listenerCalledCount)
- gem.setGroupExpanded(entry2, true)
+ gem.setGroupExpanded(summary2, true)
Assert.assertEquals(3, listenerCalledCount)
- gem.setGroupExpanded(entry1, true)
+ gem.setGroupExpanded(summary1, true)
Assert.assertEquals(4, listenerCalledCount)
- gem.setGroupExpanded(entry2, false)
+ gem.setGroupExpanded(summary2, false)
Assert.assertEquals(5, listenerCalledCount)
}
+
+ @Test
+ fun testSyncWithPipeline() {
+ featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+ gem.attach(pipeline)
+ beforeRenderListListener = withArgCaptor {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ val listener: OnGroupExpansionChangeListener = mock()
+ gem.registerGroupExpansionChangeListener(listener)
+
+ beforeRenderListListener.onBeforeRenderList(entries)
+ verify(listener, never()).onGroupExpansionChange(any(), any())
+
+ // Expand one of the groups.
+ gem.setGroupExpanded(summary1, true)
+ verify(listener).onGroupExpansionChange(summary1.row, true)
+
+ // Empty the pipeline list and verify that the group is no longer expanded.
+ beforeRenderListListener.onBeforeRenderList(emptyList())
+ verify(listener).onGroupExpansionChange(summary1.row, false)
+ verifyNoMoreInteractions(listener)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 987861d3f133..77c9b8b48296 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.notification.stack
import android.annotation.DimenRes
+import android.content.pm.PackageManager
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
@@ -21,6 +22,7 @@ import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -66,6 +68,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
@Before
fun setUp() {
+ Assume.assumeFalse(isTv())
+
whenever(notificationShelf.viewState).thenReturn(ExpandableViewState())
whenever(notificationRow.viewState).thenReturn(ExpandableViewState())
ambientState.isSmallScreen = true
@@ -73,6 +77,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
hostView.addView(notificationRow)
}
+ private fun isTv(): Boolean {
+ return context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ }
+
@Test
fun resetViewStates_defaultHun_yTranslationIsInset() {
whenever(notificationRow.isPinned).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 8545b894ad41..3ad3c15f158a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -29,8 +31,10 @@ import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.os.PowerManager;
import android.os.UserHandle;
+import android.os.VibrationEffect;
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
+import android.view.HapticFeedbackConstants;
import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
@@ -42,6 +46,7 @@ import com.android.internal.view.AppearanceRegion;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
@@ -98,6 +103,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@Mock private UserTracker mUserTracker;
@Mock private QSHost mQSHost;
@Mock private ActivityStarter mActivityStarter;
+ private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -134,7 +140,8 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
mCameraLauncherLazy,
mUserTracker,
mQSHost,
- mActivityStarter);
+ mActivityStarter,
+ mFeatureFlags);
when(mUserTracker.getUserHandle()).thenReturn(
UserHandle.of(ActivityManager.getCurrentUser()));
@@ -241,4 +248,24 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
verifyZeroInteractions(mSystemBarAttributesListener);
}
+
+ @Test
+ public void vibrateOnNavigationKeyDown_oneWayHapticsDisabled_usesVibrate() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+
+ mSbcqCallbacks.vibrateOnNavigationKeyDown();
+
+ verify(mVibratorHelper).vibrate(VibrationEffect.EFFECT_TICK);
+ }
+
+ @Test
+ public void vibrateOnNavigationKeyDown_oneWayHapticsEnabled_usesPerformHapticFeedback() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+
+ mSbcqCallbacks.vibrateOnNavigationKeyDown();
+
+ verify(mShadeViewController).performHapticFeedback(
+ HapticFeedbackConstants.GESTURE_START
+ );
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 2e92bb948c60..0b31523e9b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -27,7 +27,7 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
@@ -77,7 +77,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var shadeControllerImpl: ShadeControllerImpl
@Mock
- private lateinit var sceneInteractor: Provider<SceneInteractor>
+ private lateinit var windowRootView: Provider<WindowRootView>
@Mock
private lateinit var shadeLogger: ShadeLogger
@Mock
@@ -203,7 +203,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
centralSurfacesImpl,
shadeControllerImpl,
shadeViewController,
- sceneInteractor,
+ windowRootView,
shadeLogger,
viewUtil,
configurationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 823155b0d7e6..b8f2cab3019e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -81,7 +81,7 @@ import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
-import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -136,8 +136,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
private StatusBarWindowStateController mStatusBarWindowStateController;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @ClassRule
- public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index ef39ff8ed521..79feb417a062 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -82,7 +82,7 @@ import com.android.systemui.statusbar.phone.LightBarController;
import org.junit.After;
import org.junit.Before;
-import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -111,8 +111,8 @@ public class RemoteInputViewTest extends SysuiTestCase {
private BlockingQueueIntentReceiver mReceiver;
private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
- @ClassRule
- public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
@Before
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
index b698e70eb379..b78e83990411 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -105,6 +106,7 @@ class VariableDateViewControllerTest : SysuiTestCase() {
systemClock,
broadcastDispatcher,
shadeExpansionStateManager,
+ mock(),
testableHandler,
view
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 462fd0a2410b..69d7586e6ab4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -104,8 +104,6 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
private UserTracker mUserTracker;
@Mock
private DumpManager mDumpManager;
- @Mock
- private Handler mHandler;
@Before
@@ -130,7 +128,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
mPackageManager, mWakefullnessLifcycle, mKeyguardManager,
- mActivityManager, mUserTracker, mDumpManager, mHandler, mCallback);
+ mActivityManager, mUserTracker, mDumpManager, mCallback);
mVolumeController.setEnableDialogs(true, true);
}
@@ -245,12 +243,11 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
ActivityManager activityManager,
UserTracker userTracker,
DumpManager dumpManager,
- Handler mainHandler,
C callback) {
super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
notificationManager, optionalVibrator, iAudioService, accessibilityManager,
packageManager, wakefulnessLifecycle, keyguardManager,
- activityManager, userTracker, dumpManager, mainHandler);
+ activityManager, userTracker, dumpManager);
mCallbacks = callback;
ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index fa18e575220c..5f0011bc809a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -31,6 +31,7 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assume.assumeNotNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -38,7 +39,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.animation.AnimatorTestRule;
import android.app.KeyguardManager;
import android.content.res.Configuration;
import android.media.AudioManager;
@@ -85,14 +85,14 @@ import java.util.function.Predicate;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class VolumeDialogImplTest extends SysuiTestCase {
- private static final AnimatorTestRule sAnimatorTestRule = new AnimatorTestRule();
-
VolumeDialogImpl mDialog;
View mActiveRinger;
View mDrawerContainer;
View mDrawerVibrate;
View mDrawerMute;
View mDrawerNormal;
+ CaptionsToggleImageButton mODICaptionsIcon;
+
private TestableLooper mTestableLooper;
private ConfigurationController mConfigurationController;
private int mOriginalOrientation;
@@ -177,9 +177,14 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mActiveRinger = mDialog.getDialogView().findViewById(
R.id.volume_new_ringer_active_icon_container);
mDrawerContainer = mDialog.getDialogView().findViewById(R.id.volume_drawer_container);
- mDrawerVibrate = mDrawerContainer.findViewById(R.id.volume_drawer_vibrate);
- mDrawerMute = mDrawerContainer.findViewById(R.id.volume_drawer_mute);
- mDrawerNormal = mDrawerContainer.findViewById(R.id.volume_drawer_normal);
+
+ // Drawer is not always available, e.g. on TVs
+ if (mDrawerContainer != null) {
+ mDrawerVibrate = mDrawerContainer.findViewById(R.id.volume_drawer_vibrate);
+ mDrawerMute = mDrawerContainer.findViewById(R.id.volume_drawer_mute);
+ mDrawerNormal = mDrawerContainer.findViewById(R.id.volume_drawer_normal);
+ }
+ mODICaptionsIcon = mDialog.getDialogView().findViewById(R.id.odi_captions_icon);
Prefs.putInt(mContext,
Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT,
@@ -188,6 +193,10 @@ public class VolumeDialogImplTest extends SysuiTestCase {
Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
}
+ private void assumeHasDrawer() {
+ assumeNotNull("Layout does not contain drawer", mDrawerContainer);
+ }
+
private State createShellState() {
State state = new VolumeDialogController.State();
for (int i = AudioManager.STREAM_VOICE_CALL; i <= AudioManager.STREAM_ACCESSIBILITY; i++) {
@@ -359,6 +368,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Test
public void testSelectVibrateFromDrawer() {
+ assumeHasDrawer();
+
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
@@ -374,6 +385,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Test
public void testSelectVibrateFromDrawer_OnewayAPI_On() {
+ assumeHasDrawer();
+
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
@@ -389,6 +402,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Test
public void testSelectMuteFromDrawer() {
+ assumeHasDrawer();
+
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
@@ -404,6 +419,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Test
public void testSelectMuteFromDrawer_OnewayAPI_On() {
+ assumeHasDrawer();
+
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
@@ -419,6 +436,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Test
public void testSelectNormalFromDrawer() {
+ assumeHasDrawer();
+
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
@@ -434,6 +453,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
@Test
public void testSelectNormalFromDrawer_OnewayAPI_On() {
+ assumeHasDrawer();
+
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
@@ -688,6 +709,28 @@ public class VolumeDialogImplTest extends SysuiTestCase {
assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.CLOSE);
}
+ @Test
+ public void testOnCaptionEnabledStateChanged_checkBeforeSwitchTrue_setCaptionsEnabledState() {
+ ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture =
+ ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
+ verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any());
+ VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();
+
+ callbacks.onCaptionEnabledStateChanged(true, true);
+ verify(mVolumeDialogController).setCaptionsEnabledState(eq(false));
+ }
+
+ @Test
+ public void testOnCaptionEnabledStateChanged_checkBeforeSwitchFalse_getCaptionsEnabledTrue() {
+ ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture =
+ ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
+ verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any());
+ VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();
+
+ callbacks.onCaptionEnabledStateChanged(true, false);
+ assertTrue(mODICaptionsIcon.getCaptionsEnabled());
+ }
+
/**
* The content description should include ringer state, and the correct one.
*/
@@ -727,7 +770,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
public void teardown() {
cleanUp(mDialog);
setOrientation(mOriginalOrientation);
- sAnimatorTestRule.advanceTimeBy(mLongestHideShowAnimationDuration);
mTestableLooper.moveTimeForward(mLongestHideShowAnimationDuration);
mTestableLooper.processAllMessages();
reset(mPostureController);
diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
index 19c68e86e5dc..41dbc147dfc5 100644
--- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
+++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
@@ -49,7 +49,7 @@ import java.util.function.Consumer;
* public class SampleAnimatorTest {
*
* {@literal @}Rule
- * public AnimatorTestRule sAnimatorTestRule = new AnimatorTestRule();
+ * public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
*
* {@literal @}UiThreadTest
* {@literal @}Test
@@ -58,7 +58,7 @@ import java.util.function.Consumer;
* animator.setDuration(1000L);
* assertThat(animator.getAnimatedValue(), is(0));
* animator.start();
- * sAnimatorTestRule.advanceTimeBy(500L);
+ * mAnimatorTestRule.advanceTimeBy(500L);
* assertThat(animator.getAnimatedValue(), is(500));
* }
* }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index 8c98aea6a990..e91e9559fa1e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -22,32 +22,24 @@ import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
class FakeBiometricSettingsRepository : BiometricSettingsRepository {
+ private val _isFingerprintEnrolledAndEnabled = MutableStateFlow(false)
+ override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean>
+ get() = _isFingerprintEnrolledAndEnabled
- private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false)
- override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow()
+ private val _isFingerprintAuthCurrentlyAllowed = MutableStateFlow(false)
+ override val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean>
+ get() = _isFingerprintAuthCurrentlyAllowed
- private val _isFaceEnrolled = MutableStateFlow(false)
- override val isFaceEnrolled: Flow<Boolean>
- get() = _isFaceEnrolled
+ private val _isFaceAuthEnrolledAndEnabled = MutableStateFlow(false)
+ override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+ get() = _isFaceAuthEnrolledAndEnabled
- private val _isFaceAuthEnabled = MutableStateFlow(false)
- override val isFaceAuthenticationEnabled: Flow<Boolean>
- get() = _isFaceAuthEnabled
-
- private val _isStrongBiometricAllowed = MutableStateFlow(false)
- override val isStrongBiometricAllowed = _isStrongBiometricAllowed.asStateFlow()
-
- private val _isNonStrongBiometricAllowed = MutableStateFlow(false)
- override val isNonStrongBiometricAllowed: StateFlow<Boolean>
- get() = _isNonStrongBiometricAllowed
-
- private val _isFingerprintEnabledByDevicePolicy = MutableStateFlow(false)
- override val isFingerprintEnabledByDevicePolicy =
- _isFingerprintEnabledByDevicePolicy.asStateFlow()
+ private val _isFaceAuthCurrentlyAllowed = MutableStateFlow(false)
+ override val isFaceAuthCurrentlyAllowed: Flow<Boolean>
+ get() = _isFaceAuthCurrentlyAllowed
private val _isFaceAuthSupportedInCurrentPosture = MutableStateFlow(false)
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
@@ -59,34 +51,33 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
private val _authFlags = MutableStateFlow(AuthenticationFlags(0, 0))
override val authenticationFlags: Flow<AuthenticationFlags>
get() = _authFlags
- fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
- _isFingerprintEnrolled.value = isFingerprintEnrolled
+
+ fun setAuthenticationFlags(value: AuthenticationFlags) {
+ _authFlags.value = value
}
- fun setStrongBiometricAllowed(isStrongBiometricAllowed: Boolean) {
- _isStrongBiometricAllowed.value = isStrongBiometricAllowed
+ fun setIsFingerprintAuthEnrolledAndEnabled(value: Boolean) {
+ _isFingerprintEnrolledAndEnabled.value = value
+ _isFingerprintAuthCurrentlyAllowed.value = _isFingerprintAuthCurrentlyAllowed.value && value
}
- fun setFingerprintEnabledByDevicePolicy(isFingerprintEnabledByDevicePolicy: Boolean) {
- _isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
+ fun setIsFingerprintAuthCurrentlyAllowed(value: Boolean) {
+ _isFingerprintAuthCurrentlyAllowed.value = value
}
- fun setAuthenticationFlags(value: AuthenticationFlags) {
- _authFlags.value = value
+ fun setIsFaceAuthEnrolledAndEnabled(value: Boolean) {
+ _isFaceAuthEnrolledAndEnabled.value = value
+ _isFaceAuthCurrentlyAllowed.value = _isFaceAuthCurrentlyAllowed.value && value
}
- fun setFaceEnrolled(isFaceEnrolled: Boolean) {
- _isFaceEnrolled.value = isFaceEnrolled
+ fun setIsFaceAuthCurrentlyAllowed(value: Boolean) {
+ _isFaceAuthCurrentlyAllowed.value = value
}
fun setIsFaceAuthSupportedInCurrentPosture(value: Boolean) {
_isFaceAuthSupportedInCurrentPosture.value = value
}
- fun setIsFaceAuthEnabled(enabled: Boolean) {
- _isFaceAuthEnabled.value = enabled
- }
-
fun setIsUserInLockdown(value: Boolean) {
if (value) {
setAuthenticationFlags(
@@ -105,12 +96,4 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
)
}
}
-
- fun setIsNonStrongBiometricAllowed(value: Boolean) {
- _isNonStrongBiometricAllowed.value = value
- }
-
- fun setIsStrongBiometricAllowed(value: Boolean) {
- _isStrongBiometricAllowed.value = value
- }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index dd45331df4b3..f0e1111c6e12 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -39,8 +39,6 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.RemoteUserInput
-import com.android.systemui.scene.shared.model.RemoteUserInputAction
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -196,15 +194,6 @@ class SceneTestUtils(
}
companion object {
- val REMOTE_INPUT_DOWN_GESTURE =
- listOf(
- RemoteUserInput(10f, 10f, RemoteUserInputAction.DOWN),
- RemoteUserInput(10f, 20f, RemoteUserInputAction.MOVE),
- RemoteUserInput(10f, 30f, RemoteUserInputAction.MOVE),
- RemoteUserInput(10f, 40f, RemoteUserInputAction.MOVE),
- RemoteUserInput(10f, 40f, RemoteUserInputAction.UP),
- )
-
fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel {
return when (this) {
DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 492e5421b010..35a3fd01675d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -30,6 +30,9 @@ class FakeShadeRepository : ShadeRepository {
private val _qsExpansion = MutableStateFlow(0f)
override val qsExpansion = _qsExpansion
+ private val _expansion = MutableStateFlow(0f)
+ override val expansion = _expansion
+
private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress
@@ -41,6 +44,10 @@ class FakeShadeRepository : ShadeRepository {
_qsExpansion.value = qsExpansion
}
+ override fun setExpansion(expansion: Float) {
+ _expansion.value = expansion
+ }
+
override fun setUdfpsTransitionToFullShadeProgress(progress: Float) {
_udfpsTransitionToFullShadeProgress.value = progress
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 830d55ad866b..7fb9580b28ab 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1289,6 +1289,16 @@ class StorageManagerService extends IStorageManager.Stub
return mVold.supportsBlockCheckpoint();
}
+ private void prepareUserStorageForMoveInternal(String fromVolumeUuid, String toVolumeUuid,
+ List<UserInfo> users) throws Exception {
+
+ final int flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
+ for (UserInfo user : users) {
+ prepareUserStorageInternal(fromVolumeUuid, user.id, user.serialNumber, flags);
+ prepareUserStorageInternal(toVolumeUuid, user.id, user.serialNumber, flags);
+ }
+ }
+
@Override
public void onAwakeStateChanged(boolean isAwake) {
// Ignored
@@ -2912,6 +2922,7 @@ class StorageManagerService extends IStorageManager.Stub
final VolumeInfo from;
final VolumeInfo to;
+ final List<UserInfo> users;
synchronized (mLock) {
if (Objects.equals(mPrimaryStorageUuid, volumeUuid)) {
@@ -2925,7 +2936,7 @@ class StorageManagerService extends IStorageManager.Stub
mMoveTargetUuid = volumeUuid;
// We need all the users unlocked to move their primary storage
- final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
+ users = mContext.getSystemService(UserManager.class).getUsers();
for (UserInfo user : users) {
if (StorageManager.isFileEncrypted() && !isUserKeyUnlocked(user.id)) {
Slog.w(TAG, "Failing move due to locked user " + user.id);
@@ -2961,6 +2972,19 @@ class StorageManagerService extends IStorageManager.Stub
}
}
+ // Prepare the storage before move, this is required to unlock adoptable storage (as the
+ // keys are tied to prepare user data step) & also is required for the destination files to
+ // end up with the correct SELinux labels and encryption policies for directories
+ try {
+ prepareUserStorageForMoveInternal(mPrimaryStorageUuid, volumeUuid, users);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failing move due to failure on prepare user data", e);
+ synchronized (mLock) {
+ onMoveStatusLocked(PackageManager.MOVE_FAILED_INTERNAL_ERROR);
+ }
+ return;
+ }
+
try {
mVold.moveStorage(from.id, to.id, new IVoldTaskListener.Stub() {
@Override
@@ -4904,5 +4928,16 @@ class StorageManagerService extends IStorageManager.Stub
mCloudProviderChangeListeners.add(listener);
mHandler.obtainMessage(H_CLOUD_MEDIA_PROVIDER_CHANGED, listener).sendToTarget();
}
+
+ @Override
+ public void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
+ List<UserInfo> users) {
+ try {
+ prepareUserStorageForMoveInternal(fromVolumeUuid, toVolumeUuid, users);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
}
}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
index 137a418e31ab..e109cc8011e7 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStats.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
@@ -22,6 +22,8 @@ package com.android.server.biometrics;
*/
public class AuthenticationStats {
+ private static final float FRR_NOT_ENOUGH_ATTEMPTS = -1.0f;
+
private final int mUserId;
private int mTotalAttempts;
private int mRejectedAttempts;
@@ -70,7 +72,7 @@ public class AuthenticationStats {
if (mTotalAttempts > 0) {
return mRejectedAttempts / (float) mTotalAttempts;
} else {
- return -1.0f;
+ return FRR_NOT_ENOUGH_ATTEMPTS;
}
}
@@ -87,4 +89,32 @@ public class AuthenticationStats {
mTotalAttempts = 0;
mRejectedAttempts = 0;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof AuthenticationStats)) {
+ return false;
+ }
+
+ AuthenticationStats target = (AuthenticationStats) obj;
+ return this.getUserId() == target.getUserId()
+ && this.getTotalAttempts()
+ == target.getTotalAttempts()
+ && this.getRejectedAttempts()
+ == target.getRejectedAttempts()
+ && this.getEnrollmentNotifications()
+ == target.getEnrollmentNotifications()
+ && this.getModality() == target.getModality();
+ }
+
+ @Override
+ public int hashCode() {
+ return String.format("userId: %d, totalAttempts: %d, rejectedAttempts: %d, "
+ + "enrollmentNotifications: %d, modality: %d", mUserId, mTotalAttempts,
+ mRejectedAttempts, mEnrollmentNotifications, mModality).hashCode();
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index c9cd8148c0f1..97e5c6fbd8c3 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -18,10 +18,18 @@ package com.android.server.biometrics;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.sensors.BiometricNotification;
import java.util.HashMap;
import java.util.Map;
@@ -37,23 +45,60 @@ public class AuthenticationStatsCollector {
// The minimum number of attempts that will calculate the FRR and trigger the notification.
private static final int MINIMUM_ATTEMPTS = 500;
+ // Upload the data every 50 attempts (average number of daily authentications).
+ private static final int AUTHENTICATION_UPLOAD_INTERVAL = 50;
// The maximum number of eligible biometric enrollment notification can be sent.
private static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
+ @NonNull private final Context mContext;
+
private final float mThreshold;
private final int mModality;
@NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
- public AuthenticationStatsCollector(@NonNull Context context, int modality) {
+ // TODO(b/295582896): Find a way to make this NonNull
+ @Nullable private AuthenticationStatsPersister mAuthenticationStatsPersister;
+ @NonNull private BiometricNotification mBiometricNotification;
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId != UserHandle.USER_NULL
+ && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
+ onUserRemoved(userId);
+ }
+ }
+ };
+
+ public AuthenticationStatsCollector(@NonNull Context context, int modality,
+ @NonNull BiometricNotification biometricNotification) {
+ mContext = context;
mThreshold = context.getResources()
.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1);
mUserAuthenticationStatsMap = new HashMap<>();
mModality = modality;
+ mBiometricNotification = biometricNotification;
+
+ context.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED));
+ }
+
+ private void initializeUserAuthenticationStatsMap() {
+ mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext);
+ for (AuthenticationStats stats : mAuthenticationStatsPersister.getAllFrrStats(mModality)) {
+ mUserAuthenticationStatsMap.put(stats.getUserId(), stats);
+ }
}
/** Update total authentication and rejected attempts. */
public void authenticate(int userId, boolean authenticated) {
+ // SharedPreference is not ready when starting system server, initialize
+ // mUserAuthenticationStatsMap in authentication to ensure SharedPreference
+ // is ready for application use.
+ if (mUserAuthenticationStatsMap.isEmpty()) {
+ initializeUserAuthenticationStatsMap();
+ }
// Check if this is a new user.
if (!mUserAuthenticationStatsMap.containsKey(userId)) {
mUserAuthenticationStatsMap.put(userId, new AuthenticationStats(userId, mModality));
@@ -67,24 +112,65 @@ public class AuthenticationStatsCollector {
sendNotificationIfNeeded(userId);
}
+ /** Check if a notification should be sent after a calculation cycle. */
private void sendNotificationIfNeeded(int userId) {
AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
- if (authenticationStats.getTotalAttempts() >= MINIMUM_ATTEMPTS) {
- // Send notification if FRR exceeds the threshold
- if (authenticationStats.getEnrollmentNotifications() < MAXIMUM_ENROLLMENT_NOTIFICATIONS
- && authenticationStats.getFrr() >= mThreshold) {
- // TODO(wenhuiy): Send notifications.
- }
+ if (authenticationStats.getTotalAttempts() < MINIMUM_ATTEMPTS) {
+ return;
+ }
+ // Don't send notification if FRR below the threshold.
+ if (authenticationStats.getEnrollmentNotifications() >= MAXIMUM_ENROLLMENT_NOTIFICATIONS
+ || authenticationStats.getFrr() < mThreshold) {
authenticationStats.resetData();
+ return;
+ }
+
+ authenticationStats.resetData();
+
+ final PackageManager packageManager = mContext.getPackageManager();
+
+ // Don't send notification to single-modality devices.
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
+ || !packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+ return;
+ }
+
+ final FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+ final boolean hasEnrolledFace = faceManager.hasEnrolledTemplates(userId);
+
+ final FingerprintManager fingerprintManager = mContext
+ .getSystemService(FingerprintManager.class);
+ final boolean hasEnrolledFingerprint = fingerprintManager.hasEnrolledTemplates(userId);
+
+ // Don't send notification when both face and fingerprint are enrolled.
+ if (hasEnrolledFace && hasEnrolledFingerprint) {
+ return;
+ }
+ if (hasEnrolledFace && !hasEnrolledFingerprint) {
+ mBiometricNotification.sendFpEnrollNotification(mContext);
+ } else if (!hasEnrolledFace && hasEnrolledFingerprint) {
+ mBiometricNotification.sendFaceEnrollNotification(mContext);
}
}
private void persistDataIfNeeded(int userId) {
AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
- if (authenticationStats.getTotalAttempts() % 50 == 0) {
- // TODO(wenhuiy): Persist data.
+ if (authenticationStats.getTotalAttempts() % AUTHENTICATION_UPLOAD_INTERVAL == 0) {
+ mAuthenticationStatsPersister.persistFrrStats(authenticationStats.getUserId(),
+ authenticationStats.getTotalAttempts(),
+ authenticationStats.getRejectedAttempts(),
+ authenticationStats.getEnrollmentNotifications(),
+ authenticationStats.getModality());
+ }
+ }
+
+ private void onUserRemoved(final int userId) {
+ if (mAuthenticationStatsPersister == null) {
+ initializeUserAuthenticationStatsMap();
}
+ mUserAuthenticationStatsMap.remove(userId);
+ mAuthenticationStatsPersister.removeFrrStats(userId);
}
/**
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
new file mode 100644
index 000000000000..21e93a8bc024
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.os.Environment;
+import android.util.Slog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Persists and retrieves stats for Biometric Authentication.
+ * Authentication stats include userId, total attempts, rejected attempts,
+ * and the number of sent enrollment notifications.
+ * Data are stored in SharedPreferences in a form of a set of JSON objects,
+ * where it's one element per user.
+ */
+public class AuthenticationStatsPersister {
+
+ private static final String TAG = "AuthenticationStatsPersister";
+ private static final String FILE_NAME = "authentication_stats";
+ private static final String USER_ID = "user_id";
+ private static final String FACE_ATTEMPTS = "face_attempts";
+ private static final String FACE_REJECTIONS = "face_rejections";
+ private static final String FINGERPRINT_ATTEMPTS = "fingerprint_attempts";
+ private static final String FINGERPRINT_REJECTIONS = "fingerprint_rejections";
+ private static final String ENROLLMENT_NOTIFICATIONS = "enrollment_notifications";
+ private static final String KEY = "frr_stats";
+
+ @NonNull private final SharedPreferences mSharedPreferences;
+
+ AuthenticationStatsPersister(@NonNull Context context) {
+ // The package info in the context isn't initialized in the way it is for normal apps,
+ // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+ // build the path manually below using the same policy that appears in ContextImpl.
+ final File prefsFile = new File(Environment.getDataSystemDeDirectory(), FILE_NAME);
+ mSharedPreferences = context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Get all frr data from SharedPreference.
+ */
+ public List<AuthenticationStats> getAllFrrStats(int modality) {
+ List<AuthenticationStats> authenticationStatsList = new ArrayList<>();
+ for (String frrStats : readFrrStats()) {
+ try {
+ JSONObject frrStatsJson = new JSONObject(frrStats);
+ if (modality == BiometricsProtoEnums.MODALITY_FACE) {
+ authenticationStatsList.add(new AuthenticationStats(
+ getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */),
+ getIntValue(frrStatsJson, FACE_ATTEMPTS),
+ getIntValue(frrStatsJson, FACE_REJECTIONS),
+ getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS),
+ modality));
+ } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+ authenticationStatsList.add(new AuthenticationStats(
+ getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */),
+ getIntValue(frrStatsJson, FINGERPRINT_ATTEMPTS),
+ getIntValue(frrStatsJson, FINGERPRINT_REJECTIONS),
+ getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS),
+ modality));
+ }
+ } catch (JSONException e) {
+ Slog.w(TAG, String.format("Unable to resolve authentication stats JSON: %s",
+ frrStats));
+ }
+ }
+ return authenticationStatsList;
+ }
+
+ /**
+ * Remove frr data for a specific user.
+ */
+ public void removeFrrStats(int userId) {
+ try {
+ // Copy into a new HashSet to allow modification.
+ Set<String> frrStatsSet = new HashSet<>(readFrrStats());
+
+ // Remove the old authentication stat for the user if it exists.
+ for (Iterator<String> iterator = frrStatsSet.iterator(); iterator.hasNext();) {
+ String frrStats = iterator.next();
+ JSONObject frrStatJson = new JSONObject(frrStats);
+ if (getValue(frrStatJson, USER_ID).equals(String.valueOf(userId))) {
+ iterator.remove();
+ break;
+ }
+ }
+
+ mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
+ } catch (JSONException ignored) {
+ }
+ }
+
+ /**
+ * Persist frr data for a specific user.
+ */
+ public void persistFrrStats(int userId, int totalAttempts, int rejectedAttempts,
+ int enrollmentNotifications, int modality) {
+ try {
+ // Copy into a new HashSet to allow modification.
+ Set<String> frrStatsSet = new HashSet<>(readFrrStats());
+
+ // Remove the old authentication stat for the user if it exists.
+ JSONObject frrStatJson = null;
+ for (Iterator<String> iterator = frrStatsSet.iterator(); iterator.hasNext();) {
+ String frrStats = iterator.next();
+ frrStatJson = new JSONObject(frrStats);
+ if (getValue(frrStatJson, USER_ID).equals(String.valueOf(userId))) {
+ iterator.remove();
+ break;
+ }
+ }
+
+ // If there's existing frr stats in the file, we want to update the stats for the given
+ // modality and keep the stats for other modalities.
+ if (frrStatJson != null) {
+ frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts,
+ enrollmentNotifications, modality));
+ } else {
+ frrStatsSet.add(buildFrrStats(userId, totalAttempts, rejectedAttempts,
+ enrollmentNotifications, modality));
+ }
+
+ mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
+
+ } catch (JSONException e) {
+ Slog.e(TAG, "Unable to persist authentication stats");
+ }
+ }
+
+ private Set<String> readFrrStats() {
+ return mSharedPreferences.getStringSet(KEY, Set.of());
+ }
+
+ // Update frr stats for existing frrStats JSONObject and build the new string.
+ private String buildFrrStats(JSONObject frrStats, int totalAttempts, int rejectedAttempts,
+ int enrollmentNotifications, int modality) throws JSONException {
+ if (modality == BiometricsProtoEnums.MODALITY_FACE) {
+ return frrStats
+ .put(FACE_ATTEMPTS, totalAttempts)
+ .put(FACE_REJECTIONS, rejectedAttempts)
+ .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+ .toString();
+ } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+ return frrStats
+ .put(FINGERPRINT_ATTEMPTS, totalAttempts)
+ .put(FINGERPRINT_REJECTIONS, rejectedAttempts)
+ .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+ .toString();
+ } else {
+ return frrStats.toString();
+ }
+ }
+
+ // Build string for new user and new authentication stats.
+ private String buildFrrStats(int userId, int totalAttempts, int rejectedAttempts,
+ int enrollmentNotifications, int modality)
+ throws JSONException {
+ if (modality == BiometricsProtoEnums.MODALITY_FACE) {
+ return new JSONObject()
+ .put(USER_ID, userId)
+ .put(FACE_ATTEMPTS, totalAttempts)
+ .put(FACE_REJECTIONS, rejectedAttempts)
+ .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+ .toString();
+ } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+ return new JSONObject()
+ .put(USER_ID, userId)
+ .put(FINGERPRINT_ATTEMPTS, totalAttempts)
+ .put(FINGERPRINT_REJECTIONS, rejectedAttempts)
+ .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+ .toString();
+ } else {
+ return "";
+ }
+ }
+
+ private String getValue(JSONObject jsonObject, String key) throws JSONException {
+ return jsonObject.has(key) ? jsonObject.getString(key) : "";
+ }
+
+ private int getIntValue(JSONObject jsonObject, String key) throws JSONException {
+ return getIntValue(jsonObject, key, 0 /* defaultValue */);
+ }
+
+ private int getIntValue(JSONObject jsonObject, String key, int defaultValue)
+ throws JSONException {
+ return jsonObject.has(key) ? jsonObject.getInt(key) : defaultValue;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java
new file mode 100644
index 000000000000..90e18604d945
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.content.Context;
+
+/**
+ * Interface for biometrics to send notifications.
+ */
+public interface BiometricNotification {
+
+ /**
+ * Sends a face enrollment notification.
+ */
+ void sendFaceEnrollNotification(@NonNull Context context);
+
+ /**
+ * Sends a fingerprint enrollment notification.
+ */
+ void sendFpEnrollNotification(@NonNull Context context);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java
new file mode 100644
index 000000000000..7b420468f628
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.content.Context;
+
+import com.android.server.biometrics.AuthenticationStatsCollector;
+
+/**
+ * Implementation to send biometric notifications for {@link AuthenticationStatsCollector}.
+ */
+public class BiometricNotificationImpl implements BiometricNotification {
+
+ @Override
+ public void sendFaceEnrollNotification(@NonNull Context context) {
+ BiometricNotificationUtils.showFaceEnrollNotification(context);
+ }
+
+ @Override
+ public void sendFpEnrollNotification(@NonNull Context context) {
+ BiometricNotificationUtils.showFingerprintEnrollNotification(context);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index f516a4930a58..2ff695d7b85d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -35,9 +35,22 @@ import com.android.internal.R;
public class BiometricNotificationUtils {
private static final String TAG = "BiometricNotificationUtils";
- private static final String RE_ENROLL_NOTIFICATION_TAG = "FaceService";
- private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintService";
+ private static final String FACE_RE_ENROLL_NOTIFICATION_TAG = "FaceReEnroll";
+ private static final String FACE_ENROLL_NOTIFICATION_TAG = "FaceEnroll";
+ private static final String FINGERPRINT_ENROLL_NOTIFICATION_TAG = "FingerprintEnroll";
+ private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintBadCalibration";
private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
+ private static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS";
+ private static final String FINGERPRINT_SETTINGS_ACTION =
+ "android.settings.FINGERPRINT_SETTINGS";
+ private static final String FACE_ENROLL_ACTION = "android.settings.FACE_ENROLL";
+ private static final String FINGERPRINT_ENROLL_ACTION = "android.settings.FINGERPRINT_ENROLL";
+ private static final String SETTINGS_PACKAGE = "com.android.settings";
+ private static final String FACE_ENROLL_CHANNEL = "FaceEnrollNotificationChannel";
+ private static final String FACE_RE_ENROLL_CHANNEL = "FaceReEnrollNotificationChannel";
+ private static final String FINGERPRINT_ENROLL_CHANNEL = "FingerprintEnrollNotificationChannel";
+ private static final String FINGERPRINT_BAD_CALIBRATION_CHANNEL =
+ "FingerprintBadCalibrationNotificationChannel";
private static final int NOTIFICATION_ID = 1;
private static final long NOTIFICATION_INTERVAL_MS = 24 * 60 * 60 * 1000;
private static long sLastAlertTime = 0;
@@ -56,18 +69,67 @@ public class BiometricNotificationUtils {
final String content =
context.getString(R.string.face_recalibrate_notification_content);
- final Intent intent = new Intent("android.settings.FACE_SETTINGS");
- intent.setPackage("com.android.settings");
+ final Intent intent = new Intent(FACE_SETTINGS_ACTION);
+ intent.setPackage(SETTINGS_PACKAGE);
intent.putExtra(KEY_RE_ENROLL_FACE, true);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
null /* options */, UserHandle.CURRENT);
- final String channelName = "FaceEnrollNotificationChannel";
+ showNotificationHelper(context, name, title, content, pendingIntent, FACE_RE_ENROLL_CHANNEL,
+ FACE_RE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET);
+ }
+
+ /**
+ * Shows a face enrollment notification.
+ */
+ public static void showFaceEnrollNotification(@NonNull Context context) {
+
+ final String name =
+ context.getString(R.string.device_unlock_notification_name);
+ final String title =
+ context.getString(R.string.alternative_unlock_setup_notification_title);
+ final String content =
+ context.getString(R.string.alternative_face_setup_notification_content);
+
+ final Intent intent = new Intent(FACE_ENROLL_ACTION);
+ intent.setPackage(SETTINGS_PACKAGE);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+
+ final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
+ 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
+ null /* options */, UserHandle.CURRENT);
+
+ showNotificationHelper(context, name, title, content, pendingIntent, FACE_ENROLL_CHANNEL,
+ FACE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC);
+ }
+
+ /**
+ * Shows a fingerprint enrollment notification.
+ */
+ public static void showFingerprintEnrollNotification(@NonNull Context context) {
+
+ final String name =
+ context.getString(R.string.device_unlock_notification_name);
+ final String title =
+ context.getString(R.string.alternative_unlock_setup_notification_title);
+ final String content =
+ context.getString(R.string.alternative_fp_setup_notification_content);
+
+ final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION);
+ intent.setPackage(SETTINGS_PACKAGE);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+
+ final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
+ 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
+ null /* options */, UserHandle.CURRENT);
- showNotificationHelper(context, name, title, content, pendingIntent, channelName,
- RE_ENROLL_NOTIFICATION_TAG);
+ showNotificationHelper(context, name, title, content, pendingIntent,
+ FINGERPRINT_ENROLL_CHANNEL, FINGERPRINT_ENROLL_NOTIFICATION_TAG,
+ Notification.VISIBILITY_PUBLIC);
}
/**
@@ -93,22 +155,21 @@ public class BiometricNotificationUtils {
final String content =
context.getString(R.string.fingerprint_recalibrate_notification_content);
- final Intent intent = new Intent("android.settings.FINGERPRINT_SETTINGS");
- intent.setPackage("com.android.settings");
+ final Intent intent = new Intent(FINGERPRINT_SETTINGS_ACTION);
+ intent.setPackage(SETTINGS_PACKAGE);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
null /* options */, UserHandle.CURRENT);
- final String channelName = "FingerprintBadCalibrationNotificationChannel";
-
- showNotificationHelper(context, name, title, content, pendingIntent, channelName,
- BAD_CALIBRATION_NOTIFICATION_TAG);
+ showNotificationHelper(context, name, title, content, pendingIntent,
+ FINGERPRINT_BAD_CALIBRATION_CHANNEL, BAD_CALIBRATION_NOTIFICATION_TAG,
+ Notification.VISIBILITY_SECRET);
}
private static void showNotificationHelper(Context context, String name, String title,
String content, PendingIntent pendingIntent, String channelName,
- String notificationTag) {
+ String notificationTag, int visibility) {
final NotificationManager notificationManager =
context.getSystemService(NotificationManager.class);
final NotificationChannel channel = new NotificationChannel(channelName, name,
@@ -123,7 +184,7 @@ public class BiometricNotificationUtils {
.setAutoCancel(true)
.setCategory(Notification.CATEGORY_SYSTEM)
.setContentIntent(pendingIntent)
- .setVisibility(Notification.VISIBILITY_SECRET)
+ .setVisibility(visibility)
.build();
notificationManager.createNotificationChannel(channel);
@@ -134,10 +195,30 @@ public class BiometricNotificationUtils {
/**
* Cancels a face re-enrollment notification
*/
- public static void cancelReEnrollNotification(@NonNull Context context) {
+ public static void cancelFaceReEnrollNotification(@NonNull Context context) {
+ final NotificationManager notificationManager =
+ context.getSystemService(NotificationManager.class);
+ notificationManager.cancelAsUser(FACE_RE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
+ UserHandle.CURRENT);
+ }
+
+ /**
+ * Cancels a face enrollment notification
+ */
+ public static void cancelFaceEnrollNotification(@NonNull Context context) {
+ final NotificationManager notificationManager =
+ context.getSystemService(NotificationManager.class);
+ notificationManager.cancelAsUser(FACE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
+ UserHandle.CURRENT);
+ }
+
+ /**
+ * Cancels a fingerprint enrollment notification
+ */
+ public static void cancelFingerprintEnrollNotification(@NonNull Context context) {
final NotificationManager notificationManager =
context.getSystemService(NotificationManager.class);
- notificationManager.cancelAsUser(RE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
+ notificationManager.cancelAsUser(FINGERPRINT_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
UserHandle.CURRENT);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 722c9afbeaf8..f55cf0549382 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -109,7 +109,8 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
public void start(@NonNull ClientMonitorCallback callback) {
super.start(callback);
- BiometricNotificationUtils.cancelReEnrollNotification(getContext());
+ BiometricNotificationUtils.cancelFaceEnrollNotification(getContext());
+ BiometricNotificationUtils.cancelFaceReEnrollNotification(getContext());
}
@NonNull
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index a7d160c4fa60..28f0a4dadbd5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -56,6 +56,7 @@ import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricNotificationImpl;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -177,7 +178,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
mDaemon = daemon;
mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
- BiometricsProtoEnums.MODALITY_FACE);
+ BiometricsProtoEnums.MODALITY_FACE, new BiometricNotificationImpl());
for (SensorProps prop : props) {
final int sensorId = prop.commonProps.sensorId;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 10991d5f9133..808626120c1e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -62,7 +62,7 @@ import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
+import com.android.server.biometrics.sensors.BiometricNotificationImpl;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -367,7 +367,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
});
mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
- BiometricsProtoEnums.MODALITY_FACE);
+ BiometricsProtoEnums.MODALITY_FACE, new BiometricNotificationImpl());
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
@@ -615,8 +615,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
- BiometricNotificationUtils.cancelReEnrollNotification(mContext);
-
final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 16d2f7a03c6d..27b9c79516af 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -33,6 +33,7 @@ import com.android.internal.R;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -71,6 +72,14 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
.getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
}
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+
+ BiometricNotificationUtils.cancelFaceEnrollNotification(getContext());
+ BiometricNotificationUtils.cancelFaceReEnrollNotification(getContext());
+ }
+
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index f9e08d69ef48..46ff6b4fab1a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -104,6 +104,13 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
}
}
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+
+ BiometricNotificationUtils.cancelFingerprintEnrollNotification(getContext());
+ }
+
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 2d062db12cdc..5f4b89439fd0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -64,6 +64,7 @@ import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricNotificationImpl;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -184,7 +185,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mDaemon = daemon;
mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
- BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ BiometricsProtoEnums.MODALITY_FINGERPRINT, new BiometricNotificationImpl());
final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 4b07dca75e9e..d0b71fcf2dbb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -66,6 +66,7 @@ import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricNotificationImpl;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -354,7 +355,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
});
mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
- BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ BiometricsProtoEnums.MODALITY_FINGERPRINT, new BiometricNotificationImpl());
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 6fee84a5e057..382e7e2121f4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -81,6 +81,13 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
}
}
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+
+ BiometricNotificationUtils.cancelFingerprintEnrollNotification(getContext());
+ }
+
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 4ad26c46d7ed..7ea576d1ed3a 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -63,10 +63,15 @@ public class DisplayWhiteBalanceController implements
// high errors. This default is introduced to provide a fixed display color
// temperature when sensor readings become unreliable.
private final float mLowLightAmbientColorTemperature;
+ // As above, but used when in strong mode (idle screen brightness mode).
+ private final float mLowLightAmbientColorTemperatureStrong;
+
// In high brightness conditions certain color temperatures can cause peak display
// brightness to drop. This fixed color temperature can be used to compensate for
// this effect.
private final float mHighLightAmbientColorTemperature;
+ // As above, but used when in strong mode (idle screen brightness mode).
+ private final float mHighLightAmbientColorTemperatureStrong;
private final boolean mLightModeAllowed;
@@ -97,9 +102,11 @@ public class DisplayWhiteBalanceController implements
// ambient color temperature to the defaults. A piecewise linear relationship
// between low light brightness and low light bias.
private Spline.LinearSpline mLowLightAmbientBrightnessToBiasSpline;
+ private Spline.LinearSpline mLowLightAmbientBrightnessToBiasSplineStrong;
// A piecewise linear relationship between high light brightness and high light bias.
private Spline.LinearSpline mHighLightAmbientBrightnessToBiasSpline;
+ private Spline.LinearSpline mHighLightAmbientBrightnessToBiasSplineStrong;
private float mLatestAmbientColorTemperature;
private float mLatestAmbientBrightness;
@@ -134,17 +141,29 @@ public class DisplayWhiteBalanceController implements
* @param lowLightAmbientBrightnesses
* The ambient brightness used to map the ambient brightnesses to the biases used to
* interpolate to lowLightAmbientColorTemperature.
+ * @param lowLightAmbientBrightnessesStrong
+ * The ambient brightness used to map the ambient brightnesses to the biases used to
+ * interpolate to lowLightAmbientColorTemperature.
* @param lowLightAmbientBiases
* The biases used to map the ambient brightnesses to the biases used to interpolate to
* lowLightAmbientColorTemperature.
+ * @param lowLightAmbientBiasesStrong
+ * The biases used to map the ambient brightnesses to the biases used to interpolate to
+ * lowLightAmbientColorTemperature.
* @param lowLightAmbientColorTemperature
* The ambient color temperature to which we interpolate to based on the low light curve.
* @param highLightAmbientBrightnesses
* The ambient brightness used to map the ambient brightnesses to the biases used to
* interpolate to highLightAmbientColorTemperature.
+ * @param highLightAmbientBrightnessesStrong
+ * The ambient brightness used to map the ambient brightnesses to the biases used to
+ * interpolate to highLightAmbientColorTemperature.
* @param highLightAmbientBiases
* The biases used to map the ambient brightnesses to the biases used to interpolate to
* highLightAmbientColorTemperature.
+ * @param highLightAmbientBiasesStrong
+ * The biases used to map the ambient brightnesses to the biases used to interpolate to
+ * highLightAmbientColorTemperature.
* @param highLightAmbientColorTemperature
* The ambient color temperature to which we interpolate to based on the high light curve.
* @param ambientColorTemperatures
@@ -170,11 +189,17 @@ public class DisplayWhiteBalanceController implements
@NonNull AmbientFilter colorTemperatureFilter,
@NonNull DisplayWhiteBalanceThrottler throttler,
float[] lowLightAmbientBrightnesses,
+ float[] lowLightAmbientBrightnessesStrong,
float[] lowLightAmbientBiases,
+ float[] lowLightAmbientBiasesStrong,
float lowLightAmbientColorTemperature,
+ float lowLightAmbientColorTemperatureStrong,
float[] highLightAmbientBrightnesses,
+ float[] highLightAmbientBrightnessesStrong,
float[] highLightAmbientBiases,
+ float[] highLightAmbientBiasesStrong,
float highLightAmbientColorTemperature,
+ float highLightAmbientColorTemperatureStrong,
float[] ambientColorTemperatures,
float[] displayColorTemperatures,
float[] strongAmbientColorTemperatures,
@@ -188,7 +213,9 @@ public class DisplayWhiteBalanceController implements
mColorTemperatureFilter = colorTemperatureFilter;
mThrottler = throttler;
mLowLightAmbientColorTemperature = lowLightAmbientColorTemperature;
+ mLowLightAmbientColorTemperatureStrong = lowLightAmbientColorTemperatureStrong;
mHighLightAmbientColorTemperature = highLightAmbientColorTemperature;
+ mHighLightAmbientColorTemperatureStrong = highLightAmbientColorTemperatureStrong;
mAmbientColorTemperature = -1.0f;
mPendingAmbientColorTemperature = -1.0f;
mLastAmbientColorTemperature = -1.0f;
@@ -214,6 +241,23 @@ public class DisplayWhiteBalanceController implements
}
try {
+ mLowLightAmbientBrightnessToBiasSplineStrong = new Spline.LinearSpline(
+ lowLightAmbientBrightnessesStrong, lowLightAmbientBiasesStrong);
+ } catch (Exception e) {
+ Slog.e(TAG, "failed to create strong low light ambient brightness to bias spline.", e);
+ mLowLightAmbientBrightnessToBiasSplineStrong = null;
+ }
+ if (mLowLightAmbientBrightnessToBiasSplineStrong != null) {
+ if (mLowLightAmbientBrightnessToBiasSplineStrong.interpolate(0.0f) != 0.0f
+ || mLowLightAmbientBrightnessToBiasSplineStrong.interpolate(
+ Float.POSITIVE_INFINITY) != 1.0f) {
+ Slog.d(TAG, "invalid strong low light ambient brightness to bias spline, "
+ + "bias must begin at 0.0 and end at 1.0.");
+ mLowLightAmbientBrightnessToBiasSplineStrong = null;
+ }
+ }
+
+ try {
mHighLightAmbientBrightnessToBiasSpline = new Spline.LinearSpline(
highLightAmbientBrightnesses, highLightAmbientBiases);
} catch (Exception e) {
@@ -230,6 +274,23 @@ public class DisplayWhiteBalanceController implements
}
}
+ try {
+ mHighLightAmbientBrightnessToBiasSplineStrong = new Spline.LinearSpline(
+ highLightAmbientBrightnessesStrong, highLightAmbientBiasesStrong);
+ } catch (Exception e) {
+ Slog.e(TAG, "failed to create strong high light ambient brightness to bias spline.", e);
+ mHighLightAmbientBrightnessToBiasSplineStrong = null;
+ }
+ if (mHighLightAmbientBrightnessToBiasSplineStrong != null) {
+ if (mHighLightAmbientBrightnessToBiasSplineStrong.interpolate(0.0f) != 0.0f
+ || mHighLightAmbientBrightnessToBiasSplineStrong.interpolate(
+ Float.POSITIVE_INFINITY) != 1.0f) {
+ Slog.d(TAG, "invalid strong high light ambient brightness to bias spline, "
+ + "bias must begin at 0.0 and end at 1.0.");
+ mHighLightAmbientBrightnessToBiasSplineStrong = null;
+ }
+ }
+
if (mLowLightAmbientBrightnessToBiasSpline != null &&
mHighLightAmbientBrightnessToBiasSpline != null) {
if (lowLightAmbientBrightnesses[lowLightAmbientBrightnesses.length - 1] >
@@ -241,6 +302,18 @@ public class DisplayWhiteBalanceController implements
}
}
+ if (mLowLightAmbientBrightnessToBiasSplineStrong != null
+ && mHighLightAmbientBrightnessToBiasSplineStrong != null) {
+ if (lowLightAmbientBrightnessesStrong[lowLightAmbientBrightnessesStrong.length - 1]
+ > highLightAmbientBrightnessesStrong[0]) {
+ Slog.d(TAG,
+ "invalid strong low light and high light ambient brightness to bias "
+ + "spline combination, defined domains must not intersect.");
+ mLowLightAmbientBrightnessToBiasSplineStrong = null;
+ mHighLightAmbientBrightnessToBiasSplineStrong = null;
+ }
+ }
+
try {
mAmbientToDisplayColorTemperatureSpline = new Spline.LinearSpline(
ambientColorTemperatures, displayColorTemperatures);
@@ -365,7 +438,11 @@ public class DisplayWhiteBalanceController implements
mColorTemperatureFilter.dump(writer);
mThrottler.dump(writer);
writer.println(" mLowLightAmbientColorTemperature=" + mLowLightAmbientColorTemperature);
+ writer.println(" mLowLightAmbientColorTemperatureStrong="
+ + mLowLightAmbientColorTemperatureStrong);
writer.println(" mHighLightAmbientColorTemperature=" + mHighLightAmbientColorTemperature);
+ writer.println(" mHighLightAmbientColorTemperatureStrong="
+ + mHighLightAmbientColorTemperatureStrong);
writer.println(" mAmbientColorTemperature=" + mAmbientColorTemperature);
writer.println(" mPendingAmbientColorTemperature=" + mPendingAmbientColorTemperature);
writer.println(" mLastAmbientColorTemperature=" + mLastAmbientColorTemperature);
@@ -377,8 +454,12 @@ public class DisplayWhiteBalanceController implements
+ mStrongAmbientToDisplayColorTemperatureSpline);
writer.println(" mLowLightAmbientBrightnessToBiasSpline="
+ mLowLightAmbientBrightnessToBiasSpline);
+ writer.println(" mLowLightAmbientBrightnessToBiasSplineStrong="
+ + mLowLightAmbientBrightnessToBiasSplineStrong);
writer.println(" mHighLightAmbientBrightnessToBiasSpline="
+ mHighLightAmbientBrightnessToBiasSpline);
+ writer.println(" mHighLightAmbientBrightnessToBiasSplineStrong="
+ + mHighLightAmbientBrightnessToBiasSplineStrong);
}
@Override // AmbientSensor.AmbientBrightnessSensor.Callbacks
@@ -400,6 +481,17 @@ public class DisplayWhiteBalanceController implements
*/
public void updateAmbientColorTemperature() {
final long time = System.currentTimeMillis();
+ final float lowLightAmbientColorTemperature = mStrongModeEnabled
+ ? mLowLightAmbientColorTemperatureStrong : mLowLightAmbientColorTemperature;
+ final float highLightAmbientColorTemperature = mStrongModeEnabled
+ ? mHighLightAmbientColorTemperatureStrong : mHighLightAmbientColorTemperature;
+ final Spline.LinearSpline lowLightAmbientBrightnessToBiasSpline = mStrongModeEnabled
+ ? mLowLightAmbientBrightnessToBiasSplineStrong
+ : mLowLightAmbientBrightnessToBiasSpline;
+ final Spline.LinearSpline highLightAmbientBrightnessToBiasSpline = mStrongModeEnabled
+ ? mHighLightAmbientBrightnessToBiasSplineStrong
+ : mHighLightAmbientBrightnessToBiasSpline;
+
float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time);
mLatestAmbientColorTemperature = ambientColorTemperature;
@@ -423,19 +515,19 @@ public class DisplayWhiteBalanceController implements
mLatestAmbientBrightness = ambientBrightness;
if (ambientColorTemperature != -1.0f && ambientBrightness != -1.0f
- && mLowLightAmbientBrightnessToBiasSpline != null) {
- float bias = mLowLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
+ && lowLightAmbientBrightnessToBiasSpline != null) {
+ float bias = lowLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
ambientColorTemperature =
bias * ambientColorTemperature + (1.0f - bias)
- * mLowLightAmbientColorTemperature;
+ * lowLightAmbientColorTemperature;
mLatestLowLightBias = bias;
}
if (ambientColorTemperature != -1.0f && ambientBrightness != -1.0f
- && mHighLightAmbientBrightnessToBiasSpline != null) {
- float bias = mHighLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
+ && highLightAmbientBrightnessToBiasSpline != null) {
+ float bias = highLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
ambientColorTemperature =
(1.0f - bias) * ambientColorTemperature + bias
- * mHighLightAmbientColorTemperature;
+ * highLightAmbientColorTemperature;
mLatestHighLightBias = bias;
}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
index 62f813f2857a..39e6b3f288fb 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
@@ -70,21 +70,39 @@ public class DisplayWhiteBalanceFactory {
final float[] displayWhiteBalanceLowLightAmbientBrightnesses = getFloatArray(resources,
com.android.internal.R.array
.config_displayWhiteBalanceLowLightAmbientBrightnesses);
+ final float[] displayWhiteBalanceLowLightAmbientBrightnessesStrong = getFloatArray(
+ resources, com.android.internal.R.array
+ .config_displayWhiteBalanceLowLightAmbientBrightnessesStrong);
final float[] displayWhiteBalanceLowLightAmbientBiases = getFloatArray(resources,
com.android.internal.R.array
.config_displayWhiteBalanceLowLightAmbientBiases);
+ final float[] displayWhiteBalanceLowLightAmbientBiasesStrong = getFloatArray(resources,
+ com.android.internal.R.array
+ .config_displayWhiteBalanceLowLightAmbientBiasesStrong);
final float lowLightAmbientColorTemperature = getFloat(resources,
com.android.internal.R.dimen
.config_displayWhiteBalanceLowLightAmbientColorTemperature);
+ final float lowLightAmbientColorTemperatureStrong = getFloat(resources,
+ com.android.internal.R.dimen
+ .config_displayWhiteBalanceLowLightAmbientColorTemperatureStrong);
final float[] displayWhiteBalanceHighLightAmbientBrightnesses = getFloatArray(resources,
com.android.internal.R.array
.config_displayWhiteBalanceHighLightAmbientBrightnesses);
+ final float[] displayWhiteBalanceHighLightAmbientBrightnessesStrong = getFloatArray(
+ resources, com.android.internal.R.array
+ .config_displayWhiteBalanceHighLightAmbientBrightnessesStrong);
final float[] displayWhiteBalanceHighLightAmbientBiases = getFloatArray(resources,
com.android.internal.R.array
.config_displayWhiteBalanceHighLightAmbientBiases);
+ final float[] displayWhiteBalanceHighLightAmbientBiasesStrong = getFloatArray(resources,
+ com.android.internal.R.array
+ .config_displayWhiteBalanceHighLightAmbientBiasesStrong);
final float highLightAmbientColorTemperature = getFloat(resources,
com.android.internal.R.dimen
.config_displayWhiteBalanceHighLightAmbientColorTemperature);
+ final float highLightAmbientColorTemperatureStrong = getFloat(resources,
+ com.android.internal.R.dimen
+ .config_displayWhiteBalanceHighLightAmbientColorTemperatureStrong);
final float[] ambientColorTemperatures = getFloatArray(resources,
com.android.internal.R.array.config_displayWhiteBalanceAmbientColorTemperatures);
final float[] displayColorTemperatures = getFloatArray(resources,
@@ -100,9 +118,15 @@ public class DisplayWhiteBalanceFactory {
final DisplayWhiteBalanceController controller = new DisplayWhiteBalanceController(
brightnessSensor, brightnessFilter, colorTemperatureSensor, colorTemperatureFilter,
throttler, displayWhiteBalanceLowLightAmbientBrightnesses,
- displayWhiteBalanceLowLightAmbientBiases, lowLightAmbientColorTemperature,
+ displayWhiteBalanceLowLightAmbientBrightnessesStrong,
+ displayWhiteBalanceLowLightAmbientBiases,
+ displayWhiteBalanceLowLightAmbientBiasesStrong, lowLightAmbientColorTemperature,
+ lowLightAmbientColorTemperatureStrong,
displayWhiteBalanceHighLightAmbientBrightnesses,
- displayWhiteBalanceHighLightAmbientBiases, highLightAmbientColorTemperature,
+ displayWhiteBalanceHighLightAmbientBrightnessesStrong,
+ displayWhiteBalanceHighLightAmbientBiases,
+ displayWhiteBalanceHighLightAmbientBiasesStrong, highLightAmbientColorTemperature,
+ highLightAmbientColorTemperatureStrong,
ambientColorTemperatures, displayColorTemperatures, strongAmbientColorTemperatures,
strongDisplayColorTemperatures, lightModeAllowed);
brightnessSensor.setCallbacks(controller);
diff --git a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
index d764ec41b3b9..9172dc02a4ac 100644
--- a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
@@ -32,6 +32,10 @@ final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction {
private int mInitialAudioStatusRetriesLeft = 2;
+ // Flag to notify AudioService of the next audio status reported,
+ // regardless of whether the audio status changed.
+ private boolean mForceNextAudioStatusUpdate = false;
+
private static final int STATE_WAIT_FOR_INITIAL_AUDIO_STATUS = 1;
private static final int STATE_MONITOR_AUDIO_STATUS = 2;
@@ -70,6 +74,17 @@ final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction {
return false;
}
+
+ /**
+ * If AVB has been enabled, send <Give Audio Status> and notify AudioService of the response.
+ */
+ void requestAndUpdateAudioStatus() {
+ if (mState == STATE_MONITOR_AUDIO_STATUS) {
+ mForceNextAudioStatusUpdate = true;
+ sendGiveAudioStatus();
+ }
+ }
+
private boolean handleReportAudioStatus(HdmiCecMessage cmd) {
if (mTargetAddress != cmd.getSource() || cmd.getParams().length == 0) {
return false;
@@ -89,12 +104,15 @@ final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction {
localDevice().getService().enableAbsoluteVolumeBehavior(audioStatus);
mState = STATE_MONITOR_AUDIO_STATUS;
} else if (mState == STATE_MONITOR_AUDIO_STATUS) {
- if (audioStatus.getVolume() != mLastAudioStatus.getVolume()) {
+ if (mForceNextAudioStatusUpdate
+ || audioStatus.getVolume() != mLastAudioStatus.getVolume()) {
localDevice().getService().notifyAvbVolumeChange(audioStatus.getVolume());
}
- if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
+ if (mForceNextAudioStatusUpdate
+ || audioStatus.getMute() != mLastAudioStatus.getMute()) {
localDevice().getService().notifyAvbMuteChange(audioStatus.getMute());
}
+ mForceNextAudioStatusUpdate = false;
}
mLastAudioStatus = audioStatus;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 207d38eb17ac..0671464a1ed4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -1048,6 +1048,19 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice {
}
/**
+ * If AVB has been enabled, request the System Audio device's audio status and notify
+ * AudioService of its response.
+ */
+ @ServiceThreadOnly
+ void requestAndUpdateAvbAudioStatus() {
+ assertRunOnServiceThread();
+ for (AbsoluteVolumeAudioStatusAction action :
+ getActions(AbsoluteVolumeAudioStatusAction.class)) {
+ action.requestAndUpdateAudioStatus();
+ }
+ }
+
+ /**
* Determines whether {@code targetAddress} supports <Set Audio Volume Level>. Does two things
* in parallel: send <Give Features> (to get <Report Features> in response),
* and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response).
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 5abb2b5ae861..99fa3a3271aa 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4661,6 +4661,13 @@ public class HdmiControlService extends SystemService {
// same keycode for all three mute options.
keyCode = KeyEvent.KEYCODE_VOLUME_MUTE;
break;
+ case AudioManager.ADJUST_SAME:
+ // Query the current audio status of the Audio System and display UI for it
+ // Only for TVs, because Playback devices don't display UI when using AVB
+ if (tv() != null) {
+ tv().requestAndUpdateAvbAudioStatus();
+ }
+ return;
default:
return;
}
diff --git a/services/core/java/com/android/server/input/FocusEventDebugView.java b/services/core/java/com/android/server/input/FocusEventDebugView.java
index 02e0eb053c88..39519ef79b1a 100644
--- a/services/core/java/com/android/server/input/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/FocusEventDebugView.java
@@ -28,7 +28,7 @@ import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Typeface;
-import android.util.Log;
+import android.util.DisplayMetrics;
import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
@@ -38,22 +38,26 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RoundedCorner;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.animation.AccelerateInterpolator;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Displays focus events, such as physical keyboard KeyEvents and non-pointer MotionEvents on
* the screen.
*/
-class FocusEventDebugView extends LinearLayout {
+class FocusEventDebugView extends RelativeLayout {
private static final String TAG = FocusEventDebugView.class.getSimpleName();
@@ -80,18 +84,24 @@ class FocusEventDebugView extends LinearLayout {
private PressedKeyContainer mPressedKeyContainer;
@Nullable
private PressedKeyContainer mPressedModifierContainer;
+ private final Supplier<RotaryInputValueView> mRotaryInputValueViewFactory;
+ @Nullable
+ private RotaryInputValueView mRotaryInputValueView;
- FocusEventDebugView(Context c, InputManagerService service) {
+ @VisibleForTesting
+ FocusEventDebugView(Context c, InputManagerService service,
+ Supplier<RotaryInputValueView> rotaryInputValueViewFactory) {
super(c);
setFocusableInTouchMode(true);
mService = service;
+ mRotaryInputValueViewFactory = rotaryInputValueViewFactory;
final var dm = mContext.getResources().getDisplayMetrics();
mOuterPadding = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, OUTER_PADDING_DP, dm);
+ }
- setOrientation(HORIZONTAL);
- setLayoutDirection(LAYOUT_DIRECTION_RTL);
- setGravity(Gravity.START | Gravity.BOTTOM);
+ FocusEventDebugView(Context c, InputManagerService service) {
+ this(c, service, () -> new RotaryInputValueView(c));
}
@Override
@@ -100,13 +110,13 @@ class FocusEventDebugView extends LinearLayout {
final RoundedCorner bottomLeft =
insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
- if (bottomLeft != null) {
+ if (bottomLeft != null && !insets.isRound()) {
paddingBottom = bottomLeft.getRadius();
}
final RoundedCorner bottomRight =
insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
- if (bottomRight != null) {
+ if (bottomRight != null && !insets.isRound()) {
paddingBottom = Math.max(paddingBottom, bottomRight.getRadius());
}
@@ -151,7 +161,7 @@ class FocusEventDebugView extends LinearLayout {
}
mPressedKeyContainer = new PressedKeyContainer(mContext);
- mPressedKeyContainer.setOrientation(HORIZONTAL);
+ mPressedKeyContainer.setOrientation(LinearLayout.HORIZONTAL);
mPressedKeyContainer.setGravity(Gravity.RIGHT | Gravity.BOTTOM);
mPressedKeyContainer.setLayoutDirection(LAYOUT_DIRECTION_LTR);
final var scroller = new HorizontalScrollView(mContext);
@@ -160,15 +170,23 @@ class FocusEventDebugView extends LinearLayout {
scroller.addOnLayoutChangeListener(
(view, l, t, r, b, ol, ot, or, ob) -> scroller.fullScroll(View.FOCUS_RIGHT));
scroller.setHorizontalFadingEdgeEnabled(true);
- addView(scroller, new LayoutParams(0, WRAP_CONTENT, 1));
+ LayoutParams scrollerLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ scrollerLayoutParams.addRule(ALIGN_PARENT_BOTTOM);
+ scrollerLayoutParams.addRule(ALIGN_PARENT_RIGHT);
+ addView(scroller, scrollerLayoutParams);
mPressedModifierContainer = new PressedKeyContainer(mContext);
- mPressedModifierContainer.setOrientation(VERTICAL);
+ mPressedModifierContainer.setOrientation(LinearLayout.VERTICAL);
mPressedModifierContainer.setGravity(Gravity.LEFT | Gravity.BOTTOM);
- addView(mPressedModifierContainer, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ LayoutParams modifierLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ modifierLayoutParams.addRule(ALIGN_PARENT_BOTTOM);
+ modifierLayoutParams.addRule(ALIGN_PARENT_LEFT);
+ modifierLayoutParams.addRule(LEFT_OF, scroller.getId());
+ addView(mPressedModifierContainer, modifierLayoutParams);
}
- private void handleUpdateShowRotaryInput(boolean enabled) {
+ @VisibleForTesting
+ void handleUpdateShowRotaryInput(boolean enabled) {
if (enabled == showRotaryInput()) {
return;
}
@@ -176,10 +194,18 @@ class FocusEventDebugView extends LinearLayout {
if (!enabled) {
mFocusEventDebugGlobalMonitor.dispose();
mFocusEventDebugGlobalMonitor = null;
+ removeView(mRotaryInputValueView);
+ mRotaryInputValueView = null;
return;
}
mFocusEventDebugGlobalMonitor = new FocusEventDebugGlobalMonitor(this, mService);
+
+ mRotaryInputValueView = mRotaryInputValueViewFactory.get();
+ LayoutParams valueLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ valueLayoutParams.addRule(CENTER_HORIZONTAL);
+ valueLayoutParams.addRule(ALIGN_PARENT_BOTTOM);
+ addView(mRotaryInputValueView, valueLayoutParams);
}
/** Report a key event to the debug view. */
@@ -242,14 +268,14 @@ class FocusEventDebugView extends LinearLayout {
keyEvent.recycle();
}
- private void handleRotaryInput(MotionEvent motionEvent) {
+ @VisibleForTesting
+ void handleRotaryInput(MotionEvent motionEvent) {
if (!showRotaryInput()) {
return;
}
float scrollAxisValue = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL);
- // TODO(b/286086154): replace log with visualization.
- Log.d(TAG, "ROTARY INPUT: " + String.valueOf(scrollAxisValue));
+ mRotaryInputValueView.updateValue(scrollAxisValue);
motionEvent.recycle();
}
@@ -308,7 +334,14 @@ class FocusEventDebugView extends LinearLayout {
/** Determine whether to show rotary input by checking one of the rotary-related objects. */
private boolean showRotaryInput() {
- return mFocusEventDebugGlobalMonitor != null;
+ return mRotaryInputValueView != null;
+ }
+
+ /**
+ * Converts a dimension in scaled pixel units to integer display pixels.
+ */
+ private static int applyDimensionSp(int dimensionSp, DisplayMetrics dm) {
+ return (int) TypedValue.applyDimension(COMPLEX_UNIT_SP, dimensionSp, dm);
}
private static class PressedKeyView extends TextView {
@@ -419,4 +452,66 @@ class FocusEventDebugView extends LinearLayout {
invalidate();
}
}
+
+ /** Draws the most recent rotary input value and indicates whether the source is active. */
+ @VisibleForTesting
+ static class RotaryInputValueView extends TextView {
+
+ private static final int INACTIVE_TEXT_COLOR = 0xffff00ff;
+ private static final int ACTIVE_TEXT_COLOR = 0xff420f28;
+ private static final int TEXT_SIZE_SP = 8;
+ private static final int SIDE_PADDING_SP = 4;
+ /** Determines how long the active status lasts. */
+ private static final int ACTIVE_STATUS_DURATION = 250 /* milliseconds */;
+ private static final ColorFilter ACTIVE_BACKGROUND_FILTER =
+ new ColorMatrixColorFilter(new float[]{
+ 0, 0, 0, 0, 255, // red
+ 0, 0, 0, 0, 0, // green
+ 0, 0, 0, 0, 255, // blue
+ 0, 0, 0, 0, 200 // alpha
+ });
+
+ private final Runnable mUpdateActivityStatusCallback = () -> updateActivityStatus(false);
+ private final float mScaledVerticalScrollFactor;
+
+ @VisibleForTesting
+ RotaryInputValueView(Context c) {
+ super(c);
+
+ DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+ mScaledVerticalScrollFactor = ViewConfiguration.get(c).getScaledVerticalScrollFactor();
+
+ setText(getFormattedValue(0));
+ setTextColor(INACTIVE_TEXT_COLOR);
+ setTextSize(applyDimensionSp(TEXT_SIZE_SP, dm));
+ setPaddingRelative(applyDimensionSp(SIDE_PADDING_SP, dm), 0,
+ applyDimensionSp(SIDE_PADDING_SP, dm), 0);
+ setTypeface(null, Typeface.BOLD);
+ setBackgroundResource(R.drawable.focus_event_rotary_input_background);
+ }
+
+ void updateValue(float value) {
+ removeCallbacks(mUpdateActivityStatusCallback);
+
+ setText(getFormattedValue(value * mScaledVerticalScrollFactor));
+
+ updateActivityStatus(true);
+ postDelayed(mUpdateActivityStatusCallback, ACTIVE_STATUS_DURATION);
+ }
+
+ @VisibleForTesting
+ void updateActivityStatus(boolean active) {
+ if (active) {
+ setTextColor(ACTIVE_TEXT_COLOR);
+ getBackground().setColorFilter(ACTIVE_BACKGROUND_FILTER);
+ } else {
+ setTextColor(INACTIVE_TEXT_COLOR);
+ getBackground().clearColorFilter();
+ }
+ }
+
+ private static String getFormattedValue(float value) {
+ return String.format("%s%.1f", value < 0 ? "-" : "+", Math.abs(value));
+ }
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2fc48294ae70..8e7baf26984a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5959,6 +5959,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mVisibilityStateComputer.dump(pw);
p.println(" mInFullscreenMode=" + mInFullscreenMode);
p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
+ p.println(" ENABLE_HIDE_IME_CAPTION_BAR="
+ + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR);
p.println(" mSettingsObserver=" + mSettingsObserver);
p.println(" mStylusIds=" + (mStylusIds != null
? Arrays.toString(mStylusIds.toArray()) : ""));
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 3a20cd962667..f2242bf48dcd 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -2188,11 +2188,10 @@ public class MediaSessionService extends SystemService implements Monitor {
MediaSessionRecordImpl session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
: mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
- boolean preferSuggestedStream = false;
- if (isValidLocalStreamType(suggestedStream)
- && AudioSystem.isStreamActive(suggestedStream, 0)) {
- preferSuggestedStream = true;
- }
+ boolean preferSuggestedStream =
+ isValidLocalStreamType(suggestedStream)
+ && AudioSystem.isStreamActive(suggestedStream, 0);
+
if (session == null || preferSuggestedStream) {
if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Adjusting suggestedStream=" + suggestedStream + " by " + direction
diff --git a/services/core/java/com/android/server/net/watchlist/OWNERS b/services/core/java/com/android/server/net/watchlist/OWNERS
index a3d4b85367cf..d0c4e553ad8c 100644
--- a/services/core/java/com/android/server/net/watchlist/OWNERS
+++ b/services/core/java/com/android/server/net/watchlist/OWNERS
@@ -1,3 +1,2 @@
-rickywai@google.com
alanstokes@google.com
simonjw@google.com
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index b2d3fca16112..100c63863f7f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,8 +25,6 @@ import static android.service.notification.NotificationListenerService.Ranking.U
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.IActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -48,6 +46,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.VibrationEffect;
import android.provider.Settings;
@@ -98,8 +97,7 @@ public final class NotificationRecord {
// the period after which a notification is updated where it can make sound
private static final int MAX_SOUND_DELAY_MS = 2000;
private final StatusBarNotification sbn;
- IActivityManager mAm;
- UriGrantsManagerInternal mUgmInternal;
+ private final UriGrantsManagerInternal mUgmInternal;
final int mTargetSdkVersion;
final int mOriginalFlags;
private final Context mContext;
@@ -223,7 +221,6 @@ public final class NotificationRecord {
this.sbn = sbn;
mTargetSdkVersion = LocalServices.getService(PackageManagerInternal.class)
.getPackageTargetSdkVersion(sbn.getPackageName());
- mAm = ActivityManager.getService();
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mOriginalFlags = sbn.getNotification().flags;
mRankingTimeMs = calculateRankingTimeMs(0L);
@@ -1387,18 +1384,27 @@ public final class NotificationRecord {
* Collect all {@link Uri} that should have permission granted to whoever
* will be rendering it.
*/
- protected void calculateGrantableUris() {
- final Notification notification = getNotification();
- notification.visitUris((uri) -> {
- visitGrantableUri(uri, false, false);
- });
-
- if (notification.getChannelId() != null) {
- NotificationChannel channel = getChannel();
- if (channel != null) {
- visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
- & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+ private void calculateGrantableUris() {
+ Trace.beginSection("NotificationRecord.calculateGrantableUris");
+ try {
+ // We can't grant URI permissions from system.
+ final int sourceUid = getSbn().getUid();
+ if (sourceUid == android.os.Process.SYSTEM_UID) return;
+
+ final Notification notification = getNotification();
+ notification.visitUris((uri) -> {
+ visitGrantableUri(uri, false, false);
+ });
+
+ if (notification.getChannelId() != null) {
+ NotificationChannel channel = getChannel();
+ if (channel != null) {
+ visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
+ & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+ }
}
+ } finally {
+ Trace.endSection();
}
}
@@ -1413,13 +1419,14 @@ public final class NotificationRecord {
private void visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
- // We can't grant Uri permissions from system
- final int sourceUid = getSbn().getUid();
- if (sourceUid == android.os.Process.SYSTEM_UID) return;
+ if (mGrantableUris != null && mGrantableUris.contains(uri)) {
+ return; // already verified this URI
+ }
+ final int sourceUid = getSbn().getUid();
final long ident = Binder.clearCallingIdentity();
try {
- // This will throw SecurityException if caller can't grant
+ // This will throw a SecurityException if the caller can't grant.
mUgmInternal.checkGrantUriPermission(sourceUid, null,
ContentProvider.getUriWithoutUserId(uri),
Intent.FLAG_GRANT_READ_URI_PERMISSION,
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 6e7560563c8d..f9876299e8e0 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -487,6 +487,8 @@ public interface Computer extends PackageDataSnapshot {
boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId);
+ boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId);
+
boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId);
@NonNull
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 7d878ece3176..1cfc7d76919a 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4925,8 +4925,8 @@ public class ComputerEngine implements Computer {
}
}
- @Override
- public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) {
+ private PackageUserStateInternal getUserStageOrDefaultForUser(@NonNull String packageName,
+ int userId) {
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
false /* checkShell */, "isPackageSuspendedForUser for user " + userId);
@@ -4934,7 +4934,17 @@ public class ComputerEngine implements Computer {
if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
throw new IllegalArgumentException("Unknown target package: " + packageName);
}
- return ps.getUserStateOrDefault(userId).isSuspended();
+ return ps.getUserStateOrDefault(userId);
+ }
+
+ @Override
+ public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) {
+ return getUserStageOrDefaultForUser(packageName, userId).isSuspended();
+ }
+
+ @Override
+ public boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId) {
+ return getUserStageOrDefaultForUser(packageName, userId).isQuarantined();
}
@Override
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index fd47846af9ef..76203ac7650d 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -955,6 +955,13 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub {
@Override
@Deprecated
+ public final boolean isPackageQuarantinedForUser(@NonNull String packageName,
+ @UserIdInt int userId) {
+ return snapshot().isPackageQuarantinedForUser(packageName, userId);
+ }
+
+ @Override
+ @Deprecated
public final boolean isSafeMode() {
// allow instant applications
return mService.getSafeMode();
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 1a5591c98269..01c27348db94 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -49,8 +49,8 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
import android.os.storage.VolumeInfo;
-import android.text.TextUtils;
import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseIntArray;
@@ -64,6 +64,7 @@ import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUtils;
import java.io.File;
+import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -220,9 +221,7 @@ public final class MovePackageHelper {
}
try {
- for (int index = 0; index < installedUserIds.length; index++) {
- prepareUserDataForVolumeIfRequired(volumeUuid, installedUserIds[index], storage);
- }
+ prepareUserStorageForMove(currentVolumeUuid, volumeUuid, installedUserIds);
} catch (RuntimeException e) {
freezer.close();
throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
@@ -376,27 +375,20 @@ public final class MovePackageHelper {
return true;
}
- private void prepareUserDataForVolumeIfRequired(String volumeUuid, int userId,
- StorageManager storageManager) {
- if (TextUtils.isEmpty(volumeUuid)
- || doesDataDirectoryExistForUser(volumeUuid, userId)) {
- return;
- }
+ private void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
+ int[] userIds) {
if (DEBUG_INSTALL) {
- Slog.d(TAG, "Preparing user directories for user u" + userId + " for UUID "
- + volumeUuid);
+ Slog.d(TAG, "Preparing user directories before moving app, from UUID " + fromVolumeUuid
+ + " to UUID " + toVolumeUuid);
}
- final UserInfo user = mPm.mUserManager.getUserInfo(userId);
- if (user == null) return;
- // This call is same as StorageEventHelper#loadPrivatePackagesInner which prepares
- // the storage before reconciling apps
- storageManager.prepareUserStorage(volumeUuid, user.id, user.serialNumber,
- StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
- }
-
- private boolean doesDataDirectoryExistForUser(String uuid, int userId) {
- final File userDirectoryFile = Environment.getDataUserCeDirectory(uuid, userId);
- return userDirectoryFile != null && userDirectoryFile.exists();
+ final StorageManagerInternal smInternal =
+ mPm.mInjector.getLocalService(StorageManagerInternal.class);
+ final ArrayList<UserInfo> users = new ArrayList<>();
+ for (int userId : userIds) {
+ final UserInfo user = mPm.mUserManager.getUserInfo(userId);
+ users.add(user);
+ }
+ smInternal.prepareUserStorageForMove(fromVolumeUuid, toVolumeUuid, users);
}
public static class MoveCallbacks extends Handler {
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 6efd06762678..651845e71924 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -755,9 +755,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal {
@Override
public boolean isPackageQuarantined(@NonNull String packageName,
@UserIdInt int userId) {
- final PackageStateInternal packageState = getPackageStateInternal(packageName);
- return (packageState == null) ? false
- : packageState.getUserStateOrDefault(userId).isQuarantined();
+ return snapshot().isPackageQuarantinedForUser(packageName, userId);
}
@NonNull
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index ceae1fe3bcb9..8bdbe04ec4e6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -226,6 +226,8 @@ class PackageManagerShellCommand extends ShellCommand {
return runPath();
case "dump":
return runDump();
+ case "dump-package":
+ return runDumpPackage();
case "list":
return runList();
case "gc":
@@ -978,6 +980,7 @@ class PackageManagerShellCommand extends ShellCommand {
boolean listInstaller = false;
boolean showUid = false;
boolean showVersionCode = false;
+ boolean listQuarantinedOnly = false;
boolean listApexOnly = false;
boolean showStopped = false;
int uid = -1;
@@ -1008,6 +1011,9 @@ class PackageManagerShellCommand extends ShellCommand {
case "-s":
listSystem = true;
break;
+ case "-q":
+ listQuarantinedOnly = true;
+ break;
case "-U":
showUid = true;
break;
@@ -1093,6 +1099,10 @@ class PackageManagerShellCommand extends ShellCommand {
|| (listApexOnly && !isApex)) {
continue;
}
+ if (listQuarantinedOnly && !mInterface.isPackageQuarantinedForUser(info.packageName,
+ translatedUserId)) {
+ continue;
+ }
String name = null;
if (showSdks) {
@@ -3598,6 +3608,23 @@ class PackageManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runDumpPackage() {
+ String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified");
+ return 1;
+ }
+ try {
+ ((IBinder) mInterface).dump(getOutFileDescriptor(), new String[]{pkg});
+ } catch (Throwable e) {
+ PrintWriter pw = getErrPrintWriter();
+ pw.println("Failure dumping service:");
+ e.printStackTrace(pw);
+ pw.flush();
+ }
+ return 0;
+ }
+
private int runSetHarmfulAppWarning() throws RemoteException {
int userId = UserHandle.USER_CURRENT;
@@ -4282,6 +4309,9 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" dump PACKAGE");
pw.println(" Print various system state associated with the given PACKAGE.");
pw.println("");
+ pw.println(" dump-package PACKAGE");
+ pw.println(" Print package manager state associated with the given PACKAGE.");
+ pw.println("");
pw.println(" has-feature FEATURE_NAME [version]");
pw.println(" Prints true and returns exit status 0 when system has a FEATURE_NAME,");
pw.println(" otherwise prints false and returns exit status 1");
@@ -4299,7 +4329,7 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" Options:");
pw.println(" -v: shows the location of the library in the device's filesystem");
pw.println("");
- pw.println(" list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] ");
+ pw.println(" list packages [-f] [-d] [-e] [-s] [-q] [-3] [-i] [-l] [-u] [-U] ");
pw.println(" [--show-versioncode] [--apex-only] [--factory-only]");
pw.println(" [--uid UID] [--user USER_ID] [FILTER]");
pw.println(" Prints all packages; optionally only those whose name contains");
@@ -4309,6 +4339,7 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" -d: filter to only show disabled packages");
pw.println(" -e: filter to only show enabled packages");
pw.println(" -s: filter to only show system packages");
+ pw.println(" -q: filter to only show quarantined packages");
pw.println(" -3: filter to only show third party packages");
pw.println(" -i: see the installer for the packages");
pw.println(" -l: ignored (used for compatibility with older releases)");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 385dfcb8e4ec..f2797eb48305 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4907,6 +4907,11 @@ public class UserManagerService extends IUserManager.Stub {
USER_OPERATION_ERROR_UNKNOWN);
}
}
+ if (isMainUser && getMainUserIdUnchecked() != UserHandle.USER_NULL) {
+ throwCheckedUserOperationException(
+ "Cannot add user with FLAG_MAIN as main user already exists.",
+ UserManager.USER_OPERATION_ERROR_MAX_USERS);
+ }
if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) {
throwCheckedUserOperationException(
"Cannot add more users of type " + userType
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3125518a24d5..cba215ad23fd 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -224,6 +224,9 @@ import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
@@ -2690,6 +2693,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (isTransferringSplashScreen()) {
return true;
}
+ // Only do transfer after transaction has done when starting window exist.
+ if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) {
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
+ return true;
+ }
requestCopySplashScreen();
return isTransferringSplashScreen();
}
@@ -2850,11 +2858,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mStartingData == null) {
return;
}
- mStartingData.mWaitForSyncTransactionCommit = false;
- if (mStartingData.mRemoveAfterTransaction) {
- mStartingData.mRemoveAfterTransaction = false;
- removeStartingWindowAnimation(mStartingData.mPrepareRemoveAnimation);
+ final StartingData lastData = mStartingData;
+ lastData.mWaitForSyncTransactionCommit = false;
+ if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_REMOVE_DIRECTLY) {
+ removeStartingWindowAnimation(lastData.mPrepareRemoveAnimation);
+ } else if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_COPY_TO_CLIENT) {
+ removeStartingWindow();
}
+ lastData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
}
void removeStartingWindowAnimation(boolean prepareAnimation) {
@@ -2881,7 +2892,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mStartingData != null) {
if (mStartingData.mWaitForSyncTransactionCommit
|| mTransitionController.inCollectingTransition(startingWindow)) {
- mStartingData.mRemoveAfterTransaction = true;
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
mStartingData.mPrepareRemoveAnimation = prepareAnimation;
return;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index ba242ecd0ec3..01786becda61 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -969,8 +969,10 @@ final class LetterboxUiController {
final Rect innerFrame = hasInheritedLetterboxBehavior()
? mActivityRecord.getBounds() : w.getFrame();
mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
- // We need to notify Shell that letterbox position has changed.
- mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
+ if (mDoubleTapEvent) {
+ // We need to notify Shell that letterbox position has changed.
+ mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
+ }
} else if (mLetterbox != null) {
mLetterbox.hide();
}
@@ -1242,6 +1244,7 @@ final class LetterboxUiController {
? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
: LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
logLetterboxPositionChange(changeToLog);
+ mDoubleTapEvent = true;
} else if (mLetterbox.getInnerFrame().right < x) {
// Moving to the next stop on the right side of the app window: left > center > right.
mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop(
@@ -1252,8 +1255,8 @@ final class LetterboxUiController {
? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
: LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
logLetterboxPositionChange(changeToLog);
+ mDoubleTapEvent = true;
}
- mDoubleTapEvent = true;
// TODO(197549949): Add animation for transition.
mActivityRecord.recomputeConfiguration();
}
@@ -1281,6 +1284,7 @@ final class LetterboxUiController {
? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
: LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
logLetterboxPositionChange(changeToLog);
+ mDoubleTapEvent = true;
} else if (mLetterbox.getInnerFrame().bottom < y) {
// Moving to the next stop on the bottom side of the app window: top > center > bottom.
mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop(
@@ -1291,8 +1295,8 @@ final class LetterboxUiController {
? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
: LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
logLetterboxPositionChange(changeToLog);
+ mDoubleTapEvent = true;
}
- mDoubleTapEvent = true;
// TODO(197549949): Add animation for transition.
mActivityRecord.recomputeConfiguration();
}
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 0af9fe930d83..26abe51c8c34 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -18,4 +18,4 @@ rgl@google.com
yunfanc@google.com
per-file BackgroundActivityStartController.java = set noparent
-per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com, rickywai@google.com
+per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 34806bd023a0..a23547ef1d5b 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import android.annotation.IntDef;
+
import com.android.server.wm.StartingSurfaceController.StartingSurface;
/**
@@ -23,6 +25,20 @@ import com.android.server.wm.StartingSurfaceController.StartingSurface;
*/
public abstract class StartingData {
+ /** Nothing need to do after transaction */
+ static final int AFTER_TRANSACTION_IDLE = 0;
+ /** Remove the starting window directly after transaction done. */
+ static final int AFTER_TRANSACTION_REMOVE_DIRECTLY = 1;
+ /** Do copy splash screen to client after transaction done. */
+ static final int AFTER_TRANSACTION_COPY_TO_CLIENT = 2;
+
+ @IntDef(prefix = { "AFTER_TRANSACTION" }, value = {
+ AFTER_TRANSACTION_IDLE,
+ AFTER_TRANSACTION_REMOVE_DIRECTLY,
+ AFTER_TRANSACTION_COPY_TO_CLIENT,
+ })
+ @interface AfterTransaction {}
+
protected final WindowManagerService mService;
protected final int mTypeParams;
@@ -60,7 +76,7 @@ public abstract class StartingData {
* This starting window should be removed after applying the start transaction of transition,
* which ensures the app window has shown.
*/
- boolean mRemoveAfterTransaction;
+ @AfterTransaction int mRemoveAfterTransaction;
/** Whether to prepare the removal animation. */
boolean mPrepareRemoveAnimation;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 69eddb9fe105..43430dd1eed0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3459,6 +3459,8 @@ class Task extends TaskFragment {
info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.isUserFullscreenOverrideEnabled = top != null
+ && top.mLetterboxUiController.shouldApplyUserFullscreenOverride();
info.isFromLetterboxDoubleTap = top != null && top.mLetterboxUiController.isFromDoubleTap();
if (info.isLetterboxDoubleTapEnabled) {
info.topActivityLetterboxWidth = top.getBounds().width();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 57b6e379c54c..6dc896a92e52 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1175,7 +1175,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
- boolean deferPause) {
+ boolean skipPause) {
ActivityRecord next = topRunningActivity(true /* focusableOnly */);
if (next == null || !next.canResumeByCompat()) {
return false;
@@ -1183,11 +1183,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
next.delayedResume = false;
- // If we are currently pausing an activity, then don't do anything until that is done.
- final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
- if (!allPausedComplete) {
- ProtoLog.v(WM_DEBUG_STATES,
- "resumeTopActivity: Skip resume: some activity pausing.");
+ if (!skipPause && !mRootWindowContainer.allPausedActivitiesComplete()) {
+ // If we aren't skipping pause, then we have to wait for currently pausing activities.
+ ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: some activity pausing.");
return false;
}
@@ -1251,7 +1249,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
lastResumed = lastFocusedRootTask.getTopResumedActivity();
}
- boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
+ boolean pausing = !skipPause && taskDisplayArea.pauseBackTasks(next);
if (mResumedActivity != null) {
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Pausing %s", mResumedActivity);
pausing |= startPausing(mTaskSupervisor.mUserLeaving, false /* uiSleeping */,
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
index ed7f0af9299f..9aa0a4182eb7 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
@@ -77,7 +77,7 @@ inline fun <T> MutableIntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T {
}
operator fun <T> MutableIntMap<T>.minusAssign(key: Int) {
- array.remove(key)
+ array.remove(key).also { array.gc() }
}
fun <T> MutableIntMap<T>.putWithDefault(key: Int, value: T, defaultValue: T): T {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
index b4de5d164e3b..1ed4f8a777d2 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
@@ -77,7 +77,7 @@ inline fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.mutateOrPut(
}
operator fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.minusAssign(key: Int) {
- array.remove(key)
+ array.remove(key).also { array.gc() }
}
operator fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.set(key: Int, value: M) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index f975b6fd1d6f..183a84de0eb0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -64,7 +64,9 @@ public final class AmbientLuxTest {
private static final int AMBIENT_COLOR_TYPE = 20705;
private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5432.1f;
+ private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE_STRONG = 5555.5f;
private static final float HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE = 3456.7f;
+ private static final float HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE_STRONG = 3333.3f;
private Handler mHandler = new Handler(Looper.getMainLooper());
private Sensor mLightSensor;
@@ -78,6 +80,10 @@ public final class AmbientLuxTest {
@Mock private TypedArray mBiases;
@Mock private TypedArray mHighLightBrightnesses;
@Mock private TypedArray mHighLightBiases;
+ @Mock private TypedArray mBrightnessesStrong;
+ @Mock private TypedArray mBiasesStrong;
+ @Mock private TypedArray mHighLightBrightnessesStrong;
+ @Mock private TypedArray mHighLightBiasesStrong;
@Mock private TypedArray mAmbientColorTemperatures;
@Mock private TypedArray mDisplayColorTemperatures;
@Mock private TypedArray mStrongAmbientColorTemperatures;
@@ -108,6 +114,10 @@ public final class AmbientLuxTest {
LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE);
mockResourcesFloat(R.dimen.config_displayWhiteBalanceHighLightAmbientColorTemperature,
HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE);
+ mockResourcesFloat(R.dimen.config_displayWhiteBalanceLowLightAmbientColorTemperatureStrong,
+ LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE_STRONG);
+ mockResourcesFloat(R.dimen.config_displayWhiteBalanceHighLightAmbientColorTemperatureStrong,
+ HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE_STRONG);
when(mResourcesSpy.obtainTypedArray(
R.array.config_displayWhiteBalanceAmbientColorTemperatures))
.thenReturn(mAmbientColorTemperatures);
@@ -133,6 +143,18 @@ public final class AmbientLuxTest {
when(mResourcesSpy.obtainTypedArray(
R.array.config_displayWhiteBalanceHighLightAmbientBiases))
.thenReturn(mHighLightBiases);
+ when(mResourcesSpy.obtainTypedArray(
+ R.array.config_displayWhiteBalanceLowLightAmbientBrightnessesStrong))
+ .thenReturn(mBrightnessesStrong);
+ when(mResourcesSpy.obtainTypedArray(
+ R.array.config_displayWhiteBalanceLowLightAmbientBiasesStrong))
+ .thenReturn(mBiasesStrong);
+ when(mResourcesSpy.obtainTypedArray(
+ R.array.config_displayWhiteBalanceHighLightAmbientBrightnessesStrong))
+ .thenReturn(mHighLightBrightnessesStrong);
+ when(mResourcesSpy.obtainTypedArray(
+ R.array.config_displayWhiteBalanceHighLightAmbientBiasesStrong))
+ .thenReturn(mHighLightBiasesStrong);
mockThrottler();
LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
@@ -388,8 +410,8 @@ public final class AmbientLuxTest {
public void testStrongMode() {
final float lowerBrightness = 10.0f;
final float upperBrightness = 50.0f;
- setBrightnesses(lowerBrightness, upperBrightness);
- setBiases(0.0f, 1.0f);
+ setBrightnessesStrong(lowerBrightness, upperBrightness);
+ setBiasesStrong(0.0f, 1.0f);
final int ambientColorTempLow = 6000;
final int ambientColorTempHigh = 8000;
final int displayColorTempLow = 6400;
@@ -413,7 +435,7 @@ public final class AmbientLuxTest {
setEstimatedBrightnessAndUpdate(controller,
mix(lowerBrightness, upperBrightness, brightnessFraction));
assertEquals(controller.mPendingAmbientColorTemperature,
- mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE,
+ mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE_STRONG,
mix(displayColorTempLow, displayColorTempHigh, ambientTempFraction),
brightnessFraction),
ALLOWED_ERROR_DELTA);
@@ -458,7 +480,7 @@ public final class AmbientLuxTest {
assertEquals(-1.0f, controller.mPendingAmbientColorTemperature, 0);
}
- void mockThrottler() {
+ private void mockThrottler() {
when(mResourcesSpy.getInteger(
R.integer.config_displayWhiteBalanceDecreaseDebounce)).thenReturn(0);
when(mResourcesSpy.getInteger(
@@ -513,10 +535,18 @@ public final class AmbientLuxTest {
setFloatArrayResource(mBrightnesses, vals);
}
+ private void setBrightnessesStrong(float... vals) {
+ setFloatArrayResource(mBrightnessesStrong, vals);
+ }
+
private void setBiases(float... vals) {
setFloatArrayResource(mBiases, vals);
}
+ private void setBiasesStrong(float... vals) {
+ setFloatArrayResource(mBiasesStrong, vals);
+ }
+
private void setHighLightBrightnesses(float... vals) {
setFloatArrayResource(mHighLightBrightnesses, vals);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 78e5a42e6d4c..bdbf4ecbd346 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -63,6 +63,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
+import java.io.File;
+
/**
* Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
*/
@@ -107,6 +109,8 @@ public final class UserManagerServiceTest {
.getTargetContext();
private final SparseArray<UserData> mUsers = new SparseArray<>();
+ private File mTestDir;
+
private Context mSpiedContext;
private @Mock PackageManagerService mMockPms;
@@ -144,17 +148,23 @@ public final class UserManagerServiceTest {
doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
// Must construct UserManagerService in the UiThread
+ mTestDir = new File(mRealContext.getDataDir(), "umstest");
+ mTestDir.mkdirs();
mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
- mPackagesLock, mRealContext.getDataDir(), mUsers);
+ mPackagesLock, mTestDir, mUsers);
mUmi = LocalServices.getService(UserManagerInternal.class);
assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mUmi)
.isNotNull();
}
@After
- public void resetUserManagerInternal() {
+ public void tearDown() {
// LocalServices follows the "Highlander rule" - There can be only one!
LocalServices.removeServiceForTest(UserManagerInternal.class);
+
+ // Clean up test dir to remove persisted user files.
+ assertThat(deleteRecursive(mTestDir)).isTrue();
+ mUsers.clear();
}
@Test
@@ -496,6 +506,14 @@ public final class UserManagerServiceTest {
@Test
public void testMainUser_hasNoCallsOrSMSRestrictionsByDefault() {
+ // Remove the main user so we can add another one
+ for (int i = 0; i < mUsers.size(); i++) {
+ UserData userData = mUsers.valueAt(i);
+ if (userData.info.isMain()) {
+ mUsers.delete(i);
+ break;
+ }
+ }
UserInfo mainUser = mUms.createUserWithThrow("main user", USER_TYPE_FULL_SECONDARY,
UserInfo.FLAG_FULL | UserInfo.FLAG_MAIN);
@@ -621,6 +639,18 @@ public final class UserManagerServiceTest {
userData.mLastEnteredForegroundTimeMillis = timeMillis;
}
+ public boolean deleteRecursive(File file) {
+ if (file.isDirectory()) {
+ for (File item : file.listFiles()) {
+ boolean success = deleteRecursive(item);
+ if (!success) {
+ return false;
+ }
+ }
+ }
+ return file.delete();
+ }
+
private static final class TestUserData extends UserData {
@SuppressWarnings("deprecation")
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 99d66c5bda19..746fb53c7254 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -16,20 +16,42 @@
package com.android.server.biometrics;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.util.Collections.emptySet;
+
import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.server.biometrics.sensors.BiometricNotification;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.File;
+
+@Presubmit
+@SmallTest
public class AuthenticationStatsCollectorTest {
private AuthenticationStatsCollector mAuthenticationStatsCollector;
@@ -40,47 +62,220 @@ public class AuthenticationStatsCollectorTest {
private Context mContext;
@Mock
private Resources mResources;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private FingerprintManager mFingerprintManager;
+ @Mock
+ private FaceManager mFaceManager;
+ @Mock
+ private SharedPreferences mSharedPreferences;
+ @Mock
+ private BiometricNotification mBiometricNotification;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getResources()).thenReturn(mResources);
- when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
- .thenReturn(FRR_THRESHOLD);
+ when(mResources.getFraction(eq(R.fraction.config_biometricNotificationFrrThreshold),
+ anyInt(), anyInt())).thenReturn(FRR_THRESHOLD);
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+
+ when(mContext.getSystemServiceName(FingerprintManager.class))
+ .thenReturn(Context.FINGERPRINT_SERVICE);
+ when(mContext.getSystemService(Context.FINGERPRINT_SERVICE))
+ .thenReturn(mFingerprintManager);
+ when(mContext.getSystemServiceName(FaceManager.class)).thenReturn(Context.FACE_SERVICE);
+ when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
+
+ when(mContext.getSharedPreferences(any(File.class), anyInt()))
+ .thenReturn(mSharedPreferences);
+ when(mSharedPreferences.getStringSet(anyString(), anySet())).thenReturn(emptySet());
mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
- 0 /* modality */);
+ 0 /* modality */, mBiometricNotification);
}
@Test
public void authenticate_authenticationSucceeded_mapShouldBeUpdated() {
// Assert that the user doesn't exist in the map initially.
- assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+ assertThat(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1)).isNull();
- mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated*/);
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated */);
AuthenticationStats authenticationStats =
mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
- assertEquals(USER_ID_1, authenticationStats.getUserId());
- assertEquals(1, authenticationStats.getTotalAttempts());
- assertEquals(0, authenticationStats.getRejectedAttempts());
- assertEquals(0, authenticationStats.getEnrollmentNotifications());
+ assertThat(authenticationStats.getUserId()).isEqualTo(USER_ID_1);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(1);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
}
@Test
public void authenticate_authenticationFailed_mapShouldBeUpdated() {
// Assert that the user doesn't exist in the map initially.
- assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+ assertThat(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1)).isNull();
- mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated*/);
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
AuthenticationStats authenticationStats =
mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
- assertEquals(USER_ID_1, authenticationStats.getUserId());
- assertEquals(1, authenticationStats.getTotalAttempts());
- assertEquals(1, authenticationStats.getRejectedAttempts());
- assertEquals(0, authenticationStats.getEnrollmentNotifications());
+
+ assertThat(authenticationStats.getUserId()).isEqualTo(USER_ID_1);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(1);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(1);
+ assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
+ }
+
+ @Test
+ public void authenticate_frrNotExceeded_notificationNotExceeded_shouldNotSendNotification() {
+
+ mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+ new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+ 40 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+ 0 /* modality */));
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+ // Assert that no notification should be sent.
+ verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
+ verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
+ // Assert that data has been reset.
+ AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+ .getAuthenticationStatsForUser(USER_ID_1);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+ }
+
+ @Test
+ public void authenticate_frrExceeded_notificationExceeded_shouldNotSendNotification() {
+
+ mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+ new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+ 400 /* rejectedAttempts */, 2 /* enrollmentNotifications */,
+ 0 /* modality */));
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+ // Assert that no notification should be sent.
+ verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
+ verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
+ // Assert that data has been reset.
+ AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+ .getAuthenticationStatsForUser(USER_ID_1);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+ }
+
+ @Test
+ public void authenticate_frrExceeded_bothBiometricsEnrolled_shouldNotSendNotification() {
+
+ mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+ new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+ 400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+ 0 /* modality */));
+
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+ .thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+ // Assert that no notification should be sent.
+ verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
+ verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
+ // Assert that data has been reset.
+ AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+ .getAuthenticationStatsForUser(USER_ID_1);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+ }
+
+ @Test
+ public void authenticate_frrExceeded_singleModality_shouldNotSendNotification() {
+
+ mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+ new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+ 400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+ 0 /* modality */));
+
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+ .thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+ // Assert that no notification should be sent.
+ verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
+ verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
+ // Assert that data has been reset.
+ AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+ .getAuthenticationStatsForUser(USER_ID_1);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+ }
+
+ @Test
+ public void authenticate_frrExceeded_faceEnrolled_shouldSendFpNotification() {
+ mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+ new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+ 400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+ 0 /* modality */));
+
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+ .thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+ when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+ // Assert that fingerprint enrollment notification should be sent.
+ verify(mBiometricNotification, times(1))
+ .sendFpEnrollNotification(mContext);
+ verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
+ // Assert that data has been reset.
+ AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+ .getAuthenticationStatsForUser(USER_ID_1);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+ }
+
+ @Test
+ public void authenticate_frrExceeded_fpEnrolled_shouldSendFaceNotification() {
+ mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+ new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+ 400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+ 0 /* modality */));
+
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+ .thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+ // Assert that fingerprint enrollment notification should be sent.
+ verify(mBiometricNotification, times(1))
+ .sendFaceEnrollNotification(mContext);
+ verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
+ // Assert that data has been reset.
+ AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+ .getAuthenticationStatsForUser(USER_ID_1);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
new file mode 100644
index 000000000000..dde2a3c2cfd7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+@Presubmit
+@SmallTest
+public class AuthenticationStatsPersisterTest {
+
+ @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ private static final int USER_ID_1 = 1;
+ private static final int USER_ID_2 = 2;
+ private static final String USER_ID = "user_id";
+ private static final String FACE_ATTEMPTS = "face_attempts";
+ private static final String FACE_REJECTIONS = "face_rejections";
+ private static final String FINGERPRINT_ATTEMPTS = "fingerprint_attempts";
+ private static final String FINGERPRINT_REJECTIONS = "fingerprint_rejections";
+ private static final String ENROLLMENT_NOTIFICATIONS = "enrollment_notifications";
+ private static final String KEY = "frr_stats";
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private SharedPreferences mSharedPreferences;
+ @Mock
+ private SharedPreferences.Editor mEditor;
+ private AuthenticationStatsPersister mAuthenticationStatsPersister;
+
+ @Captor
+ private ArgumentCaptor<Set<String>> mStringSetArgumentCaptor;
+
+ @Before
+ public void setUp() {
+ when(mContext.getSharedPreferences(any(File.class), anyInt()))
+ .thenReturn(mSharedPreferences);
+ when(mSharedPreferences.edit()).thenReturn(mEditor);
+ when(mEditor.putStringSet(anyString(), anySet())).thenReturn(mEditor);
+
+ mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext);
+ }
+
+ @Test
+ public void getAllFrrStats_face_shouldListAllFrrStats() throws JSONException {
+ AuthenticationStats stats1 = new AuthenticationStats(USER_ID_1,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ AuthenticationStats stats2 = new AuthenticationStats(USER_ID_2,
+ 200 /* totalAttempts */, 20 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+ Set.of(buildFrrStats(stats1), buildFrrStats(stats2)));
+
+ List<AuthenticationStats> authenticationStatsList =
+ mAuthenticationStatsPersister.getAllFrrStats(BiometricsProtoEnums.MODALITY_FACE);
+
+ assertThat(authenticationStatsList.size()).isEqualTo(2);
+ AuthenticationStats expectedStats2 = new AuthenticationStats(USER_ID_2,
+ 0 /* totalAttempts */, 0 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ assertThat(authenticationStatsList).contains(stats1);
+ assertThat(authenticationStatsList).contains(expectedStats2);
+ }
+
+ @Test
+ public void getAllFrrStats_fingerprint_shouldListAllFrrStats() throws JSONException {
+ // User 1 with fingerprint authentication stats.
+ AuthenticationStats stats1 = new AuthenticationStats(USER_ID_1,
+ 200 /* totalAttempts */, 20 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ // User 2 without fingerprint authentication stats.
+ AuthenticationStats stats2 = new AuthenticationStats(USER_ID_2,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+ Set.of(buildFrrStats(stats1), buildFrrStats(stats2)));
+
+ List<AuthenticationStats> authenticationStatsList =
+ mAuthenticationStatsPersister
+ .getAllFrrStats(BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
+ assertThat(authenticationStatsList.size()).isEqualTo(2);
+ AuthenticationStats expectedStats2 = new AuthenticationStats(USER_ID_2,
+ 0 /* totalAttempts */, 0 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ assertThat(authenticationStatsList).contains(stats1);
+ assertThat(authenticationStatsList).contains(expectedStats2);
+ }
+
+ @Test
+ public void persistFrrStats_newUser_face_shouldSuccess() throws JSONException {
+ AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+
+ mAuthenticationStatsPersister.persistFrrStats(authenticationStats.getUserId(),
+ authenticationStats.getTotalAttempts(),
+ authenticationStats.getRejectedAttempts(),
+ authenticationStats.getEnrollmentNotifications(),
+ authenticationStats.getModality());
+
+ verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+ assertThat(mStringSetArgumentCaptor.getValue())
+ .contains(buildFrrStats(authenticationStats));
+ }
+
+ @Test
+ public void persistFrrStats_newUser_fingerprint_shouldSuccess() throws JSONException {
+ AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
+ mAuthenticationStatsPersister.persistFrrStats(authenticationStats.getUserId(),
+ authenticationStats.getTotalAttempts(),
+ authenticationStats.getRejectedAttempts(),
+ authenticationStats.getEnrollmentNotifications(),
+ authenticationStats.getModality());
+
+ verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+ assertThat(mStringSetArgumentCaptor.getValue())
+ .contains(buildFrrStats(authenticationStats));
+ }
+
+ @Test
+ public void persistFrrStats_existingUser_shouldUpdateRecord() throws JSONException {
+ AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ AuthenticationStats newAuthenticationStats = new AuthenticationStats(USER_ID_1,
+ 500 /* totalAttempts */, 30 /* rejectedAttempts */,
+ 1 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+ Set.of(buildFrrStats(authenticationStats)));
+
+ mAuthenticationStatsPersister.persistFrrStats(newAuthenticationStats.getUserId(),
+ newAuthenticationStats.getTotalAttempts(),
+ newAuthenticationStats.getRejectedAttempts(),
+ newAuthenticationStats.getEnrollmentNotifications(),
+ newAuthenticationStats.getModality());
+
+ verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+ assertThat(mStringSetArgumentCaptor.getValue())
+ .contains(buildFrrStats(newAuthenticationStats));
+ }
+
+ @Test
+ public void persistFrrStats_existingUserWithFingerprint_faceAuthenticate_shouldUpdateRecord()
+ throws JSONException {
+ // User with fingerprint authentication stats.
+ AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+ 200 /* totalAttempts */, 20 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ // The same user with face authentication stats.
+ AuthenticationStats newAuthenticationStats = new AuthenticationStats(USER_ID_1,
+ 500 /* totalAttempts */, 30 /* rejectedAttempts */,
+ 1 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+ Set.of(buildFrrStats(authenticationStats)));
+
+ mAuthenticationStatsPersister.persistFrrStats(newAuthenticationStats.getUserId(),
+ newAuthenticationStats.getTotalAttempts(),
+ newAuthenticationStats.getRejectedAttempts(),
+ newAuthenticationStats.getEnrollmentNotifications(),
+ newAuthenticationStats.getModality());
+
+ String expectedFrrStats = new JSONObject(buildFrrStats(authenticationStats))
+ .put(ENROLLMENT_NOTIFICATIONS, newAuthenticationStats.getEnrollmentNotifications())
+ .put(FACE_ATTEMPTS, newAuthenticationStats.getTotalAttempts())
+ .put(FACE_REJECTIONS, newAuthenticationStats.getRejectedAttempts()).toString();
+ verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+ assertThat(mStringSetArgumentCaptor.getValue()).contains(expectedFrrStats);
+ }
+
+ @Test
+ public void removeFrrStats_existingUser_shouldUpdateRecord() throws JSONException {
+ AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+ Set.of(buildFrrStats(authenticationStats)));
+
+ mAuthenticationStatsPersister.removeFrrStats(USER_ID_1);
+
+ verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+ assertThat(mStringSetArgumentCaptor.getValue()).doesNotContain(authenticationStats);
+ }
+
+ private String buildFrrStats(AuthenticationStats authenticationStats)
+ throws JSONException {
+ if (authenticationStats.getModality() == BiometricsProtoEnums.MODALITY_FACE) {
+ return new JSONObject()
+ .put(USER_ID, authenticationStats.getUserId())
+ .put(FACE_ATTEMPTS, authenticationStats.getTotalAttempts())
+ .put(FACE_REJECTIONS, authenticationStats.getRejectedAttempts())
+ .put(ENROLLMENT_NOTIFICATIONS, authenticationStats.getEnrollmentNotifications())
+ .toString();
+ } else if (authenticationStats.getModality() == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+ return new JSONObject()
+ .put(USER_ID, authenticationStats.getUserId())
+ .put(FINGERPRINT_ATTEMPTS, authenticationStats.getTotalAttempts())
+ .put(FINGERPRINT_REJECTIONS, authenticationStats.getRejectedAttempts())
+ .put(ENROLLMENT_NOTIFICATIONS, authenticationStats.getEnrollmentNotifications())
+ .toString();
+ }
+ return "";
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index e9a7d85ae755..037637630b7a 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -67,6 +67,8 @@ import java.util.stream.Collectors;
@RunWith(AndroidJUnit4.class)
public final class UpdatableFontDirTest {
+ private static final String LEGACY_FONTS_XML = "/system/etc/fonts.xml";
+
/**
* A {@link UpdatableFontDir.FontFileParser} for testing. Instead of using real font files,
* this test uses fake font files. A fake font file has its PostScript naem and revision as the
@@ -140,7 +142,7 @@ public final class UpdatableFontDirTest {
private List<File> mPreinstalledFontDirs;
private final Supplier<Long> mCurrentTimeSupplier = () -> CURRENT_TIME;
private final Function<Map<String, File>, FontConfig> mConfigSupplier =
- (map) -> SystemFonts.getSystemFontConfig(map, 0, 0);
+ (map) -> SystemFonts.getSystemFontConfigForTesting(LEGACY_FONTS_XML, map, 0, 0);
private FakeFontFileParser mParser;
private FakeFsverityUtil mFakeFsverityUtil;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index 399655ffac55..e6d326a33415 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -92,11 +92,11 @@ public abstract class BaseAbsoluteVolumeBehaviorTest {
// Default Audio Status given by the System Audio device in its initial <Report Audio Status>
// that triggers AVB being enabled
- private static final AudioStatus INITIAL_SYSTEM_AUDIO_DEVICE_STATUS =
+ protected static final AudioStatus INITIAL_SYSTEM_AUDIO_DEVICE_STATUS =
new AudioStatus(50, false);
// VolumeInfo passed to AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior to enable AVB
- private static final VolumeInfo ENABLE_AVB_VOLUME_INFO =
+ protected static final VolumeInfo ENABLE_AVB_VOLUME_INFO =
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
.setMuted(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute())
.setVolumeIndex(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume())
@@ -106,6 +106,8 @@ public abstract class BaseAbsoluteVolumeBehaviorTest {
private static final int EMPTY_FLAGS = 0;
+ protected static final int STREAM_MUSIC_MAX_VOLUME = 25;
+
protected abstract HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService);
protected abstract int getPhysicalAddress();
@@ -201,7 +203,7 @@ public abstract class BaseAbsoluteVolumeBehaviorTest {
Collections.singletonList(getAudioOutputDevice()));
// Max volume of STREAM_MUSIC
- mAudioFramework.setStreamMaxVolume(AudioManager.STREAM_MUSIC, 25);
+ mAudioFramework.setStreamMaxVolume(AudioManager.STREAM_MUSIC, STREAM_MUSIC_MAX_VOLUME);
// Receive messages from devices to make sure they're registered in HdmiCecNetwork
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java
index 024e36d62273..86647fc16826 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java
@@ -321,4 +321,98 @@ public class TvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehaviorTest {
getLogicalAddress(), getSystemAudioDeviceLogicalAddress(),
Constants.AUDIO_VOLUME_STATUS_UNKNOWN));
}
+
+ /**
+ * Tests that a volume adjustment command with direction ADJUST_SAME causes HdmiControlService
+ * to request the System Audio device's audio status, and notify AudioService of the
+ * audio status.
+ */
+ @Test
+ public void avbEnabled_audioDeviceVolumeAdjusted_adjustSame_updatesAudioService() {
+ enableAbsoluteVolumeBehavior();
+ mNativeWrapper.clearResultMessages();
+
+ // HdmiControlService receives a volume adjustment with direction ADJUST_SAME
+ mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeAdjusted(
+ getAudioOutputDevice(),
+ ENABLE_AVB_VOLUME_INFO,
+ AudioManager.ADJUST_SAME,
+ AudioDeviceVolumeManager.ADJUST_MODE_NORMAL
+ );
+ mTestLooper.dispatchAll();
+
+ // Device sends <Give Audio Status>
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress()));
+
+ clearInvocations(mAudioManager);
+
+ // Device receives <Report Audio Status> with a new volume and mute state
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+ getSystemAudioDeviceLogicalAddress(),
+ getLogicalAddress(),
+ 80,
+ true));
+ mTestLooper.dispatchAll();
+
+ // HdmiControlService calls setStreamVolume and adjustStreamVolume to trigger volume UI
+ verify(mAudioManager).setStreamVolume(
+ eq(AudioManager.STREAM_MUSIC),
+ // Volume level is rescaled to the max volume of STREAM_MUSIC
+ eq(80 * STREAM_MUSIC_MAX_VOLUME / AudioStatus.MAX_VOLUME),
+ eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
+ verify(mAudioManager).adjustStreamVolume(
+ eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_MUTE),
+ eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
+ }
+
+ /**
+ * Tests that a volume adjustment command with direction ADJUST_SAME causes HdmiControlService
+ * to request the System Audio device's audio status, and notify AudioService of the
+ * audio status, even if it's unchanged from the previous one.
+ */
+ @Test
+ public void avbEnabled_audioDeviceVolumeAdjusted_adjustSame_noChange_updatesAudioService() {
+ enableAbsoluteVolumeBehavior();
+ mNativeWrapper.clearResultMessages();
+
+ // HdmiControlService receives a volume adjustment with direction ADJUST_SAME
+ mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeAdjusted(
+ getAudioOutputDevice(),
+ ENABLE_AVB_VOLUME_INFO,
+ AudioManager.ADJUST_SAME,
+ AudioDeviceVolumeManager.ADJUST_MODE_NORMAL
+ );
+ mTestLooper.dispatchAll();
+
+ // Device sends <Give Audio Status>
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress()));
+
+ clearInvocations(mAudioManager);
+
+ // Device receives <Report Audio Status> with the same volume level and mute state that
+ // as when AVB was enabled
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+ getSystemAudioDeviceLogicalAddress(),
+ getLogicalAddress(),
+ INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
+ INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute()));
+ mTestLooper.dispatchAll();
+
+ // HdmiControlService calls setStreamVolume and adjustStreamVolume to trigger volume UI
+ verify(mAudioManager).setStreamVolume(
+ eq(AudioManager.STREAM_MUSIC),
+ // Volume level is rescaled to the max volume of STREAM_MUSIC
+ eq(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume()
+ * STREAM_MUSIC_MAX_VOLUME / AudioStatus.MAX_VOLUME),
+ eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
+ verify(mAudioManager).adjustStreamVolume(
+ eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE),
+ eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index cb659b6dbd56..ecd35a55e291 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -1565,6 +1565,25 @@ public final class UserManagerTest {
assertThat(userInfo.name).isEqualTo(newName);
}
+ @Test
+ public void testCannotCreateAdditionalMainUser() {
+ UserHandle mainUser = mUserManager.getMainUser();
+ assumeTrue("There is no main user", mainUser != null);
+
+ // Users with FLAG_MAIN can't be removed, so no point using the local createUser method.
+ UserInfo newMainUser = mUserManager.createUser("test", UserInfo.FLAG_MAIN);
+ assertThat(newMainUser).isNull();
+
+ List<UserInfo> users = mUserManager.getUsers();
+ int mainUserCount = 0;
+ for (UserInfo user : users) {
+ if (user.isMain()) {
+ mainUserCount++;
+ }
+ }
+ assertThat(mainUserCount).isEqualTo(1);
+ }
+
private boolean isPackageInstalledForUser(String packageName, int userId) {
try {
return mPackageManager.getPackageInfoAsUser(packageName, 0, userId) != null;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index fae92d9ac738..f83a1df358bd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -36,7 +36,7 @@ import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -44,7 +44,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
-import android.app.IActivityManager;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.NotificationChannel;
@@ -76,6 +75,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.LocalServices;
import com.android.server.UiServiceTestCase;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -850,84 +850,78 @@ public class NotificationRecordTest extends UiServiceTestCase {
@Test
public void testCalculateGrantableUris_PappProvided() {
- IActivityManager am = mock(IActivityManager.class);
UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
anyInt(), anyInt())).thenThrow(new SecurityException());
+ LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+ LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
channel.setSound(null, null);
Notification n = new Notification.Builder(mContext, channel.getId())
.setSmallIcon(Icon.createWithContentUri(Uri.parse("content://something")))
.build();
StatusBarNotification sbn =
new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- record.mAm = am;
- record.mUgmInternal = ugm;
-
- try {
- record.calculateGrantableUris();
- fail("App provided uri for p targeting app should throw exception");
- } catch (SecurityException e) {
- // expected
- }
+
+ assertThrows("App provided uri for p targeting app should throw exception",
+ SecurityException.class,
+ () -> new NotificationRecord(mMockContext, sbn, channel));
}
@Test
public void testCalculateGrantableUris_PappProvided_invalidSound() {
- IActivityManager am = mock(IActivityManager.class);
UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
anyInt(), anyInt())).thenThrow(new SecurityException());
+ LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+ LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
channel.setSound(Uri.parse("content://something"), mock(AudioAttributes.class));
Notification n = mock(Notification.class);
when(n.getChannelId()).thenReturn(channel.getId());
StatusBarNotification sbn =
new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- record.mAm = am;
- record.mUgmInternal = ugm;
- record.calculateGrantableUris();
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound());
}
@Test
public void testCalculateGrantableUris_PuserOverridden() {
- IActivityManager am = mock(IActivityManager.class);
UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
anyInt(), anyInt())).thenThrow(new SecurityException());
+ LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+ LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
Notification n = mock(Notification.class);
when(n.getChannelId()).thenReturn(channel.getId());
StatusBarNotification sbn =
new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- record.mAm = am;
- record.calculateGrantableUris();
+ new NotificationRecord(mMockContext, sbn, channel); // should not throw
}
@Test
public void testCalculateGrantableUris_prePappProvided() {
- IActivityManager am = mock(IActivityManager.class);
UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
anyInt(), anyInt())).thenThrow(new SecurityException());
+ LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+ LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
Notification n = mock(Notification.class);
when(n.getChannelId()).thenReturn(channel.getId());
StatusBarNotification sbn =
new StatusBarNotification(PKG_O, PKG_O, id1, tag1, uid, uid, n, mUser, null, uid);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- record.mAm = am;
- record.calculateGrantableUris();
- // should not throw
+ new NotificationRecord(mMockContext, sbn, channel); // should not throw
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index d5afe3b2f078..0cdd9b8a9e0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1880,6 +1880,11 @@ public class SizeCompatTests extends WindowTestsBase {
final int dh = 2500;
final int notchHeight = 200;
setUpApp(new TestDisplayContent.Builder(mAtm, dw, dh).setNotch(notchHeight).build());
+ // The test assumes the notch will be at left side when the orientation is landscape.
+ if (mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_reverseDefaultRotation)) {
+ setReverseDefaultRotation(mActivity.mDisplayContent, false);
+ }
addStatusBar(mActivity.mDisplayContent);
mActivity.setVisible(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ae7b161dea16..99688dabda6e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -48,6 +48,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -59,6 +60,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import android.annotation.IntDef;
@@ -956,6 +958,38 @@ class WindowTestsBase extends SystemServiceTestsBase {
return testPlayer;
}
+ /** Overrides the behavior of config_reverseDefaultRotation for the given display. */
+ void setReverseDefaultRotation(DisplayContent dc, boolean reverse) {
+ final DisplayRotation displayRotation = dc.getDisplayRotation();
+ if (!Mockito.mockingDetails(displayRotation).isSpy()) {
+ spyOn(displayRotation);
+ }
+ doAnswer(invocation -> {
+ invocation.callRealMethod();
+ final int w = invocation.getArgument(0);
+ final int h = invocation.getArgument(1);
+ if (w > h) {
+ if (reverse) {
+ displayRotation.mPortraitRotation = Surface.ROTATION_90;
+ displayRotation.mUpsideDownRotation = Surface.ROTATION_270;
+ } else {
+ displayRotation.mPortraitRotation = Surface.ROTATION_270;
+ displayRotation.mUpsideDownRotation = Surface.ROTATION_90;
+ }
+ } else {
+ if (reverse) {
+ displayRotation.mLandscapeRotation = Surface.ROTATION_270;
+ displayRotation.mSeascapeRotation = Surface.ROTATION_90;
+ } else {
+ displayRotation.mLandscapeRotation = Surface.ROTATION_90;
+ displayRotation.mSeascapeRotation = Surface.ROTATION_270;
+ }
+ }
+ return null;
+ }).when(displayRotation).configure(anyInt(), anyInt());
+ displayRotation.configure(dc.mBaseDisplayWidth, dc.mBaseDisplayHeight);
+ }
+
/**
* Avoids rotating screen disturbed by some conditions. It is usually used for the default
* display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 943d8d6cdd55..4a541da9e5aa 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -3388,7 +3388,11 @@ public abstract class Connection extends Conferenceable {
public void onAbort() {}
/**
- * Notifies this Connection of a request to hold.
+ * Notifies this Connection of a request to hold. {@link Connection#setOnHold} should be within
+ * the onHold() body in order to transition the call state to {@link Connection#STATE_HOLDING}.
+ * <p>
+ * Note: If the Connection does not transition to {@link Connection#STATE_HOLDING} within 2
+ * seconds, the call will be disconnected.
*/
public void onHold() {}
diff --git a/telecomm/java/android/telecom/RemoteConnectionManager.java b/telecomm/java/android/telecom/RemoteConnectionManager.java
index fbbfefd9d00e..fbf8eeffd947 100644
--- a/telecomm/java/android/telecom/RemoteConnectionManager.java
+++ b/telecomm/java/android/telecom/RemoteConnectionManager.java
@@ -39,18 +39,21 @@ public class RemoteConnectionManager {
void addConnectionService(
ComponentName componentName,
IConnectionService outgoingConnectionServiceRpc) {
- if (!mRemoteConnectionServices.containsKey(componentName)) {
- try {
- RemoteConnectionService remoteConnectionService = new RemoteConnectionService(
- outgoingConnectionServiceRpc,
- mOurConnectionServiceImpl);
- mRemoteConnectionServices.put(componentName, remoteConnectionService);
- } catch (RemoteException e) {
- Log.w(RemoteConnectionManager.this,
- "error when addConnectionService of %s: %s", componentName,
- e.toString());
- }
- }
+ mRemoteConnectionServices.computeIfAbsent(
+ componentName,
+ key -> {
+ try {
+ return new RemoteConnectionService(
+ outgoingConnectionServiceRpc, mOurConnectionServiceImpl);
+ } catch (RemoteException e) {
+ Log.w(
+ RemoteConnectionManager.this,
+ "error when addConnectionService of %s: %s",
+ componentName,
+ e.toString());
+ return null;
+ }
+ });
}
public RemoteConnection createRemoteConnection(
@@ -63,17 +66,14 @@ public class RemoteConnectionManager {
}
ComponentName componentName = request.getAccountHandle().getComponentName();
- if (!mRemoteConnectionServices.containsKey(componentName)) {
+ RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
+ if (remoteService == null) {
throw new UnsupportedOperationException("accountHandle not supported: "
+ componentName);
}
- RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
- if (remoteService != null) {
- return remoteService.createRemoteConnection(
- connectionManagerPhoneAccount, request, isIncoming);
- }
- return null;
+ return remoteService.createRemoteConnection(
+ connectionManagerPhoneAccount, request, isIncoming);
}
/**
@@ -94,17 +94,14 @@ public class RemoteConnectionManager {
}
ComponentName componentName = request.getAccountHandle().getComponentName();
- if (!mRemoteConnectionServices.containsKey(componentName)) {
+ RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
+ if (remoteService == null) {
throw new UnsupportedOperationException("accountHandle not supported: "
+ componentName);
}
- RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
- if (remoteService != null) {
- return remoteService.createRemoteConference(
- connectionManagerPhoneAccount, request, isIncoming);
- }
- return null;
+ return remoteService.createRemoteConference(
+ connectionManagerPhoneAccount, request, isIncoming);
}
public void conferenceRemoteConnections(RemoteConnection a, RemoteConnection b) {
diff --git a/tests/Input/src/com/android/server/input/FocusEventDebugViewTest.java b/tests/Input/src/com/android/server/input/FocusEventDebugViewTest.java
new file mode 100644
index 000000000000..d8113fc95591
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/FocusEventDebugViewTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
+import android.view.ViewConfiguration;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest FocusEventDebugViewTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class FocusEventDebugViewTest {
+
+ private FocusEventDebugView mFocusEventDebugView;
+ private FocusEventDebugView.RotaryInputValueView mRotaryInputValueView;
+ private float mScaledVerticalScrollFactor;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ mScaledVerticalScrollFactor =
+ ViewConfiguration.get(context).getScaledVerticalScrollFactor();
+ InputManagerService mockService = mock(InputManagerService.class);
+ when(mockService.monitorInput(anyString(), anyInt()))
+ .thenReturn(InputChannel.openInputChannelPair("FocusEventDebugViewTest")[1]);
+
+ mRotaryInputValueView = new FocusEventDebugView.RotaryInputValueView(context);
+ mFocusEventDebugView = new FocusEventDebugView(context, mockService,
+ () -> mRotaryInputValueView);
+ }
+
+ @Test
+ public void startsRotaryInputValueViewWithDefaultValue() {
+ assertEquals("+0.0", mRotaryInputValueView.getText());
+ }
+
+ @Test
+ public void handleRotaryInput_updatesRotaryInputValueViewWithScrollValue() {
+ mFocusEventDebugView.handleUpdateShowRotaryInput(true);
+
+ mFocusEventDebugView.handleRotaryInput(createRotaryMotionEvent(0.5f));
+
+ assertEquals(String.format("+%.1f", 0.5f * mScaledVerticalScrollFactor),
+ mRotaryInputValueView.getText());
+ }
+
+ @Test
+ public void updateActivityStatus_setsAndRemovesColorFilter() {
+ // It should not be active initially.
+ assertNull(mRotaryInputValueView.getBackground().getColorFilter());
+
+ mRotaryInputValueView.updateActivityStatus(true);
+ // It should be active after rotary input.
+ assertNotNull(mRotaryInputValueView.getBackground().getColorFilter());
+
+ mRotaryInputValueView.updateActivityStatus(false);
+ // It should not be active after waiting for mUpdateActivityStatusCallback.
+ assertNull(mRotaryInputValueView.getBackground().getColorFilter());
+ }
+
+ private MotionEvent createRotaryMotionEvent(float scrollAxisValue) {
+ PointerCoords pointerCoords = new PointerCoords();
+ pointerCoords.setAxisValue(MotionEvent.AXIS_SCROLL, scrollAxisValue);
+ PointerProperties pointerProperties = new PointerProperties();
+
+ return MotionEvent.obtain(
+ /* downTime */ 0,
+ /* eventTime */ 0,
+ /* action */ MotionEvent.ACTION_SCROLL,
+ /* pointerCount */ 1,
+ /* pointerProperties */ new PointerProperties[] {pointerProperties},
+ /* pointerCoords */ new PointerCoords[] {pointerCoords},
+ /* metaState */ 0,
+ /* buttonState */ 0,
+ /* xPrecision */ 0,
+ /* yPrecision */ 0,
+ /* deviceId */ 0,
+ /* edgeFlags */ 0,
+ /* source */ InputDevice.SOURCE_ROTARY_ENCODER,
+ /* flags */ 0
+ );
+ }
+}
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
index 31ea8327c2b3..c92d768439fd 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
@@ -16,10 +16,6 @@
package com.android.test.silkfx.hdr
-import android.animation.AnimatorSet
-import android.animation.ObjectAnimator
-import android.animation.ValueAnimator
-import android.animation.ValueAnimator.AnimatorUpdateListener
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
@@ -46,7 +42,7 @@ class GainmapImage(context: Context, attrs: AttributeSet?) : FrameLayout(context
private var selectedImage = -1
private var outputMode = R.id.output_hdr
private var bitmap: Bitmap? = null
- private var gainmap: Gainmap? = null
+ private var originalGainmap: Gainmap? = null
private var gainmapVisualizer: Bitmap? = null
private lateinit var imageView: SubsamplingScaleImageView
private lateinit var gainmapMetadataEditor: GainmapMetadataEditor
@@ -70,7 +66,6 @@ class GainmapImage(context: Context, attrs: AttributeSet?) : FrameLayout(context
it.check(outputMode)
it.setOnCheckedChangeListener { _, checkedId ->
outputMode = checkedId
- // Intentionally don't do anything fancy so that mode A/B comparisons are easy
updateDisplay()
}
}
@@ -101,41 +96,10 @@ class GainmapImage(context: Context, attrs: AttributeSet?) : FrameLayout(context
imageView.apply {
isClickable = true
- // Example of animating between SDR and HDR using gainmap params; animates HDR->SDR->HDR
- // with a brief pause on SDR. The key thing here is that the gainmap's
- // minDisplayRatioForHdrTransition is animated between its original value (for full HDR)
- // and displayRatioForFullHdr (for full SDR). The view must also be invalidated during
- // the animation for the updates to take effect.
setOnClickListener {
- if (gainmap != null && (outputMode == R.id.output_hdr ||
- outputMode == R.id.output_hdr_test)) {
- val animationLengthMs: Long = 500
- val updateListener = object : AnimatorUpdateListener {
- override fun onAnimationUpdate(animation: ValueAnimator) {
- imageView.invalidate()
- }
- }
- val hdrToSdr = ObjectAnimator.ofFloat(
- gainmap, "minDisplayRatioForHdrTransition",
- gainmap!!.minDisplayRatioForHdrTransition,
- gainmap!!.displayRatioForFullHdr).apply {
- duration = animationLengthMs
- addUpdateListener(updateListener)
- }
- val sdrToHdr = ObjectAnimator.ofFloat(
- gainmap, "minDisplayRatioForHdrTransition",
- gainmap!!.displayRatioForFullHdr,
- gainmap!!.minDisplayRatioForHdrTransition).apply {
- duration = animationLengthMs
- addUpdateListener(updateListener)
- }
-
- AnimatorSet().apply {
- play(hdrToSdr)
- play(sdrToHdr).after(animationLengthMs)
- start()
- }
- }
+ animate().alpha(.5f).withEndAction {
+ animate().alpha(1f).start()
+ }.start()
}
}
}
@@ -149,7 +113,7 @@ class GainmapImage(context: Context, attrs: AttributeSet?) : FrameLayout(context
}
private fun doDecode(source: ImageDecoder.Source) {
- gainmap = null
+ originalGainmap = null
bitmap = ImageDecoder.decodeBitmap(source) { decoder, info, source ->
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
}
@@ -167,9 +131,10 @@ class GainmapImage(context: Context, attrs: AttributeSet?) : FrameLayout(context
findViewById<TextView>(R.id.error_msg)!!.visibility = View.GONE
findViewById<RadioGroup>(R.id.output_mode)!!.visibility = View.VISIBLE
- gainmap = bitmap!!.gainmap
- gainmapMetadataEditor.setGainmap(gainmap)
- val map = gainmap!!.gainmapContents
+ val gainmap = bitmap!!.gainmap!!
+ originalGainmap = gainmap
+ gainmapMetadataEditor.setGainmap(Gainmap(gainmap, gainmap.gainmapContents))
+ val map = gainmap.gainmapContents
if (map.config != Bitmap.Config.ALPHA_8) {
gainmapVisualizer = map
} else {
@@ -198,14 +163,12 @@ class GainmapImage(context: Context, attrs: AttributeSet?) : FrameLayout(context
imageView.setImage(ImageSource.cachedBitmap(when (outputMode) {
R.id.output_hdr -> {
- gainmapMetadataEditor.useOriginalMetadata()
- bitmap!!.gainmap = gainmap
+ bitmap!!.gainmap = originalGainmap
bitmap!!
}
R.id.output_hdr_test -> {
- gainmapMetadataEditor.useEditMetadata()
- bitmap!!.gainmap = gainmap
+ bitmap!!.gainmap = gainmapMetadataEditor.editedGainmap()
bitmap!!
}
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt
index 8a653045c97b..c4bc6001533e 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt
@@ -28,23 +28,27 @@ import android.widget.TextView
import com.android.test.silkfx.R
data class GainmapMetadata(
- var ratioMin: Float,
- var ratioMax: Float,
- var capacityMin: Float,
- var capacityMax: Float,
- var gamma: Float,
- var offsetSdr: Float,
- var offsetHdr: Float
+ var ratioMin: Float,
+ var ratioMax: Float,
+ var capacityMin: Float,
+ var capacityMax: Float,
+ var gamma: Float,
+ var offsetSdr: Float,
+ var offsetHdr: Float
)
+/**
+ * Note: This can only handle single-channel gainmaps nicely. It will force all 3-channel
+ * metadata to have the same value single value and is not intended to be a robust demonstration
+ * of gainmap metadata editing
+ */
class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
- private var gainmap: Gainmap? = null
- private var showingEdits = false
+ private lateinit var gainmap: Gainmap
private var metadataPopup: PopupWindow? = null
private var originalMetadata: GainmapMetadata = GainmapMetadata(
- 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f)
+ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f)
private var currentMetadata: GainmapMetadata = originalMetadata.copy()
private val maxProgress = 100.0f
@@ -61,23 +65,18 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
private val maxGamma = 3.0f
// Min and max offsets are 0.0 and 1.0 respectively
- fun setGainmap(newGainmap: Gainmap?) {
+ fun setGainmap(newGainmap: Gainmap) {
gainmap = newGainmap
- originalMetadata = GainmapMetadata(gainmap!!.getRatioMin()[0],
- gainmap!!.getRatioMax()[0], gainmap!!.getMinDisplayRatioForHdrTransition(),
- gainmap!!.getDisplayRatioForFullHdr(), gainmap!!.getGamma()[0],
- gainmap!!.getEpsilonSdr()[0], gainmap!!.getEpsilonHdr()[0])
+ originalMetadata = GainmapMetadata(gainmap.getRatioMin()[0],
+ gainmap.getRatioMax()[0], gainmap.getMinDisplayRatioForHdrTransition(),
+ gainmap.getDisplayRatioForFullHdr(), gainmap.getGamma()[0],
+ gainmap.getEpsilonSdr()[0], gainmap.getEpsilonHdr()[0])
currentMetadata = originalMetadata.copy()
}
- fun useOriginalMetadata() {
- showingEdits = false
- applyMetadata(originalMetadata)
- }
-
- fun useEditMetadata() {
- showingEdits = true
+ fun editedGainmap(): Gainmap {
applyMetadata(currentMetadata)
+ return gainmap
}
fun closeEditor() {
@@ -93,7 +92,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
val view = LayoutInflater.from(parent.getContext()).inflate(R.layout.gainmap_metadata, null)
metadataPopup = PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT)
+ ViewGroup.LayoutParams.WRAP_CONTENT)
metadataPopup!!.showAtLocation(view, Gravity.CENTER, 0, 0)
(view.getParent() as ViewGroup).removeView(view)
@@ -117,7 +116,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
val offsetSdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr)
val offsetHdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr)
arrayOf(gainmapMinSeek, gainmapMaxSeek, capacityMinSeek, capacityMaxSeek, gammaSeek,
- offsetSdrSeek, offsetHdrSeek).forEach {
+ offsetSdrSeek, offsetHdrSeek).forEach {
it.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (!fromUser) return
@@ -149,37 +148,37 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
val offsetHdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr)
gainmapMinSeek.setProgress(
- ((currentMetadata.ratioMin - minRatioMin) / maxRatioMin * maxProgress).toInt())
+ ((currentMetadata.ratioMin - minRatioMin) / maxRatioMin * maxProgress).toInt())
gainmapMaxSeek.setProgress(
- ((currentMetadata.ratioMax - minRatioMax) / maxRatioMax * maxProgress).toInt())
+ ((currentMetadata.ratioMax - minRatioMax) / maxRatioMax * maxProgress).toInt())
capacityMinSeek.setProgress(
- ((currentMetadata.capacityMin - minCapacityMin) / maxCapacityMin * maxProgress).toInt())
+ ((currentMetadata.capacityMin - minCapacityMin) / maxCapacityMin * maxProgress).toInt())
capacityMaxSeek.setProgress(
- ((currentMetadata.capacityMax - minCapacityMax) / maxCapacityMax * maxProgress).toInt())
+ ((currentMetadata.capacityMax - minCapacityMax) / maxCapacityMax * maxProgress).toInt())
gammaSeek.setProgress(
- ((currentMetadata.gamma - minGamma) / maxGamma * maxProgress).toInt())
+ ((currentMetadata.gamma - minGamma) / maxGamma * maxProgress).toInt())
// Log base 3 via: log_b(x) = log_y(x) / log_y(b)
offsetSdrSeek.setProgress(
- ((1.0 - Math.log(currentMetadata.offsetSdr.toDouble() / Math.log(3.0)) / -11.0)
- .toFloat() * maxProgress).toInt())
+ ((1.0 - Math.log(currentMetadata.offsetSdr.toDouble() / Math.log(3.0)) / -11.0)
+ .toFloat() * maxProgress).toInt())
offsetHdrSeek.setProgress(
- ((1.0 - Math.log(currentMetadata.offsetHdr.toDouble() / Math.log(3.0)) / -11.0)
- .toFloat() * maxProgress).toInt())
+ ((1.0 - Math.log(currentMetadata.offsetHdr.toDouble() / Math.log(3.0)) / -11.0)
+ .toFloat() * maxProgress).toInt())
parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText(
- "%.3f".format(currentMetadata.ratioMin))
+ "%.3f".format(currentMetadata.ratioMin))
parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText(
- "%.3f".format(currentMetadata.ratioMax))
+ "%.3f".format(currentMetadata.ratioMax))
parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText(
- "%.3f".format(currentMetadata.capacityMin))
+ "%.3f".format(currentMetadata.capacityMin))
parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText(
- "%.3f".format(currentMetadata.capacityMax))
+ "%.3f".format(currentMetadata.capacityMax))
parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText(
- "%.3f".format(currentMetadata.gamma))
+ "%.3f".format(currentMetadata.gamma))
parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText(
- "%.5f".format(currentMetadata.offsetSdr))
+ "%.5f".format(currentMetadata.offsetSdr))
parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText(
- "%.5f".format(currentMetadata.offsetHdr))
+ "%.5f".format(currentMetadata.offsetHdr))
}
private fun resetGainmapMetadata() {
@@ -189,69 +188,59 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
}
private fun applyMetadata(newMetadata: GainmapMetadata) {
- gainmap!!.setRatioMin(newMetadata.ratioMin, newMetadata.ratioMin, newMetadata.ratioMin)
- gainmap!!.setRatioMax(newMetadata.ratioMax, newMetadata.ratioMax, newMetadata.ratioMax)
- gainmap!!.setMinDisplayRatioForHdrTransition(newMetadata.capacityMin)
- gainmap!!.setDisplayRatioForFullHdr(newMetadata.capacityMax)
- gainmap!!.setGamma(newMetadata.gamma, newMetadata.gamma, newMetadata.gamma)
- gainmap!!.setEpsilonSdr(newMetadata.offsetSdr, newMetadata.offsetSdr, newMetadata.offsetSdr)
- gainmap!!.setEpsilonHdr(newMetadata.offsetHdr, newMetadata.offsetHdr, newMetadata.offsetHdr)
+ gainmap.setRatioMin(newMetadata.ratioMin, newMetadata.ratioMin, newMetadata.ratioMin)
+ gainmap.setRatioMax(newMetadata.ratioMax, newMetadata.ratioMax, newMetadata.ratioMax)
+ gainmap.setMinDisplayRatioForHdrTransition(newMetadata.capacityMin)
+ gainmap.setDisplayRatioForFullHdr(newMetadata.capacityMax)
+ gainmap.setGamma(newMetadata.gamma, newMetadata.gamma, newMetadata.gamma)
+ gainmap.setEpsilonSdr(newMetadata.offsetSdr, newMetadata.offsetSdr, newMetadata.offsetSdr)
+ gainmap.setEpsilonHdr(newMetadata.offsetHdr, newMetadata.offsetHdr, newMetadata.offsetHdr)
renderView.invalidate()
}
private fun updateGainmapMin(normalized: Float) {
val newValue = minRatioMin + normalized * (maxRatioMin - minRatioMin)
parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText(
- "%.3f".format(newValue))
+ "%.3f".format(newValue))
currentMetadata.ratioMin = newValue
- if (showingEdits) {
- gainmap!!.setRatioMin(newValue, newValue, newValue)
- renderView.invalidate()
- }
+ gainmap.setRatioMin(newValue, newValue, newValue)
+ renderView.invalidate()
}
private fun updateGainmapMax(normalized: Float) {
val newValue = minRatioMax + normalized * (maxRatioMax - minRatioMax)
parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText(
- "%.3f".format(newValue))
+ "%.3f".format(newValue))
currentMetadata.ratioMax = newValue
- if (showingEdits) {
- gainmap!!.setRatioMax(newValue, newValue, newValue)
- renderView.invalidate()
- }
+ gainmap.setRatioMax(newValue, newValue, newValue)
+ renderView.invalidate()
}
private fun updateCapacityMin(normalized: Float) {
val newValue = minCapacityMin + normalized * (maxCapacityMin - minCapacityMin)
parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText(
- "%.3f".format(newValue))
+ "%.3f".format(newValue))
currentMetadata.capacityMin = newValue
- if (showingEdits) {
- gainmap!!.setMinDisplayRatioForHdrTransition(newValue)
- renderView.invalidate()
- }
+ gainmap.setMinDisplayRatioForHdrTransition(newValue)
+ renderView.invalidate()
}
private fun updateCapacityMax(normalized: Float) {
val newValue = minCapacityMax + normalized * (maxCapacityMax - minCapacityMax)
parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText(
- "%.3f".format(newValue))
+ "%.3f".format(newValue))
currentMetadata.capacityMax = newValue
- if (showingEdits) {
- gainmap!!.setDisplayRatioForFullHdr(newValue)
- renderView.invalidate()
- }
+ gainmap.setDisplayRatioForFullHdr(newValue)
+ renderView.invalidate()
}
private fun updateGamma(normalized: Float) {
val newValue = minGamma + normalized * (maxGamma - minGamma)
parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText(
- "%.3f".format(newValue))
+ "%.3f".format(newValue))
currentMetadata.gamma = newValue
- if (showingEdits) {
- gainmap!!.setGamma(newValue, newValue, newValue)
- renderView.invalidate()
- }
+ gainmap.setGamma(newValue, newValue, newValue)
+ renderView.invalidate()
}
private fun updateOffsetSdr(normalized: Float) {
@@ -260,12 +249,10 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat()
}
parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText(
- "%.5f".format(newValue))
+ "%.5f".format(newValue))
currentMetadata.offsetSdr = newValue
- if (showingEdits) {
- gainmap!!.setEpsilonSdr(newValue, newValue, newValue)
- renderView.invalidate()
- }
+ gainmap.setEpsilonSdr(newValue, newValue, newValue)
+ renderView.invalidate()
}
private fun updateOffsetHdr(normalized: Float) {
@@ -274,11 +261,9 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat()
}
parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText(
- "%.5f".format(newValue))
+ "%.5f".format(newValue))
currentMetadata.offsetHdr = newValue
- if (showingEdits) {
- gainmap!!.setEpsilonHdr(newValue, newValue, newValue)
- renderView.invalidate()
- }
+ gainmap.setEpsilonHdr(newValue, newValue, newValue)
+ renderView.invalidate()
}
}
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index d02fd83df6af..51cf38bce64e 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -353,8 +353,8 @@ static void printCompatibleScreens(ResXMLTree& tree, String8* outError) {
}
static void printUsesPermission(const String8& name, bool optional=false, int maxSdkVersion=-1,
- const String8& requiredFeature = String8::empty(),
- const String8& requiredNotFeature = String8::empty()) {
+ const String8& requiredFeature = String8(),
+ const String8& requiredNotFeature = String8()) {
printf("uses-permission: name='%s'", ResTable::normalizeForOutput(name.string()).string());
if (maxSdkVersion != -1) {
printf(" maxSdkVersion='%d'", maxSdkVersion);
diff --git a/tools/hiddenapi/OWNERS b/tools/hiddenapi/OWNERS
index afbeef5a0b41..dc82aac9d41c 100644
--- a/tools/hiddenapi/OWNERS
+++ b/tools/hiddenapi/OWNERS
@@ -1,5 +1,4 @@
# compat-team@ for changes to hiddenapi files
-andreionea@google.com
mathewi@google.com
satayev@google.com
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
index 5ad3ede8498d..c828de9f221d 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
@@ -196,7 +196,7 @@ public final class SharedConnectivitySettingsState implements Parcelable {
public String toString() {
return new StringBuilder("SharedConnectivitySettingsState[")
.append("instantTetherEnabled=").append(mInstantTetherEnabled)
- .append("PendingIntent=").append(mInstantTetherSettingsPendingIntent.toString())
+ .append("PendingIntent=").append(mInstantTetherSettingsPendingIntent)
.append("extras=").append(mExtras.toString())
.append("]").toString();
}