summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp12
-rw-r--r--apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java7
-rw-r--r--apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java4
-rw-r--r--apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java3
-rw-r--r--api/StubLibraries.bp7
-rw-r--r--cmds/idmap2/Android.bp4
-rw-r--r--cmds/idmap2/idmap2d/Idmap2Service.cpp44
-rw-r--r--cmds/idmap2/idmap2d/Idmap2Service.h15
-rw-r--r--cmds/incidentd/src/IncidentService.cpp2
-rw-r--r--core/api/current.txt38
-rw-r--r--core/java/android/app/Notification.java17
-rw-r--r--core/java/android/app/StatusBarManager.java36
-rw-r--r--core/java/android/content/pm/PackageInfo.java27
-rw-r--r--core/java/android/content/pm/PackageManager.java14
-rw-r--r--core/java/android/content/pm/ResolveInfo.java11
-rw-r--r--core/java/android/hardware/biometrics/BiometricManager.java21
-rw-r--r--core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl4
-rw-r--r--core/java/android/inputmethodservice/NavigationBarController.java100
-rw-r--r--core/java/android/preference/SeekBarVolumizer.java21
-rw-r--r--core/java/android/provider/Settings.java12
-rwxr-xr-xcore/java/android/util/DisplayMetrics.java8
-rw-r--r--core/java/android/view/InsetsFrameProvider.java4
-rw-r--r--core/java/android/view/InsetsSource.java43
-rw-r--r--core/java/android/view/InsetsState.java3
-rw-r--r--core/java/android/view/SurfaceControlRegistry.java2
-rw-r--r--core/java/android/view/View.java11
-rw-r--r--core/java/android/view/WindowManager.java22
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureSession.java35
-rw-r--r--core/java/android/view/contentcapture/ViewNode.java5
-rw-r--r--core/java/android/widget/Editor.java2
-rw-r--r--core/java/android/widget/RemoteViews.java5
-rw-r--r--core/java/android/window/TransitionInfo.java3
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java5
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java22
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl2
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl3
-rw-r--r--core/java/com/android/internal/util/LatencyTracker.java48
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java23
-rw-r--r--core/proto/android/os/system_properties.proto4
-rw-r--r--core/proto/android/providers/settings/global.proto4
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java107
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java9
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java33
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java65
-rw-r--r--core/tests/coretests/src/android/widget/RemoteViewsTest.java24
-rw-r--r--data/keyboards/Vendor_0957_Product_0001.kl3
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java21
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java15
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java16
-rw-r--r--libs/WindowManager/Shell/res/values/colors.xml2
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java115
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java9
-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.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/Android.bp98
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml (renamed from libs/WindowManager/Shell/tests/flicker/AndroidTest.xml)22
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml (renamed from libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java67
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java3
-rw-r--r--libs/hwui/MemoryPolicy.cpp3
-rw-r--r--libs/hwui/MemoryPolicy.h2
-rw-r--r--libs/hwui/renderthread/CacheManager.cpp5
-rw-r--r--media/java/android/media/MediaCodec.java7
-rw-r--r--media/java/android/media/MediaRouter2.java139
-rw-r--r--packages/CarrierDefaultApp/assets/slice_purchase_test.html6
-rw-r--r--packages/CarrierDefaultApp/assets/slice_purchase_test.js10
-rw-r--r--packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java (renamed from packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java)20
-rw-r--r--packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java22
-rw-r--r--packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java15
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java13
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java32
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java6
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java2
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt6
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt104
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/theme/SystemUIThemeTest.kt6
-rw-r--r--packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt4
-rw-r--r--packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt87
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt36
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt36
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt3
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml1
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_contents.xml2
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_face_view.xml2
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml2
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml2
-rw-r--r--packages/SystemUI/res/layout/biometric_prompt_layout.xml176
-rw-r--r--packages/SystemUI/res/layout/screen_record_dialog.xml4
-rw-r--r--packages/SystemUI/res/layout/volume_ringer_drawer.xml4
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java295
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt186
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModalities.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt (renamed from packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt)18
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java182
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt622
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt209
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptSize.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt453
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt80
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt136
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java122
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java123
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest2.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt84
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt204
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricModalitiesTest.kt102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt86
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt639
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt57
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt74
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt140
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt94
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java42
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt14
-rw-r--r--services/Android.bp7
-rw-r--r--services/accessibility/lint-baseline.xml26
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java3
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java3
-rw-r--r--services/backup/lint-baseline.xml24
-rw-r--r--services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java111
-rw-r--r--services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java7
-rw-r--r--services/core/java/com/android/server/DockObserver.java4
-rw-r--r--services/core/java/com/android/server/OWNERS3
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastConstants.java14
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java46
-rw-r--r--services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java16
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java19
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java35
-rw-r--r--services/core/java/com/android/server/biometrics/AuthSession.java56
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java25
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java16
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java16
-rw-r--r--[-rwxr-xr-x]services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java10
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java8
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java25
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsStorage.java5
-rw-r--r--services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java58
-rw-r--r--services/core/java/com/android/server/os/OWNERS3
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java3
-rw-r--r--services/core/java/com/android/server/policy/AppOpsPolicy.java15
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java16
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java64
-rw-r--r--services/core/java/com/android/server/power/stats/KernelWakelockReader.java95
-rw-r--r--services/core/java/com/android/server/power/stats/KernelWakelockStats.java16
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java2
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java7
-rw-r--r--services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java8
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java11
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java8
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java27
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java14
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java73
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java2
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java3
-rw-r--r--services/core/java/com/android/server/wm/Task.java3
-rw-r--r--services/core/java/com/android/server/wm/Transition.java19
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java8
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java6
-rw-r--r--services/core/lint-baseline.xml8
-rw-r--r--services/lint-baseline.xml59
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessPersistence.kt6
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessPolicy.kt12
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt4
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt6
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt4
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt4
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt6
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt10
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt28
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt50
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt12
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionService.kt56
-rw-r--r--services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt8
-rw-r--r--services/print/lint-baseline.xml15
-rw-r--r--services/proguard.flags13
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java54
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java43
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java40
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java50
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java236
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java69
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java73
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java52
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java90
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java38
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java5
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java29
-rw-r--r--telephony/java/android/telephony/AccessNetworkConstants.java310
-rw-r--r--telephony/java/android/telephony/BarringInfo.java28
-rw-r--r--telephony/java/android/telephony/NetworkRegistrationInfo.java4
-rw-r--r--telephony/java/android/telephony/data/ApnSetting.java9
-rw-r--r--telephony/java/android/telephony/data/QosBearerFilter.java24
-rw-r--r--telephony/java/android/telephony/emergency/EmergencyNumber.java13
-rw-r--r--tests/FlickerTests/Android.bp138
-rw-r--r--tests/FlickerTests/AndroidTestTemplate.xml (renamed from tests/FlickerTests/AndroidTest.xml)6
-rw-r--r--tests/FlickerTests/manifests/AndroidManifest.xml (renamed from tests/FlickerTests/AndroidManifest.xml)37
-rw-r--r--tests/FlickerTests/manifests/AndroidManifestAppClose.xml24
-rw-r--r--tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml24
-rw-r--r--tests/FlickerTests/manifests/AndroidManifestIme.xml24
-rw-r--r--tests/FlickerTests/manifests/AndroidManifestOther.xml24
-rw-r--r--tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml24
-rw-r--r--tests/FlickerTests/manifests/AndroidManifestRotation.xml24
323 files changed, 8925 insertions, 2411 deletions
diff --git a/Android.bp b/Android.bp
index 57b239e73ce9..72e519cc04ff 100644
--- a/Android.bp
+++ b/Android.bp
@@ -100,6 +100,10 @@ filegroup {
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
":android.hardware.keymaster-V4-java-source",
+ ":android.hardware.radio-V3-java-source",
+ ":android.hardware.radio.data-V3-java-source",
+ ":android.hardware.radio.network-V3-java-source",
+ ":android.hardware.radio.voice-V3-java-source",
":android.hardware.security.keymint-V3-java-source",
":android.hardware.security.secureclock-V1-java-source",
":android.hardware.thermal-V1-java-source",
@@ -212,12 +216,6 @@ java_library {
"android.hardware.gnss-V1.0-java",
"android.hardware.gnss-V2.1-java",
"android.hardware.health-V1.0-java-constants",
- "android.hardware.radio-V1.0-java",
- "android.hardware.radio-V1.1-java",
- "android.hardware.radio-V1.2-java",
- "android.hardware.radio-V1.3-java",
- "android.hardware.radio-V1.4-java",
- "android.hardware.radio-V1.5-java",
"android.hardware.radio-V1.6-java",
"android.hardware.radio.data-V3-java",
"android.hardware.radio.ims-V2-java",
@@ -650,8 +648,6 @@ stubs_defaults {
libs: [
"android.hardware.cas-V1.2-java",
"android.hardware.health-V1.0-java-constants",
- "android.hardware.radio-V1.5-java",
- "android.hardware.radio-V1.6-java",
"android.hardware.thermal-V1.0-java-constants",
"android.hardware.thermal-V2.0-java",
"android.hardware.tv.input-V1.0-java-constants",
diff --git a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
index a69d3ffa46fa..818e11b407d5 100644
--- a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
@@ -18,6 +18,7 @@ package android.view;
import static org.junit.Assert.assertTrue;
+import android.app.UiAutomation;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
@@ -29,8 +30,8 @@ import android.widget.LinearLayout;
import androidx.benchmark.BenchmarkState;
import androidx.benchmark.junit4.BenchmarkRule;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import org.junit.Rule;
@@ -142,6 +143,10 @@ public class ViewShowHidePerfTest {
}
private void testParentWithChild(TestCallback callback) throws Throwable {
+ // Make sure that a11y is disabled to prevent the test affected by accessibility events.
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation(UiAutomation.FLAG_DONT_USE_ACCESSIBILITY);
+
mActivityRule.runOnUiThread(() -> {
final BenchmarkState state = mBenchmarkRule.getState();
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
index a9f720ac2ba0..515ddc8d1d49 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
@@ -80,7 +80,7 @@ public class BenchmarkRunner {
private void prepareForNextRun() {
SystemClock.sleep(COOL_OFF_PERIOD_MS);
- ShellHelper.runShellCommand("am wait-for-broadcast-idle");
+ ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
mStartTimeNs = System.nanoTime();
mPausedDurationNs = 0;
}
@@ -102,7 +102,7 @@ public class BenchmarkRunner {
* to avoid unnecessary waiting.
*/
public void resumeTiming() {
- ShellHelper.runShellCommand("am wait-for-broadcast-idle");
+ ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
resumeTimer();
}
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 354c741a4bd2..da700aaca047 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -1541,7 +1541,8 @@ public class UserLifecycleTests {
private void waitForBroadcastIdle() {
try {
- ShellHelper.runShellCommandWithTimeout("am wait-for-broadcast-idle", TIMEOUT_IN_SECOND);
+ ShellHelper.runShellCommandWithTimeout(
+ "am wait-for-broadcast-idle --flush-broadcast-loopers", TIMEOUT_IN_SECOND);
} catch (TimeoutException e) {
Log.e(TAG, "Ending waitForBroadcastIdle because it is taking too long", e);
}
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 4e8bc7cfabf4..93d20dd199bf 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -213,7 +213,6 @@ java_defaults {
system_modules: "none",
java_version: "1.8",
compile_dex: true,
- defaults_visibility: ["//visibility:private"],
visibility: ["//visibility:public"],
}
@@ -230,8 +229,6 @@ java_defaults {
tag: ".jar",
dest: "android-non-updatable.jar",
},
- defaults_visibility: ["//visibility:private"],
- visibility: ["//visibility:private"],
}
java_library {
@@ -453,7 +450,6 @@ java_library {
java_genrule {
name: "android_stubs_private_hjar",
- visibility: ["//visibility:private"],
srcs: [":android_stubs_private_jar{.hjar}"],
out: ["android_stubs_private.jar"],
cmd: "cp $(in) $(out)",
@@ -462,7 +458,6 @@ java_genrule {
java_library {
name: "android_stubs_private",
defaults: ["android_stubs_dists_default"],
- visibility: ["//visibility:private"],
sdk_version: "none",
system_modules: "none",
static_libs: ["android_stubs_private_hjar"],
@@ -473,7 +468,6 @@ java_library {
java_genrule {
name: "android_stubs_private_framework_aidl",
- visibility: ["//visibility:private"],
tools: ["sdkparcelables"],
srcs: [":android_stubs_private"],
out: ["framework.aidl"],
@@ -637,7 +631,6 @@ droidstubs {
"metalava-manual",
],
args: priv_apps,
- visibility: ["//visibility:private"],
}
java_library {
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index de60581564b1..55ec7dae16b1 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -28,11 +28,14 @@ cc_defaults {
tidy_checks: [
"modernize-*",
"-modernize-avoid-c-arrays",
+ "-modernize-use-nodiscard",
"-modernize-use-trailing-return-type",
"android-*",
"misc-*",
+ "-misc-const-correctness",
"readability-*",
"-readability-identifier-length",
+ "-readability-implicit-bool-conversion",
],
tidy_checks_as_errors: [
"modernize-*",
@@ -56,6 +59,7 @@ cc_defaults {
"-readability-const-return-type",
"-readability-convert-member-functions-to-static",
"-readability-duplicate-include",
+ "-readability-implicit-bool-conversion",
"-readability-else-after-return",
"-readability-named-parameter",
"-readability-redundant-access-specifiers",
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 3b7ab9ce9560..b94b3b458065 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -59,7 +59,7 @@ using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask
namespace {
-constexpr const char* kFrameworkPath = "/system/framework/framework-res.apk";
+constexpr std::string_view kFrameworkPath = "/system/framework/framework-res.apk";
Status ok() {
return Status::ok();
@@ -207,23 +207,47 @@ Status Idmap2Service::createIdmap(const std::string& target_path, const std::str
idmap2::Result<Idmap2Service::TargetResourceContainerPtr> Idmap2Service::GetTargetContainer(
const std::string& target_path) {
- if (target_path == kFrameworkPath) {
- if (framework_apk_cache_ == nullptr) {
- // Initialize the framework APK cache.
- auto target = TargetResourceContainer::FromPath(target_path);
- if (!target) {
- return target.GetError();
+ const bool is_framework = target_path == kFrameworkPath;
+ bool use_cache;
+ struct stat st = {};
+ if (is_framework || !::stat(target_path.c_str(), &st)) {
+ use_cache = true;
+ } else {
+ LOG(WARNING) << "failed to stat target path '" << target_path << "' for the cache";
+ use_cache = false;
+ }
+
+ if (use_cache) {
+ std::lock_guard lock(container_cache_mutex_);
+ if (auto cache_it = container_cache_.find(target_path); cache_it != container_cache_.end()) {
+ const auto& item = cache_it->second;
+ if (is_framework ||
+ (item.dev == st.st_dev && item.inode == st.st_ino && item.size == st.st_size
+ && item.mtime.tv_sec == st.st_mtim.tv_sec && item.mtime.tv_nsec == st.st_mtim.tv_nsec)) {
+ return {item.apk.get()};
}
- framework_apk_cache_ = std::move(*target);
+ container_cache_.erase(cache_it);
}
- return {framework_apk_cache_.get()};
}
auto target = TargetResourceContainer::FromPath(target_path);
if (!target) {
return target.GetError();
}
- return {std::move(*target)};
+ if (!use_cache) {
+ return {std::move(*target)};
+ }
+
+ const auto res = target->get();
+ std::lock_guard lock(container_cache_mutex_);
+ container_cache_.emplace(target_path, CachedContainer {
+ .dev = dev_t(st.st_dev),
+ .inode = ino_t(st.st_ino),
+ .size = st.st_size,
+ .mtime = st.st_mtim,
+ .apk = std::move(*target)
+ });
+ return {res};
}
Status Idmap2Service::createFabricatedOverlay(
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index cc8cc5f218d2..a69fa6119974 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -75,7 +75,20 @@ class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 {
private:
// idmap2d is killed after a period of inactivity, so any information stored on this class should
// be able to be recalculated if idmap2 dies and restarts.
- std::unique_ptr<idmap2::TargetResourceContainer> framework_apk_cache_;
+
+ // A cache item for the resource containers (apks or frros), with all information needed to
+ // detect if it has changed since it was parsed:
+ // - (dev, inode) pair uniquely identifies a file on a particular device partition (see stat(2)).
+ // - (mtime, size) ensure the file data hasn't changed inside that file.
+ struct CachedContainer {
+ dev_t dev;
+ ino_t inode;
+ int64_t size;
+ struct timespec mtime;
+ std::unique_ptr<idmap2::TargetResourceContainer> apk;
+ };
+ std::unordered_map<std::string, CachedContainer> container_cache_;
+ std::mutex container_cache_mutex_;
int32_t frro_iter_id_ = 0;
std::optional<std::filesystem::directory_iterator> frro_iter_;
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index c2f010097fda..05a43ad7d936 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -536,7 +536,7 @@ status_t IncidentService::onTransact(uint32_t code, const Parcel& data, Parcel*
fflush(fout);
fclose(fout);
}
- if (fout != NULL) {
+ if (ferr != NULL) {
fflush(ferr);
fclose(ferr);
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 83a92e28d252..1d3e275ceb3a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12181,32 +12181,32 @@ package android.content.pm {
field public static final int REQUESTED_PERMISSION_GRANTED = 2; // 0x2
field public static final int REQUESTED_PERMISSION_IMPLICIT = 4; // 0x4
field public static final int REQUESTED_PERMISSION_NEVER_FOR_LOCATION = 65536; // 0x10000
- field public android.content.pm.ActivityInfo[] activities;
+ field @Nullable public android.content.pm.ActivityInfo[] activities;
field @Nullable public android.content.pm.ApplicationInfo applicationInfo;
field @Nullable public android.content.pm.Attribution[] attributions;
field public int baseRevisionCode;
- field public android.content.pm.ConfigurationInfo[] configPreferences;
- field public android.content.pm.FeatureGroupInfo[] featureGroups;
+ field @Nullable public android.content.pm.ConfigurationInfo[] configPreferences;
+ field @Nullable public android.content.pm.FeatureGroupInfo[] featureGroups;
field public long firstInstallTime;
- field public int[] gids;
+ field @Nullable public int[] gids;
field public int installLocation;
- field public android.content.pm.InstrumentationInfo[] instrumentation;
+ field @Nullable public android.content.pm.InstrumentationInfo[] instrumentation;
field public boolean isApex;
field public long lastUpdateTime;
- field public String packageName;
- field public android.content.pm.PermissionInfo[] permissions;
- field public android.content.pm.ProviderInfo[] providers;
- field public android.content.pm.ActivityInfo[] receivers;
- field public android.content.pm.FeatureInfo[] reqFeatures;
- field public String[] requestedPermissions;
- field public int[] requestedPermissionsFlags;
- field public android.content.pm.ServiceInfo[] services;
- field public String sharedUserId;
+ field @NonNull public String packageName;
+ field @Nullable public android.content.pm.PermissionInfo[] permissions;
+ field @Nullable public android.content.pm.ProviderInfo[] providers;
+ field @Nullable public android.content.pm.ActivityInfo[] receivers;
+ field @Nullable public android.content.pm.FeatureInfo[] reqFeatures;
+ field @Nullable public String[] requestedPermissions;
+ field @Nullable public int[] requestedPermissionsFlags;
+ field @Nullable public android.content.pm.ServiceInfo[] services;
+ field @Nullable public String sharedUserId;
field public int sharedUserLabel;
- field @Deprecated public android.content.pm.Signature[] signatures;
- field public android.content.pm.SigningInfo signingInfo;
- field public String[] splitNames;
- field public int[] splitRevisionCodes;
+ field @Deprecated @Nullable public android.content.pm.Signature[] signatures;
+ field @Nullable public android.content.pm.SigningInfo signingInfo;
+ field @NonNull public String[] splitNames;
+ field @NonNull public int[] splitRevisionCodes;
field @Deprecated public int versionCode;
field @Nullable public String versionName;
}
@@ -13003,7 +13003,7 @@ package android.content.pm {
method public final int getIconResource();
method public boolean isCrossProfileIntentForwarderActivity();
method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
- method public CharSequence loadLabel(android.content.pm.PackageManager);
+ method @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ResolveInfo> CREATOR;
field public android.content.pm.ActivityInfo activityInfo;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 99814770dd77..b60b63c9b1d8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5006,12 +5006,6 @@ public class Notification implements Parcelable
return mUserExtras;
}
- private Bundle getAllExtras() {
- final Bundle saveExtras = (Bundle) mUserExtras.clone();
- saveExtras.putAll(mN.extras);
- return saveExtras;
- }
-
/**
* Add an action to this notification. Actions are typically displayed by
* the system as a button adjacent to the notification content.
@@ -6617,9 +6611,16 @@ public class Notification implements Parcelable
+ " vs bubble: " + mN.mBubbleMetadata.getShortcutId());
}
- // first, add any extras from the calling code
+ // Adds any new extras provided by the user.
if (mUserExtras != null) {
- mN.extras = getAllExtras();
+ final Bundle saveExtras = (Bundle) mUserExtras.clone();
+ if (SystemProperties.getBoolean(
+ "persist.sysui.notification.builder_extras_override", false)) {
+ mN.extras.putAll(saveExtras);
+ } else {
+ saveExtras.putAll(mN.extras);
+ mN.extras = saveExtras;
+ }
}
mN.creationTime = System.currentTimeMillis();
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 776e34bb4792..385fd509757b 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -24,9 +24,11 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.UserIdInt;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
+import android.compat.annotation.LoggingOnly;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -49,6 +51,7 @@ import android.util.Slog;
import android.view.KeyEvent;
import android.view.View;
+import com.android.internal.compat.IPlatformCompat;
import com.android.internal.statusbar.AppClipsServiceConnector;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IStatusBarService;
@@ -170,6 +173,8 @@ public class StatusBarManager {
public @interface Disable2Flags {}
// LINT.ThenChange(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt)
+ private static final String TAG = "StatusBarManager";
+
/**
* Default disable flags for setup
*
@@ -572,13 +577,13 @@ public class StatusBarManager {
private static final long MEDIA_CONTROL_SESSION_ACTIONS = 203800354L;
/**
- * Media controls based on {@link android.app.Notification.MediaStyle} notifications will be
- * required to include a non-empty title, either in the {@link android.media.MediaMetadata} or
+ * Media controls based on {@link android.app.Notification.MediaStyle} notifications should
+ * include a non-empty title, either in the {@link android.media.MediaMetadata} or
* notification title.
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- private static final long MEDIA_CONTROL_REQUIRES_TITLE = 274775190L;
+ @LoggingOnly
+ private static final long MEDIA_CONTROL_BLANK_TITLE = 274775190L;
@UnsupportedAppUsage
private Context mContext;
@@ -586,6 +591,9 @@ public class StatusBarManager {
@UnsupportedAppUsage
private IBinder mToken = new Binder();
+ private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
@UnsupportedAppUsage
StatusBarManager(Context context) {
mContext = context;
@@ -597,7 +605,7 @@ public class StatusBarManager {
mService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
if (mService == null) {
- Slog.w("StatusBarManager", "warning: no STATUS_BAR_SERVICE");
+ Slog.w(TAG, "warning: no STATUS_BAR_SERVICE");
}
}
return mService;
@@ -1226,18 +1234,22 @@ public class StatusBarManager {
}
/**
- * Checks whether the given package must include a non-empty title for its media controls.
+ * Log that the given package has posted media controls with a blank title
*
* @param packageName App posting media controls
- * @param user Current user handle
- * @return true if the app is required to provide a non-empty title
+ * @param userId Current user ID
+ * @throws RuntimeException if there is an error reporting the change
*
* @hide
*/
- @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
- android.Manifest.permission.LOG_COMPAT_CHANGE})
- public static boolean isMediaTitleRequiredForApp(String packageName, UserHandle user) {
- return CompatChanges.isChangeEnabled(MEDIA_CONTROL_REQUIRES_TITLE, packageName, user);
+ public void logBlankMediaTitle(String packageName, @UserIdInt int userId)
+ throws RuntimeException {
+ try {
+ mPlatformCompat.reportChangeByPackageName(MEDIA_CONTROL_BLANK_TITLE, packageName,
+ userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 8977a30fbee8..63c11b779641 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -31,11 +32,13 @@ public class PackageInfo implements Parcelable {
* The name of this package. From the &lt;manifest&gt; tag's "name"
* attribute.
*/
+ @NonNull
public String packageName;
/**
* The names of any installed split APKs for this package.
*/
+ @NonNull
public String[] splitNames;
/**
@@ -110,6 +113,7 @@ public class PackageInfo implements Parcelable {
* {@link android.R.styleable#AndroidManifest_revisionCode revisionCode}
* attribute. Indexes are a 1:1 mapping against {@link #splitNames}.
*/
+ @NonNull
public int[] splitRevisionCodes;
/**
@@ -117,6 +121,7 @@ public class PackageInfo implements Parcelable {
* tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId}
* attribute.
*/
+ @Nullable
public String sharedUserId;
/**
@@ -149,6 +154,7 @@ public class PackageInfo implements Parcelable {
* All kernel group-IDs that have been assigned to this package.
* This is only filled in if the flag {@link PackageManager#GET_GIDS} was set.
*/
+ @Nullable
public int[] gids;
/**
@@ -157,6 +163,7 @@ public class PackageInfo implements Parcelable {
* or null if there were none. This is only filled in if the flag
* {@link PackageManager#GET_ACTIVITIES} was set.
*/
+ @Nullable
public ActivityInfo[] activities;
/**
@@ -165,6 +172,7 @@ public class PackageInfo implements Parcelable {
* or null if there were none. This is only filled in if the flag
* {@link PackageManager#GET_RECEIVERS} was set.
*/
+ @Nullable
public ActivityInfo[] receivers;
/**
@@ -173,6 +181,7 @@ public class PackageInfo implements Parcelable {
* or null if there were none. This is only filled in if the flag
* {@link PackageManager#GET_SERVICES} was set.
*/
+ @Nullable
public ServiceInfo[] services;
/**
@@ -181,6 +190,7 @@ public class PackageInfo implements Parcelable {
* or null if there were none. This is only filled in if the flag
* {@link PackageManager#GET_PROVIDERS} was set.
*/
+ @Nullable
public ProviderInfo[] providers;
/**
@@ -189,6 +199,7 @@ public class PackageInfo implements Parcelable {
* or null if there were none. This is only filled in if the flag
* {@link PackageManager#GET_INSTRUMENTATION} was set.
*/
+ @Nullable
public InstrumentationInfo[] instrumentation;
/**
@@ -197,6 +208,7 @@ public class PackageInfo implements Parcelable {
* or null if there were none. This is only filled in if the flag
* {@link PackageManager#GET_PERMISSIONS} was set.
*/
+ @Nullable
public PermissionInfo[] permissions;
/**
@@ -207,6 +219,7 @@ public class PackageInfo implements Parcelable {
* all permissions requested, even those that were not granted or known
* by the system at install time.
*/
+ @Nullable
public String[] requestedPermissions;
/**
@@ -218,6 +231,7 @@ public class PackageInfo implements Parcelable {
* the flags {@link #REQUESTED_PERMISSION_GRANTED} and
* {@link #REQUESTED_PERMISSION_NEVER_FOR_LOCATION} set as appropriate.
*/
+ @Nullable
public int[] requestedPermissionsFlags;
/**
@@ -226,7 +240,8 @@ public class PackageInfo implements Parcelable {
* is only filled if the flag {@link PackageManager#GET_ATTRIBUTIONS_LONG} was set.
*/
@SuppressWarnings({"ArrayReturn", "NullableCollection"})
- public @Nullable Attribution[] attributions;
+ @Nullable
+ public Attribution[] attributions;
/**
* Flag for {@link #requestedPermissionsFlags}: the requested permission
@@ -283,6 +298,7 @@ public class PackageInfo implements Parcelable {
* @deprecated use {@code signingInfo} instead
*/
@Deprecated
+ @Nullable
public Signature[] signatures;
/**
@@ -294,6 +310,7 @@ public class PackageInfo implements Parcelable {
* Use this field instead of the deprecated {@code signatures} field.
* See {@link SigningInfo} for more information on its contents.
*/
+ @Nullable
public SigningInfo signingInfo;
/**
@@ -303,6 +320,7 @@ public class PackageInfo implements Parcelable {
* or null if there were none. This is only filled in if the flag
* {@link PackageManager#GET_CONFIGURATIONS} was set.
*/
+ @Nullable
public ConfigurationInfo[] configPreferences;
/**
@@ -310,6 +328,7 @@ public class PackageInfo implements Parcelable {
*
* @see FeatureInfo#FLAG_REQUIRED
*/
+ @Nullable
public FeatureInfo[] reqFeatures;
/**
@@ -320,6 +339,7 @@ public class PackageInfo implements Parcelable {
*
* @see FeatureInfo#FLAG_REQUIRED
*/
+ @Nullable
public FeatureGroupInfo[] featureGroups;
/**
@@ -388,12 +408,14 @@ public class PackageInfo implements Parcelable {
* The restricted account authenticator type that is used by this application.
* @hide
*/
+ @Nullable
public String restrictedAccountType;
/**
* The required account type without which this application will not function.
* @hide
*/
+ @Nullable
public String requiredAccountType;
/**
@@ -403,6 +425,7 @@ public class PackageInfo implements Parcelable {
* @hide
*/
@UnsupportedAppUsage
+ @Nullable
public String overlayTarget;
/**
@@ -411,6 +434,7 @@ public class PackageInfo implements Parcelable {
* Overlayable name defined within the target package, or null.
* @hide
*/
+ @Nullable
public String targetOverlayableName;
/**
@@ -418,6 +442,7 @@ public class PackageInfo implements Parcelable {
*
* @hide
*/
+ @Nullable
public String overlayCategory;
/** @hide */
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1973c627d2bf..dca4544f9a14 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2883,6 +2883,20 @@ public abstract class PackageManager {
"android.software.car.templates_host";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:If this
+ * feature is supported, the device should also declare {@link #FEATURE_AUTOMOTIVE} and show
+ * a UI that can display multiple tasks at the same time on a single display. The user can
+ * perform multiple actions on different tasks simultaneously. Apps open in split screen mode
+ * by default, instead of full screen. Unlike Android's multi-window mode, where users can
+ * choose how to display apps, the device determines how apps are shown.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAR_SPLITSCREEN_MULTITASKING =
+ "android.software.car.splitscreen_multitasking";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports
* {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index f90044027b09..02a4980d014f 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -33,6 +34,7 @@ import android.util.Slog;
import java.text.Collator;
import java.util.Comparator;
+import java.util.Objects;
/**
* Information that is returned from resolving an intent
@@ -224,10 +226,17 @@ public class ResolveInfo implements Parcelable {
* @return Returns a CharSequence containing the resolutions's label. If the
* item does not have a label, its name is returned.
*/
- public CharSequence loadLabel(PackageManager pm) {
+ @NonNull
+ public CharSequence loadLabel(@NonNull PackageManager pm) {
if (nonLocalizedLabel != null) {
return nonLocalizedLabel;
}
+
+ // In order to not change the original behavior. To add null check here to support backward
+ // compatible. If nonLocalizedLabel is not null, we also return nonLocalizedLabel even if pm
+ // is null.
+ Objects.requireNonNull(pm);
+
CharSequence label;
if (resolvePackageName != null && labelRes != 0) {
label = pm.getText(resolvePackageName, labelRes, null);
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 6b044fc5bfb6..82694ee3463b 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -97,27 +97,6 @@ public class BiometricManager {
public @interface BiometricError {}
/**
- * Single sensor or unspecified multi-sensor behavior (prefer an explicit choice if the
- * device is multi-sensor).
- * @hide
- */
- public static final int BIOMETRIC_MULTI_SENSOR_DEFAULT = 0;
-
- /**
- * Use face and fingerprint sensors together.
- * @hide
- */
- public static final int BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE = 1;
-
- /**
- * @hide
- */
- @IntDef({BIOMETRIC_MULTI_SENSOR_DEFAULT,
- BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE})
- @Retention(RetentionPolicy.SOURCE)
- public @interface BiometricMultiSensorMode {}
-
- /**
* Types of authenticators, defined at a level of granularity supported by
* {@link BiometricManager} and {@link BiometricPrompt}.
*
diff --git a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
index 450c5ceab04c..45f1c8ae26c0 100644
--- a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl
@@ -29,5 +29,7 @@ oneway interface IBiometricSysuiReceiver {
// Notifies the client that an internal event, e.g. back button has occurred.
void onSystemEvent(int event);
// Notifies that the dialog has finished animating.
- void onDialogAnimatedIn();
+ void onDialogAnimatedIn(boolean startFingerprintNow);
+ // Notifies that the fingerprint should start now (after onDialogAnimatedIn(false)).
+ void onStartFingerprintNow();
}
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 69105016e0ea..78388efe98c7 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -246,8 +246,7 @@ final class NavigationBarController {
@Override
public void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
@NonNull ViewTreeObserver.InternalInsetsInfo dest) {
- if (!mImeDrawsImeNavBar || mNavigationBarFrame == null
- || mService.isExtractViewShown()) {
+ if (!mImeDrawsImeNavBar || mNavigationBarFrame == null) {
return;
}
@@ -255,53 +254,58 @@ final class NavigationBarController {
if (systemInsets != null) {
final Window window = mService.mWindow.getWindow();
final View decor = window.getDecorView();
- Region touchableRegion = null;
- final View inputFrame = mService.mInputFrame;
- switch (originalInsets.touchableInsets) {
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
- if (inputFrame.getVisibility() == View.VISIBLE) {
- inputFrame.getLocationInWindow(mTempPos);
- mTempRect.set(mTempPos[0], mTempPos[1],
- mTempPos[0] + inputFrame.getWidth(),
- mTempPos[1] + inputFrame.getHeight());
- touchableRegion = new Region(mTempRect);
- }
- break;
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
- if (inputFrame.getVisibility() == View.VISIBLE) {
- inputFrame.getLocationInWindow(mTempPos);
- mTempRect.set(mTempPos[0], originalInsets.contentTopInsets,
- mTempPos[0] + inputFrame.getWidth() ,
- mTempPos[1] + inputFrame.getHeight());
- touchableRegion = new Region(mTempRect);
- }
- break;
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
- if (inputFrame.getVisibility() == View.VISIBLE) {
- inputFrame.getLocationInWindow(mTempPos);
- mTempRect.set(mTempPos[0], originalInsets.visibleTopInsets,
- mTempPos[0] + inputFrame.getWidth(),
- mTempPos[1] + inputFrame.getHeight());
- touchableRegion = new Region(mTempRect);
- }
- break;
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION:
- touchableRegion = new Region();
- touchableRegion.set(originalInsets.touchableRegion);
- break;
- }
- // Hereafter "mTempRect" means a navigation bar rect.
- mTempRect.set(decor.getLeft(), decor.getBottom() - systemInsets.bottom,
- decor.getRight(), decor.getBottom());
- if (touchableRegion == null) {
- touchableRegion = new Region(mTempRect);
- } else {
- touchableRegion.union(mTempRect);
- }
- dest.touchableRegion.set(touchableRegion);
- dest.setTouchableInsets(
- ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ // If the extract view is shown, everything is touchable, so no need to update
+ // touchable insets, but we still update normal insets below.
+ if (!mService.isExtractViewShown()) {
+ Region touchableRegion = null;
+ final View inputFrame = mService.mInputFrame;
+ switch (originalInsets.touchableInsets) {
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
+ if (inputFrame.getVisibility() == View.VISIBLE) {
+ inputFrame.getLocationInWindow(mTempPos);
+ mTempRect.set(mTempPos[0], mTempPos[1],
+ mTempPos[0] + inputFrame.getWidth(),
+ mTempPos[1] + inputFrame.getHeight());
+ touchableRegion = new Region(mTempRect);
+ }
+ break;
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
+ if (inputFrame.getVisibility() == View.VISIBLE) {
+ inputFrame.getLocationInWindow(mTempPos);
+ mTempRect.set(mTempPos[0], originalInsets.contentTopInsets,
+ mTempPos[0] + inputFrame.getWidth(),
+ mTempPos[1] + inputFrame.getHeight());
+ touchableRegion = new Region(mTempRect);
+ }
+ break;
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
+ if (inputFrame.getVisibility() == View.VISIBLE) {
+ inputFrame.getLocationInWindow(mTempPos);
+ mTempRect.set(mTempPos[0], originalInsets.visibleTopInsets,
+ mTempPos[0] + inputFrame.getWidth(),
+ mTempPos[1] + inputFrame.getHeight());
+ touchableRegion = new Region(mTempRect);
+ }
+ break;
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION:
+ touchableRegion = new Region();
+ touchableRegion.set(originalInsets.touchableRegion);
+ break;
+ }
+ // Hereafter "mTempRect" means a navigation bar rect.
+ mTempRect.set(decor.getLeft(), decor.getBottom() - systemInsets.bottom,
+ decor.getRight(), decor.getBottom());
+ if (touchableRegion == null) {
+ touchableRegion = new Region(mTempRect);
+ } else {
+ touchableRegion.union(mTempRect);
+ }
+
+ dest.touchableRegion.set(touchableRegion);
+ dest.setTouchableInsets(
+ ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ }
// TODO(b/215443343): See if we can use View#OnLayoutChangeListener().
// TODO(b/215443343): See if we can replace DecorView#mNavigationColorViewState.view
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 6f2a915cee46..3f4013908612 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -37,7 +37,6 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.preference.VolumePreference.VolumeStore;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.System;
@@ -47,7 +46,6 @@ import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.os.SomeArgs;
import java.util.concurrent.TimeUnit;
@@ -295,14 +293,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
if (zenMuted) {
mSeekBar.setProgress(mLastAudibleStreamVolume, true);
} else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
- /**
- * the first variable above is preserved and the conditions below are made explicit
- * so that when user attempts to slide the notification seekbar out of vibrate the
- * seekbar doesn't wrongly snap back to 0 when the streams aren't aliased
- */
- if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
- || mStreamType == AudioManager.STREAM_RING
+ // For ringer-mode affected streams, show volume as zero when ringermode is vibrate
+ if (mStreamType == AudioManager.STREAM_RING
|| (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
mSeekBar.setProgress(0, true);
}
@@ -397,9 +389,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
// set the time of stop volume
if ((mStreamType == AudioManager.STREAM_VOICE_CALL
|| mStreamType == AudioManager.STREAM_RING
- || (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
- && mStreamType == AudioManager.STREAM_NOTIFICATION)
+ || mStreamType == AudioManager.STREAM_NOTIFICATION
|| mStreamType == AudioManager.STREAM_ALARM)) {
sStopVolumeTime = java.lang.System.currentTimeMillis();
}
@@ -686,10 +676,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
}
private void updateVolumeSlider(int streamType, int streamValue) {
- final boolean streamMatch = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
- && mNotificationOrRing ? isNotificationOrRing(streamType) :
- streamType == mStreamType;
+ final boolean streamMatch = (streamType == mStreamType);
if (mSeekBar != null && streamMatch && streamValue != -1) {
final boolean muted = mAudioManager.isStreamMute(mStreamType)
|| streamValue == 0;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dce4902d2970..c444156426f4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -15376,18 +15376,6 @@ public final class Settings {
public static final String ANGLE_EGL_FEATURES = "angle_egl_features";
/**
- * Comma-separated list of package names that ANGLE may have issues with
- * @hide
- */
- public static final String ANGLE_DEFERLIST = "angle_deferlist";
-
- /**
- * Integer mode of the logic for applying `angle_deferlist`
- * @hide
- */
- public static final String ANGLE_DEFERLIST_MODE = "angle_deferlist_mode";
-
- /**
* Show the "ANGLE In Use" dialog box to the user when ANGLE is the OpenGL driver.
* The value is a boolean (1 or 0).
* @hide
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 25ee6aff6ac4..795500aef103 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -275,8 +275,12 @@ public class DisplayMetrics {
*/
public float density;
/**
- * The screen density expressed as dots-per-inch. May be either
- * {@link #DENSITY_LOW}, {@link #DENSITY_MEDIUM}, or {@link #DENSITY_HIGH}.
+ * The screen density expressed as dots-per-inch. May be any one of the
+ * {@code DENSITY_} constants defined above.
+ *
+ * New constants are frequently added, and constants added on new Android
+ * versions may be backported to previous Android versions, so applications
+ * should not strongly rely on density matching one of the enum constants.
*/
public int densityDpi;
/**
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 470c2801d838..a47f34f4125e 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -164,6 +164,10 @@ public class InsetsFrameProvider implements Parcelable {
return mFlags;
}
+ public boolean hasFlags(@Flags int mask) {
+ return (mFlags & mask) == mask;
+ }
+
public InsetsFrameProvider setInsetsSize(Insets insetsSize) {
mInsetsSize = insetsSize;
return this;
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 9fc42fff7084..e10184976abe 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -58,9 +58,20 @@ public class InsetsSource implements Parcelable {
*/
public static final int FLAG_SUPPRESS_SCRIM = 1;
+ /**
+ * Controls whether the insets frame will be used to move {@link RoundedCorner} inward with the
+ * insets frame size when calculating the rounded corner insets to other windows.
+ *
+ * For example, task bar will draw fake rounded corners above itself, so we need to move the
+ * rounded corner up by the task bar insets size to make other windows see a rounded corner
+ * above the task bar.
+ */
+ public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = "FLAG_", value = {
FLAG_SUPPRESS_SCRIM,
+ FLAG_INSETS_ROUNDED_CORNER,
})
public @interface Flags {}
@@ -78,7 +89,6 @@ public class InsetsSource implements Parcelable {
private @Nullable Rect mVisibleFrame;
private boolean mVisible;
- private boolean mInsetsRoundedCornerFrame;
private final Rect mTmpFrame = new Rect();
@@ -98,7 +108,6 @@ public class InsetsSource implements Parcelable {
? new Rect(other.mVisibleFrame)
: null;
mFlags = other.mFlags;
- mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame;
}
public void set(InsetsSource other) {
@@ -108,7 +117,6 @@ public class InsetsSource implements Parcelable {
? new Rect(other.mVisibleFrame)
: null;
mFlags = other.mFlags;
- mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame;
}
public InsetsSource setFrame(int left, int top, int right, int bottom) {
@@ -136,6 +144,11 @@ public class InsetsSource implements Parcelable {
return this;
}
+ public InsetsSource setFlags(@Flags int flags, @Flags int mask) {
+ mFlags = (mFlags & ~mask) | (flags & mask);
+ return this;
+ }
+
public int getId() {
return mId;
}
@@ -160,20 +173,15 @@ public class InsetsSource implements Parcelable {
return mFlags;
}
+ public boolean hasFlags(int flags) {
+ return (mFlags & flags) == flags;
+ }
+
boolean isUserControllable() {
// If mVisibleFrame is null, it will be the same area as mFrame.
return mVisibleFrame == null || !mVisibleFrame.isEmpty();
}
- public boolean insetsRoundedCornerFrame() {
- return mInsetsRoundedCornerFrame;
- }
-
- public InsetsSource setInsetsRoundedCornerFrame(boolean insetsRoundedCornerFrame) {
- mInsetsRoundedCornerFrame = insetsRoundedCornerFrame;
- return this;
- }
-
/**
* Calculates the insets this source will cause to a client window.
*
@@ -317,6 +325,9 @@ public class InsetsSource implements Parcelable {
if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
joiner.add("SUPPRESS_SCRIM");
}
+ if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) {
+ joiner.add("INSETS_ROUNDED_CORNER");
+ }
return joiner.toString();
}
@@ -347,7 +358,6 @@ public class InsetsSource implements Parcelable {
}
pw.print(" visible="); pw.print(mVisible);
pw.print(" flags="); pw.print(flagsToString(mFlags));
- pw.print(" insetsRoundedCornerFrame="); pw.print(mInsetsRoundedCornerFrame);
pw.println();
}
@@ -372,14 +382,12 @@ public class InsetsSource implements Parcelable {
if (mFlags != that.mFlags) return false;
if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
- if (mInsetsRoundedCornerFrame != that.mInsetsRoundedCornerFrame) return false;
return mFrame.equals(that.mFrame);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags,
- mInsetsRoundedCornerFrame);
+ return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags);
}
public InsetsSource(Parcel in) {
@@ -393,7 +401,6 @@ public class InsetsSource implements Parcelable {
}
mVisible = in.readBoolean();
mFlags = in.readInt();
- mInsetsRoundedCornerFrame = in.readBoolean();
}
@Override
@@ -414,7 +421,6 @@ public class InsetsSource implements Parcelable {
}
dest.writeBoolean(mVisible);
dest.writeInt(mFlags);
- dest.writeBoolean(mInsetsRoundedCornerFrame);
}
@Override
@@ -424,7 +430,6 @@ public class InsetsSource implements Parcelable {
+ " mFrame=" + mFrame.toShortString()
+ " mVisible=" + mVisible
+ " mFlags=[" + flagsToString(mFlags) + "]"
- + (mInsetsRoundedCornerFrame ? " insetsRoundedCornerFrame" : "")
+ "}";
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 5b974cdb2bca..61a72772200c 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
import static android.view.InsetsStateProto.DISPLAY_FRAME;
import static android.view.InsetsStateProto.SOURCES;
@@ -219,7 +220,7 @@ public class InsetsState implements Parcelable {
final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame);
for (int i = mSources.size() - 1; i >= 0; i--) {
final InsetsSource source = mSources.valueAt(i);
- if (source.insetsRoundedCornerFrame()) {
+ if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) {
final Insets insets = source.calculateInsets(roundedCornerFrame, false);
roundedCornerFrame.inset(insets);
}
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 095189ad03a7..67ac811287cb 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -62,7 +62,6 @@ public class SurfaceControlRegistry {
private static class DefaultReporter implements Reporter {
public void onMaxLayersExceeded(WeakHashMap<SurfaceControl, Long> surfaceControls,
int limit, PrintWriter pw) {
- final int size = Math.min(surfaceControls.size(), limit);
final long now = SystemClock.elapsedRealtime();
final ArrayList<Map.Entry<SurfaceControl, Long>> entries = new ArrayList<>();
for (Map.Entry<SurfaceControl, Long> entry : surfaceControls.entrySet()) {
@@ -71,6 +70,7 @@ public class SurfaceControlRegistry {
// Sort entries by time registered when dumping
// TODO: Or should it sort by name?
entries.sort((o1, o2) -> (int) (o1.getValue() - o2.getValue()));
+ final int size = Math.min(entries.size(), limit);
pw.println("SurfaceControlRegistry");
pw.println("----------------------");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f17ae829f817..36954a9941c4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7734,13 +7734,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
- final ListenerInfo li = mListenerInfo;
+ final OnLongClickListener listener =
+ mListenerInfo == null ? null : mListenerInfo.mOnLongClickListener;
boolean shouldPerformHapticFeedback = true;
- if (li != null && li.mOnLongClickListener != null) {
- handled = li.mOnLongClickListener.onLongClick(View.this);
+ if (listener != null) {
+ handled = listener.onLongClick(View.this);
if (handled) {
- shouldPerformHapticFeedback =
- li.mOnLongClickListener.onLongClickUseDefaultHapticFeedback(View.this);
+ shouldPerformHapticFeedback = listener.onLongClickUseDefaultHapticFeedback(
+ View.this);
}
}
if (!handled) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e95ba797a985..0e72ea8622e2 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -4244,17 +4244,6 @@ public interface WindowManager extends ViewManager {
public InsetsFrameProvider[] providedInsets;
/**
- * If specified, the frame that used to calculate relative {@link RoundedCorner} will be
- * the window frame of this window minus the insets that this window provides.
- *
- * Task bar will draw fake rounded corners above itself, so we need this insets to calculate
- * correct rounded corners for this window.
- *
- * @hide
- */
- public boolean insetsRoundedCornerFrame = false;
-
- /**
* {@link LayoutParams} to be applied to the window when layout with a assigned rotation.
* This will make layout during rotation change smoothly.
*
@@ -4710,7 +4699,6 @@ public interface WindowManager extends ViewManager {
out.writeBoolean(mFitInsetsIgnoringVisibility);
out.writeBoolean(preferMinimalPostProcessing);
out.writeInt(mBlurBehindRadius);
- out.writeBoolean(insetsRoundedCornerFrame);
out.writeBoolean(mWallpaperTouchEventsEnabled);
out.writeTypedArray(providedInsets, 0 /* parcelableFlags */);
checkNonRecursiveParams();
@@ -4782,7 +4770,6 @@ public interface WindowManager extends ViewManager {
mFitInsetsIgnoringVisibility = in.readBoolean();
preferMinimalPostProcessing = in.readBoolean();
mBlurBehindRadius = in.readInt();
- insetsRoundedCornerFrame = in.readBoolean();
mWallpaperTouchEventsEnabled = in.readBoolean();
providedInsets = in.createTypedArray(InsetsFrameProvider.CREATOR);
paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
@@ -5090,11 +5077,6 @@ public interface WindowManager extends ViewManager {
changes |= LAYOUT_CHANGED;
}
- if (insetsRoundedCornerFrame != o.insetsRoundedCornerFrame) {
- insetsRoundedCornerFrame = o.insetsRoundedCornerFrame;
- changes |= LAYOUT_CHANGED;
- }
-
if (paramsForRotation != o.paramsForRotation) {
if ((changes & LAYOUT_CHANGED) == 0) {
if (paramsForRotation != null && o.paramsForRotation != null
@@ -5332,10 +5314,6 @@ public interface WindowManager extends ViewManager {
sb.append(prefix).append(" ").append(providedInsets[i]);
}
}
- if (insetsRoundedCornerFrame) {
- sb.append(" insetsRoundedCornerFrame=");
- sb.append(insetsRoundedCornerFrame);
- }
if (paramsForRotation != null && paramsForRotation.length != 0) {
sb.append(System.lineSeparator());
sb.append(prefix).append(" paramsForRotation:");
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 62044aa78213..b22910648e71 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -181,6 +181,8 @@ public abstract class ContentCaptureSession implements AutoCloseable {
public static final int FLUSH_REASON_VIEW_TREE_APPEARING = 9;
/** @hide */
public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10;
+ /** @hide */
+ public static final int FLUSH_REASON_LOGIN_DETECTED = 11;
/**
* After {@link UPSIDE_DOWN_CAKE}, {@link #notifyViewsDisappeared(AutofillId, long[])} wraps
@@ -191,20 +193,23 @@ public abstract class ContentCaptureSession implements AutoCloseable {
static final long NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS = 258825825L;
/** @hide */
- @IntDef(prefix = { "FLUSH_REASON_" }, value = {
- FLUSH_REASON_FULL,
- FLUSH_REASON_VIEW_ROOT_ENTERED,
- FLUSH_REASON_SESSION_STARTED,
- FLUSH_REASON_SESSION_FINISHED,
- FLUSH_REASON_IDLE_TIMEOUT,
- FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
- FLUSH_REASON_SESSION_CONNECTED,
- FLUSH_REASON_FORCE_FLUSH,
- FLUSH_REASON_VIEW_TREE_APPEARING,
- FLUSH_REASON_VIEW_TREE_APPEARED
- })
+ @IntDef(
+ prefix = {"FLUSH_REASON_"},
+ value = {
+ FLUSH_REASON_FULL,
+ FLUSH_REASON_VIEW_ROOT_ENTERED,
+ FLUSH_REASON_SESSION_STARTED,
+ FLUSH_REASON_SESSION_FINISHED,
+ FLUSH_REASON_IDLE_TIMEOUT,
+ FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
+ FLUSH_REASON_SESSION_CONNECTED,
+ FLUSH_REASON_FORCE_FLUSH,
+ FLUSH_REASON_VIEW_TREE_APPEARING,
+ FLUSH_REASON_VIEW_TREE_APPEARED,
+ FLUSH_REASON_LOGIN_DETECTED
+ })
@Retention(RetentionPolicy.SOURCE)
- public @interface FlushReason{}
+ public @interface FlushReason {}
private final Object mLock = new Object();
@@ -685,8 +690,10 @@ public abstract class ContentCaptureSession implements AutoCloseable {
return "VIEW_TREE_APPEARING";
case FLUSH_REASON_VIEW_TREE_APPEARED:
return "VIEW_TREE_APPEARED";
+ case FLUSH_REASON_LOGIN_DETECTED:
+ return "LOGIN_DETECTED";
default:
- return "UNKOWN-" + reason;
+ return "UNKNOWN-" + reason;
}
}
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 044a31f3b297..f218995e55ad 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -480,6 +480,11 @@ public final class ViewNode extends AssistStructure.ViewNode {
return mLocaleList;
}
+ /** @hide */
+ public void setTextIdEntry(@NonNull String textIdEntry) {
+ mTextIdEntry = textIdEntry;
+ }
+
private void writeSelfToParcel(@NonNull Parcel parcel, int parcelFlags) {
long nodeFlags = mFlags;
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2dbff581fe84..46f2931b55cd 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -6454,7 +6454,7 @@ public class Editor {
@VisibleForTesting
public int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) {
final int trueLine = mTextView.getLineAtCoordinate(y);
- if (layout == null || prevLine > layout.getLineCount()
+ if (layout == null || prevLine >= layout.getLineCount()
|| layout.getLineCount() <= 0 || prevLine < 0) {
// Invalid parameters, just return whatever line is at y.
return trueLine;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 52554ee387df..4e20220ec98c 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -2598,6 +2598,11 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return VIEW_GROUP_ACTION_ADD_TAG;
}
+
+ @Override
+ public final void visitUris(@NonNull Consumer<Uri> visitor) {
+ mNestedViews.visitUris(visitor);
+ }
}
/**
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index c0370cc5517d..8c05130bf5fe 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -493,6 +493,9 @@ public final class TransitionInfo implements Parcelable {
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
}
+ if ((flags & FLAG_MOVED_TO_TOP) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("MOVE_TO_TOP");
+ }
return sb.toString();
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 7ad2a6898fb7..8135f9cd2f46 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -549,11 +549,6 @@ public final class SystemUiDeviceConfigFlags {
"task_manager_inform_job_scheduler_of_pending_app_stop";
/**
- * (boolean) Whether to show notification volume control slider separate from ring.
- */
- public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
-
- /**
* (boolean) Whether widget provider info would be saved to / loaded from system persistence
* layer as opposed to individual manifests in respective apps.
*/
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index e530aec2119a..869b69611eba 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -28,6 +28,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
@@ -258,8 +259,16 @@ public class InteractionJankMonitor {
public static final int CUJ_IME_INSETS_ANIMATION = 69;
public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
+ // 72 - 77 are reserved for b/281564325.
- private static final int LAST_CUJ = CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+ /**
+ * In some cases when we do not have any end-target, we play a simple slide-down animation.
+ * eg: Open an app from Overview/Task switcher such that there is no home-screen icon.
+ * eg: Exit the app using back gesture.
+ */
+ public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
+
+ private static final int LAST_CUJ = CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
private static final int NO_STATSD_LOGGING = -1;
// Used to convert CujType to InteractionType enum value for statsd logging.
@@ -340,6 +349,14 @@ public class InteractionJankMonitor {
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
+ // 72 - 77 are reserved for b/281564325.
+ CUJ_TO_STATSD_INTERACTION_TYPE[72] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[73] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[74] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[75] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[76] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
}
private static class InstanceHolder {
@@ -439,6 +456,7 @@ public class InteractionJankMonitor {
CUJ_IME_INSETS_ANIMATION,
CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
+ CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -1050,6 +1068,8 @@ public class InteractionJankMonitor {
return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
return "LAUNCHER_OPEN_SEARCH_RESULT";
+ case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK:
+ return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index ae58626e49eb..d2564fb9c268 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -157,7 +157,7 @@ oneway interface IStatusBar
*/
void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver,
in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId,
- long operationId, String opPackageName, long requestId, int multiSensorConfig);
+ long operationId, String opPackageName, long requestId);
/**
* Used to notify the authentication dialog that a biometric has been authenticated.
*/
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 370885936211..3977666627b7 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -123,8 +123,7 @@ interface IStatusBarService
// Used to show the authentication dialog (Biometrics, Device Credential)
void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver,
in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, long operationId, String opPackageName, long requestId,
- int multiSensorConfig);
+ int userId, long operationId, String opPackageName, long requestId);
// Used to notify the authentication dialog that a biometric has been authenticated
void onBiometricAuthenticated(int modality);
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index f2776353fd1b..116c301c28f0 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -367,28 +367,42 @@ public class LatencyTracker {
* using a single static object.
*/
@VisibleForTesting
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public void startListeningForLatencyTrackerConfigChanges() {
final Context context = ActivityThread.currentApplication();
- if (context != null
- && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
- // Post initialization to the background in case we're running on the main thread.
- BackgroundThread.getHandler().post(() -> this.updateProperties(
- DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER)));
- DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
- BackgroundThread.getExecutor(), mOnPropertiesChangedListener);
- } else {
+ if (context == null) {
if (DEBUG) {
- if (context == null) {
- Log.d(TAG, "No application for " + ActivityThread.currentActivityThread());
- } else {
- synchronized (mLock) {
- Log.d(TAG, "Initialized the LatencyTracker."
- + " (No READ_DEVICE_CONFIG permission to change configs)"
- + " enabled=" + mEnabled + ", package=" + context.getPackageName());
- }
- }
+ Log.d(TAG, "No application for package: " + ActivityThread.currentPackageName());
}
+ return;
}
+ if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
+ if (DEBUG) {
+ synchronized (mLock) {
+ Log.d(TAG, "Initialized the LatencyTracker."
+ + " (No READ_DEVICE_CONFIG permission to change configs)"
+ + " enabled=" + mEnabled + ", package=" + context.getPackageName());
+ }
+ }
+ return;
+ }
+
+ // Post initialization to the background in case we're running on the main thread.
+ BackgroundThread.getHandler().post(() -> {
+ try {
+ this.updateProperties(
+ DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER));
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
+ BackgroundThread.getExecutor(), mOnPropertiesChangedListener);
+ } catch (SecurityException ex) {
+ // In case of running tests that the main thread passes the check,
+ // but the background thread doesn't have necessary permissions.
+ // Swallow it since it's ok to ignore device config changes in the tests.
+ Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
+ + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+ + ", package=" + context.getPackageName());
+ }
+ });
}
/**
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index f55d15de1d51..fb4b026f0b61 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -163,6 +163,8 @@ public class PointerLocationView extends View implements InputDeviceListener,
@UnsupportedAppUsage
private boolean mPrintCoords = true;
+ private float mDensity;
+
public PointerLocationView(Context c) {
super(c);
setFocusableInTouchMode(true);
@@ -357,19 +359,20 @@ public class PointerLocationView extends View implements InputDeviceListener,
// Draw current touch ellipse.
mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128);
- drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor,
- ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint);
+ drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor * mDensity,
+ ps.mCoords.touchMinor * mDensity, ps.mCoords.orientation, mPaint);
// Draw current tool ellipse.
mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel);
- drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
- ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
+ drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor * mDensity,
+ ps.mCoords.toolMinor * mDensity, ps.mCoords.orientation, mPaint);
// Draw the orientation arrow.
float arrowSize = ps.mCoords.toolMajor * 0.7f;
if (arrowSize < 20) {
arrowSize = 20;
}
+ arrowSize *= mDensity;
mPaint.setARGB(255, pressureLevel, 255, 0);
float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation)
* arrowSize);
@@ -398,7 +401,7 @@ public class PointerLocationView extends View implements InputDeviceListener,
canvas.drawCircle(
ps.mCoords.x + orientationVectorX * tiltScale,
ps.mCoords.y + orientationVectorY * tiltScale,
- 3.0f, mPaint);
+ 3.0f * mDensity, mPaint);
// Draw the current bounding box
if (ps.mHasBoundingBox) {
@@ -1003,10 +1006,10 @@ public class PointerLocationView extends View implements InputDeviceListener,
// Compute size by display density.
private void configureDensityDependentFactors() {
- final float density = getResources().getDisplayMetrics().density;
- mTextPaint.setTextSize(10 * density);
- mPaint.setStrokeWidth(1 * density);
- mCurrentPointPaint.setStrokeWidth(1 * density);
- mPathPaint.setStrokeWidth(1 * density);
+ mDensity = getResources().getDisplayMetrics().density;
+ mTextPaint.setTextSize(10 * mDensity);
+ mPaint.setStrokeWidth(1 * mDensity);
+ mCurrentPointPaint.setStrokeWidth(1 * mDensity);
+ mPathPaint.setStrokeWidth(1 * mDensity);
}
}
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index 84c82e094dd4..3cedba0632aa 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -434,9 +434,8 @@ message SystemPropertiesProto {
optional string vibrator = 37;
optional string virtual_device = 38;
optional string vulkan = 39;
- optional string egl_legacy = 40;
- // Next Tag: 41
+ // Next Tag: 40
}
optional Hardware hardware = 27;
@@ -559,3 +558,4 @@ message SystemPropertiesProto {
// Next Tag: 32
}
+
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 128de8bf47df..052e2f20a313 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -471,10 +471,6 @@ message GlobalSettingsProto {
optional SettingProto updatable_driver_prerelease_opt_in_apps = 18;
optional SettingProto angle_egl_features = 19;
- // ANGLE - List of Apps that ANGLE may have issues with
- optional SettingProto angle_deferlist = 20;
- // ANGLE - Integer mode of the logic for applying `angle_deferlist`
- optional SettingProto angle_deferlist_mode = 21;
}
optional Gpu gpu = 59;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c6fa7c5814da..68cfd19ddb5e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1854,6 +1854,11 @@
<item>telephony</item>
</string-array>
+ <!-- The difference in millis that has to exist between a time suggestion under
+ consideration by the time_detector and the system clock before the system clock will be
+ changed. -->
+ <integer name="config_timeDetectorAutoUpdateDiffMillis">2000</integer>
+
<!-- Enables the GnssTimeUpdate service. This is the global switch for enabling Gnss time based
suggestions to TimeDetector service. See also config_autoTimeSourcesPriority. -->
<bool name="config_enableGnssTimeUpdateService">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2451bd16bb4f..38cad788dde5 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2241,6 +2241,7 @@
<java-symbol type="string" name="config_persistentDataPackageName" />
<java-symbol type="string" name="config_deviceConfiguratorPackageName" />
<java-symbol type="array" name="config_autoTimeSourcesPriority" />
+ <java-symbol type="integer" name="config_timeDetectorAutoUpdateDiffMillis" />
<java-symbol type="bool" name="config_enableGnssTimeUpdateService" />
<java-symbol type="bool" name="config_enableGeolocationTimeZoneDetection" />
<java-symbol type="bool" name="config_enablePrimaryLocationTimeZoneProvider" />
@@ -5136,6 +5137,6 @@
<java-symbol type="style" name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" />
<java-symbol type="drawable" name="focus_event_pressed_key_background" />
-
<java-symbol type="string" name="config_defaultShutdownVibrationFile" />
+ <java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" />
</resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 1b570dad1904..1516a07f875b 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -33,6 +33,8 @@ import static android.app.Notification.EXTRA_MESSAGING_PERSON;
import static android.app.Notification.EXTRA_PEOPLE_LIST;
import static android.app.Notification.EXTRA_PICTURE;
import static android.app.Notification.EXTRA_PICTURE_ICON;
+import static android.app.Notification.EXTRA_SUMMARY_TEXT;
+import static android.app.Notification.EXTRA_TITLE;
import static android.app.Notification.MessagingStyle.Message.KEY_DATA_URI;
import static android.app.Notification.MessagingStyle.Message.KEY_SENDER_PERSON;
import static android.app.Notification.MessagingStyle.Message.KEY_TEXT;
@@ -76,6 +78,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemProperties;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@@ -111,6 +114,9 @@ public class NotificationTest {
@Before
public void setUp() {
mContext = InstrumentationRegistry.getContext();
+ // TODO(b/169435530): remove this flag set once resolved.
+ SystemProperties.set("persist.sysui.notification.builder_extras_override",
+ Boolean.toString(false));
}
@Test
@@ -1502,6 +1508,107 @@ public class NotificationTest {
Assert.assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
}
+ // Ensures that extras in a Notification Builder can be updated.
+ @Test
+ public void testExtras_cachedExtrasOverwrittenByUserProvided() {
+ // Sets the flag to new state.
+ // TODO(b/169435530): remove this set value once resolved.
+ SystemProperties.set("persist.sysui.notification.builder_extras_override",
+ Boolean.toString(true));
+ Bundle extras = new Bundle();
+ extras.putCharSequence(EXTRA_TITLE, "test title");
+ extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text");
+
+ Notification.Builder builder = new Notification.Builder(mContext, "test id")
+ .addExtras(extras);
+
+ Notification notification = builder.build();
+ assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
+ "test title");
+ assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
+ "summary text");
+
+ extras.putCharSequence(EXTRA_TITLE, "new title");
+ builder.addExtras(extras);
+ notification = builder.build();
+ assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
+ "new title");
+ assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
+ "summary text");
+ }
+
+ // Ensures that extras in a Notification Builder can be updated by an extender.
+ @Test
+ public void testExtras_cachedExtrasOverwrittenByExtender() {
+ // Sets the flag to new state.
+ // TODO(b/169435530): remove this set value once resolved.
+ SystemProperties.set("persist.sysui.notification.builder_extras_override",
+ Boolean.toString(true));
+ Notification.CarExtender extender = new Notification.CarExtender().setColor(1234);
+
+ Notification notification = new Notification.Builder(mContext, "test id")
+ .extend(extender).build();
+
+ extender.setColor(5678);
+
+ Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build();
+
+ Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification);
+ assertThat(recoveredExtender.getColor()).isEqualTo(5678);
+ }
+
+ // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated.
+ // TODO(b/169435530): remove this test once resolved.
+ @Test
+ public void testExtras_cachedExtrasOverwrittenByUserProvidedOld() {
+ // Sets the flag to old state.
+ SystemProperties.set("persist.sysui.notification.builder_extras_override",
+ Boolean.toString(false));
+
+ Bundle extras = new Bundle();
+ extras.putCharSequence(EXTRA_TITLE, "test title");
+ extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text");
+
+ Notification.Builder builder = new Notification.Builder(mContext, "test id")
+ .addExtras(extras);
+
+ Notification notification = builder.build();
+ assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
+ "test title");
+ assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
+ "summary text");
+
+ extras.putCharSequence(EXTRA_TITLE, "new title");
+ builder.addExtras(extras);
+ notification = builder.build();
+ assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
+ "test title");
+ assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
+ "summary text");
+ }
+
+ // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated
+ // by an extender.
+ // TODO(b/169435530): remove this test once resolved.
+ @Test
+ public void testExtras_cachedExtrasOverwrittenByExtenderOld() {
+ // Sets the flag to old state.
+ SystemProperties.set("persist.sysui.notification.builder_extras_override",
+ Boolean.toString(false));
+
+ Notification.CarExtender extender = new Notification.CarExtender().setColor(1234);
+
+ Notification notification = new Notification.Builder(mContext, "test id")
+ .extend(extender).build();
+
+ extender.setColor(5678);
+
+ Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build();
+
+ Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification);
+ assertThat(recoveredExtender.getColor()).isEqualTo(1234);
+ }
+
private void assertValid(Notification.Colors c) {
// Assert that all colors are populated
assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 6e8e93a8c86b..33ee72d18c3a 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -137,7 +137,7 @@ public class FontScaleConverterActivityTest {
);
});
- PollingCheck.waitFor(/* timeout= */ 5000, () -> {
+ PollingCheck.waitFor(/* timeout= */ 7000, () -> {
AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false);
rule.getScenario().onActivity(activity ->
isActivityAtCorrectScale.set(
@@ -146,12 +146,7 @@ public class FontScaleConverterActivityTest {
.fontScale == fontScale
)
);
- return isActivityAtCorrectScale.get() && InstrumentationRegistry
- .getInstrumentation()
- .getContext()
- .getResources()
- .getConfiguration()
- .fontScale == fontScale;
+ return isActivityAtCorrectScale.get();
});
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 27d58b8cdcb7..f8ebd09899a8 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -27,6 +27,8 @@ import android.view.ViewStructure;
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import com.google.common.collect.ImmutableMap;
+
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -37,6 +39,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.Map;
+
/**
* Unit tests for {@link ContentCaptureSession}.
*
@@ -145,6 +149,35 @@ public class ContentCaptureSessionTest {
assertThat(session.mInternalNotifyViewTreeEventFinishedCount).isEqualTo(1);
}
+ @Test
+ public void testGetFlushReasonAsString() {
+ int invalidFlushReason = ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED + 1;
+ Map<Integer, String> expectedMap =
+ new ImmutableMap.Builder<Integer, String>()
+ .put(ContentCaptureSession.FLUSH_REASON_FULL, "FULL")
+ .put(ContentCaptureSession.FLUSH_REASON_VIEW_ROOT_ENTERED, "VIEW_ROOT")
+ .put(ContentCaptureSession.FLUSH_REASON_SESSION_STARTED, "STARTED")
+ .put(ContentCaptureSession.FLUSH_REASON_SESSION_FINISHED, "FINISHED")
+ .put(ContentCaptureSession.FLUSH_REASON_IDLE_TIMEOUT, "IDLE")
+ .put(ContentCaptureSession.FLUSH_REASON_TEXT_CHANGE_TIMEOUT, "TEXT_CHANGE")
+ .put(ContentCaptureSession.FLUSH_REASON_SESSION_CONNECTED, "CONNECTED")
+ .put(ContentCaptureSession.FLUSH_REASON_FORCE_FLUSH, "FORCE_FLUSH")
+ .put(
+ ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARING,
+ "VIEW_TREE_APPEARING")
+ .put(
+ ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED,
+ "VIEW_TREE_APPEARED")
+ .put(ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED, "LOGIN_DETECTED")
+ .put(invalidFlushReason, "UNKOWN-" + invalidFlushReason)
+ .build();
+
+ expectedMap.forEach(
+ (reason, expected) ->
+ assertThat(ContentCaptureSession.getFlushReasonAsString(reason))
+ .isEqualTo(expected));
+ }
+
// Cannot use @Spy because we need to pass the session id on constructor
private class MyContentCaptureSession extends ContentCaptureSession {
int mInternalNotifyViewTreeEventStartedCount = 0;
diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
index 93315f11d242..a4e77f5d8dc5 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
@@ -41,49 +41,60 @@ public class ViewNodeTest {
private final Context mContext = InstrumentationRegistry.getTargetContext();
+ private final View mView = new View(mContext);
+
+ private final ViewStructureImpl mViewStructure = new ViewStructureImpl(mView);
+
+ private final ViewNode mViewNode = mViewStructure.getNode();
+
@Mock
private HtmlInfo mHtmlInfoMock;
@Test
public void testUnsupportedProperties() {
- View view = new View(mContext);
+ mViewStructure.setChildCount(1);
+ assertThat(mViewNode.getChildCount()).isEqualTo(0);
+
+ mViewStructure.addChildCount(1);
+ assertThat(mViewNode.getChildCount()).isEqualTo(0);
- ViewStructureImpl structure = new ViewStructureImpl(view);
- ViewNode node = structure.getNode();
+ assertThat(mViewStructure.newChild(0)).isNull();
+ assertThat(mViewNode.getChildCount()).isEqualTo(0);
- structure.setChildCount(1);
- assertThat(node.getChildCount()).isEqualTo(0);
+ assertThat(mViewStructure.asyncNewChild(0)).isNull();
+ assertThat(mViewNode.getChildCount()).isEqualTo(0);
- structure.addChildCount(1);
- assertThat(node.getChildCount()).isEqualTo(0);
+ mViewStructure.asyncCommit();
+ assertThat(mViewNode.getChildCount()).isEqualTo(0);
- assertThat(structure.newChild(0)).isNull();
- assertThat(node.getChildCount()).isEqualTo(0);
+ mViewStructure.setWebDomain("Y U NO SET?");
+ assertThat(mViewNode.getWebDomain()).isNull();
- assertThat(structure.asyncNewChild(0)).isNull();
- assertThat(node.getChildCount()).isEqualTo(0);
+ assertThat(mViewStructure.newHtmlInfoBuilder("WHATEVER")).isNull();
- structure.asyncCommit();
- assertThat(node.getChildCount()).isEqualTo(0);
+ mViewStructure.setHtmlInfo(mHtmlInfoMock);
+ assertThat(mViewNode.getHtmlInfo()).isNull();
- structure.setWebDomain("Y U NO SET?");
- assertThat(node.getWebDomain()).isNull();
+ mViewStructure.setDataIsSensitive(true);
- assertThat(structure.newHtmlInfoBuilder("WHATEVER")).isNull();
+ assertThat(mViewStructure.getTempRect()).isNull();
- structure.setHtmlInfo(mHtmlInfoMock);
- assertThat(node.getHtmlInfo()).isNull();
+ // Graphic properties
+ mViewStructure.setElevation(6.66f);
+ assertThat(mViewNode.getElevation()).isEqualTo(0f);
+ mViewStructure.setAlpha(66.6f);
+ assertThat(mViewNode.getAlpha()).isEqualTo(1.0f);
+ mViewStructure.setTransformation(Matrix.IDENTITY_MATRIX);
+ assertThat(mViewNode.getTransformation()).isNull();
+ }
- structure.setDataIsSensitive(true);
+ @Test
+ public void testGetSet_textIdEntry() {
+ assertThat(mViewNode.getTextIdEntry()).isNull();
- assertThat(structure.getTempRect()).isNull();
+ String expected = "TEXT_ID_ENTRY";
+ mViewNode.setTextIdEntry(expected);
- // Graphic properties
- structure.setElevation(6.66f);
- assertThat(node.getElevation()).isWithin(1.0e-10f).of(0f);
- structure.setAlpha(66.6f);
- assertThat(node.getAlpha()).isWithin(1.0e-10f).of(1.0f);
- structure.setTransformation(Matrix.IDENTITY_MATRIX);
- assertThat(node.getTransformation()).isNull();
+ assertThat(mViewNode.getTextIdEntry()).isEqualTo(expected);
}
}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 33c44eaaa451..945fb6da5b65 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -719,6 +719,30 @@ public class RemoteViewsTest {
}
@Test
+ public void visitUris_nestedViews() {
+ final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test);
+
+ final RemoteViews inner = new RemoteViews(mPackage, 33);
+ final Uri imageUriI = Uri.parse("content://inner/image");
+ final Icon icon1 = Icon.createWithContentUri("content://inner/icon1");
+ final Icon icon2 = Icon.createWithContentUri("content://inner/icon2");
+ final Icon icon3 = Icon.createWithContentUri("content://inner/icon3");
+ final Icon icon4 = Icon.createWithContentUri("content://inner/icon4");
+ inner.setImageViewUri(R.id.image, imageUriI);
+ inner.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4);
+
+ outer.addView(R.id.layout, inner);
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ outer.visitUris(visitor);
+ verify(visitor, times(1)).accept(eq(imageUriI));
+ verify(visitor, times(1)).accept(eq(icon1.getUri()));
+ verify(visitor, times(1)).accept(eq(icon2.getUri()));
+ verify(visitor, times(1)).accept(eq(icon3.getUri()));
+ verify(visitor, times(1)).accept(eq(icon4.getUri()));
+ }
+
+ @Test
public void visitUris_separateOrientation() {
final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test);
final Uri imageUriL = Uri.parse("content://landscape/image");
diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl
index 54f8808e2285..5d7fd85fc178 100644
--- a/data/keyboards/Vendor_0957_Product_0001.kl
+++ b/data/keyboards/Vendor_0957_Product_0001.kl
@@ -45,6 +45,7 @@ key 11 0
# custom keys
key usage 0x000c01BB TV_INPUT
+key usage 0x000c0186 MACRO_1
key usage 0x000c0185 TV_TELETEXT
key usage 0x000c0061 CAPTIONS
@@ -77,4 +78,4 @@ key usage 0x000c009D CHANNEL_DOWN
key usage 0x000c0077 BUTTON_3 WAKE #YouTube
key usage 0x000c0078 BUTTON_4 WAKE #Netflix
key usage 0x000c0079 BUTTON_6 WAKE
-key usage 0x000c007A BUTTON_7 WAKE \ No newline at end of file
+key usage 0x000c007A BUTTON_7 WAKE
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 89f4890c254e..3b45d5d88d12 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1418,7 +1418,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
removeExistingSecondaryContainers(wct, primaryContainer);
}
- primaryContainer.getTaskContainer().mSplitContainers.add(splitContainer);
+ primaryContainer.getTaskContainer().addSplitContainer(splitContainer);
}
/** Cleanups all the dependencies when the TaskFragment is entering PIP. */
@@ -1430,8 +1430,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return;
}
final List<SplitContainer> splitsToRemove = new ArrayList<>();
+ final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>();
- for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
+ for (SplitContainer splitContainer : splitContainers) {
if (splitContainer.getPrimaryContainer() != container
&& splitContainer.getSecondaryContainer() != container) {
continue;
@@ -1449,7 +1450,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
container.resetDependencies();
- taskContainer.mSplitContainers.removeAll(splitsToRemove);
+ taskContainer.removeSplitContainers(splitsToRemove);
// If there is any TaskFragment split with the PIP TaskFragment, update their presentations
// since the split is dismissed.
// We don't want to close any of them even if they are dependencies of the PIP TaskFragment.
@@ -1481,7 +1482,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Cleanup any split references.
final List<SplitContainer> containersToRemove = new ArrayList<>();
- for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
+ final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
+ for (SplitContainer splitContainer : splitContainers) {
if (containersToRemove.contains(splitContainer)) {
// Don't need to check because it has been in the remove list.
continue;
@@ -1492,7 +1494,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
containersToRemove.add(splitContainer);
}
}
- taskContainer.mSplitContainers.removeAll(containersToRemove);
+ taskContainer.removeSplitContainers(containersToRemove);
// Cleanup any dependent references.
for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
@@ -1629,7 +1631,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** Whether the given split is the topmost split in the Task. */
private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) {
final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer()
- .getTaskContainer().mSplitContainers;
+ .getTaskContainer().getSplitContainers();
return splitContainer == splitContainers.get(splitContainers.size() - 1);
}
@@ -1641,7 +1643,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (container == null) {
return null;
}
- final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
+ final List<SplitContainer> splitContainers =
+ container.getTaskContainer().getSplitContainers();
if (splitContainers.isEmpty()) {
return null;
}
@@ -1665,7 +1668,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@NonNull TaskFragmentContainer firstContainer,
@NonNull TaskFragmentContainer secondContainer) {
final List<SplitContainer> splitContainers = firstContainer.getTaskContainer()
- .mSplitContainers;
+ .getSplitContainers();
for (int i = splitContainers.size() - 1; i >= 0; i--) {
final SplitContainer splitContainer = splitContainers.get(i);
final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
@@ -1945,7 +1948,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
SplitContainer getSplitContainer(@NonNull IBinder token) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<SplitContainer> containers = mTaskContainers.valueAt(i).mSplitContainers;
+ final List<SplitContainer> containers = mTaskContainers.valueAt(i).getSplitContainers();
for (SplitContainer container : containers) {
if (container.getToken().equals(token)) {
return container;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 4b15bb187035..06265105028b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -55,7 +55,7 @@ class TaskContainer {
/** Active split pairs in this Task. */
@NonNull
- final List<SplitContainer> mSplitContainers = new ArrayList<>();
+ private final List<SplitContainer> mSplitContainers = new ArrayList<>();
@NonNull
private final Configuration mConfiguration;
@@ -207,6 +207,19 @@ class TaskContainer {
return false;
}
+ @NonNull
+ List<SplitContainer> getSplitContainers() {
+ return new ArrayList<>(mSplitContainers);
+ }
+
+ void addSplitContainer(@NonNull SplitContainer splitContainer) {
+ mSplitContainers.add(splitContainer);
+ }
+
+ void removeSplitContainers(@NonNull List<SplitContainer> containers) {
+ mSplitContainers.removeAll(containers);
+ }
+
/** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
for (SplitContainer container : mSplitContainers) {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index ff08782e8cd8..82692a5170df 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -320,11 +320,11 @@ public class SplitControllerTest {
doReturn(tf).when(splitContainer).getSecondaryContainer();
doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
- final List<SplitContainer> splitContainers =
- mSplitController.getTaskContainer(TASK_ID).mSplitContainers;
- splitContainers.add(splitContainer);
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ taskContainer.addSplitContainer(splitContainer);
// Add a mock SplitContainer on top of splitContainer
- splitContainers.add(1, mock(SplitContainer.class));
+ final SplitContainer splitContainer2 = mock(SplitContainer.class);
+ taskContainer.addSplitContainer(splitContainer2);
mSplitController.updateContainer(mTransaction, tf);
@@ -332,7 +332,9 @@ public class SplitControllerTest {
// Verify if one or both containers in the top SplitContainer are finished,
// dismissPlaceholder() won't be called.
- splitContainers.remove(1);
+ final ArrayList<SplitContainer> splitContainersToRemove = new ArrayList<>();
+ splitContainersToRemove.add(splitContainer2);
+ taskContainer.removeSplitContainers(splitContainersToRemove);
doReturn(true).when(tf).isFinished();
mSplitController.updateContainer(mTransaction, tf);
@@ -377,7 +379,7 @@ public class SplitControllerTest {
doReturn(true).when(taskContainer).isVisible();
mSplitController.updateContainer(mTransaction, taskFragmentContainer);
- verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0),
+ verify(mSplitPresenter).updateSplitContainer(taskContainer.getSplitContainers().get(0),
mTransaction);
}
@@ -1091,7 +1093,7 @@ public class SplitControllerTest {
verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken());
verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken());
assertTrue(taskContainer.mContainers.isEmpty());
- assertTrue(taskContainer.mSplitContainers.isEmpty());
+ assertTrue(taskContainer.getSplitContainers().isEmpty());
}
@Test
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 171a6b2fe5fb..6f8a7666e5a8 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -30,6 +30,8 @@
<color name="bubbles_light">#FFFFFF</color>
<color name="bubbles_dark">@color/GM2_grey_800</color>
<color name="bubbles_icon_tint">@color/GM2_grey_700</color>
+ <color name="bubble_bar_expanded_view_handle_light">#EBffffff</color>
+ <color name="bubble_bar_expanded_view_handle_dark">#99000000</color>
<!-- PiP -->
<color name="pip_custom_close_bg">#D93025</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 2be34c90a661..aa05179ed113 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -229,7 +229,11 @@
<!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. -->
<dimen name="bubblebar_size">72dp</dimen>
<!-- The size of the drag handle / menu shown along with a bubble bar expanded view. -->
- <dimen name="bubblebar_expanded_view_menu_size">16dp</dimen>
+ <dimen name="bubble_bar_expanded_view_handle_size">40dp</dimen>
+ <!-- The width of the drag handle shown along with a bubble bar expanded view. -->
+ <dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen>
+ <!-- The height of the drag handle shown along with a bubble bar expanded view. -->
+ <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
<!-- Bottom and end margin for compat buttons. -->
<dimen name="compat_button_margin">24dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index fbdbd3e61d92..7b37d5947f17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.activityembedding;
+import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -111,6 +112,11 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
return false;
}
+ final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
+ if (options != null && options.getType() == ANIM_SCENE_TRANSITION) {
+ // Scene-transition will be handled by app side.
+ return false;
+ }
// Start ActivityEmbedding animation.
mTransitionCallbacks.put(transition, finishCallback);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3eb9fa2eef6b..988ad8c6c0f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1846,7 +1846,7 @@ public class BubbleController implements ConfigurationChangeListener,
if (mStackView != null) {
mStackView.setVisibility(VISIBLE);
}
- if (mLayerView != null && isStackExpanded()) {
+ if (mLayerView != null) {
mLayerView.setVisibility(VISIBLE);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index a48be5ed5ad5..91c7cc0c6e89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1346,7 +1346,7 @@ public class BubbleStackView extends FrameLayout
// Recreates & shows the education views. Call when a theme/config change happens.
private void updateUserEdu() {
- if (isStackEduVisible()) {
+ if (isStackEduVisible() && !mStackEduView.isHiding()) {
removeView(mStackEduView);
mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
addView(mStackEduView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 627273f093f3..d0598cd28582 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -37,8 +37,7 @@ class StackEducationView constructor(
context: Context,
positioner: BubblePositioner,
controller: BubbleController
-)
- : LinearLayout(context) {
+) : LinearLayout(context) {
private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
else BubbleDebugConfig.TAG_BUBBLES
@@ -53,7 +52,8 @@ class StackEducationView constructor(
private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) }
- private var isHiding = false
+ var isHiding = false
+ private set
init {
LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index b8f049becb6f..da1a5572e53b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles.bar;
+import android.annotation.ColorInt;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.TypedArray;
@@ -46,10 +48,10 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
private BubbleController mController;
private BubbleTaskViewHelper mBubbleTaskViewHelper;
- private HandleView mMenuView;
- private TaskView mTaskView;
+ private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
+ private @Nullable TaskView mTaskView;
- private int mMenuHeight;
+ private int mHandleHeight;
private int mBackgroundColor;
private float mCornerRadius = 0f;
@@ -83,11 +85,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
super.onFinishInflate();
Context context = getContext();
setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
- mMenuHeight = context.getResources().getDimensionPixelSize(
- R.dimen.bubblebar_expanded_view_menu_size);
- mMenuView = new HandleView(context);
- addView(mMenuView);
-
+ mHandleHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_handle_size);
+ addView(mHandleView);
applyThemeAttrs();
setClipToOutline(true);
setOutlineProvider(new ViewOutlineProvider() {
@@ -114,7 +114,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
void applyThemeAttrs() {
boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
mContext.getResources());
- final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+ final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
android.R.attr.dialogCornerRadius,
android.R.attr.colorBackgroundFloating});
mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
@@ -123,14 +123,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
ta.recycle();
- mMenuView.setCornerRadius(mCornerRadius);
- mMenuHeight = getResources().getDimensionPixelSize(
- R.dimen.bubblebar_expanded_view_menu_size);
+ mHandleHeight = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_handle_size);
if (mTaskView != null) {
mTaskView.setCornerRadius(mCornerRadius);
- mTaskView.setElevation(150);
- updateMenuColor();
+ updateHandleAndBackgroundColor(true /* animated */);
}
}
@@ -138,10 +136,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
-
- // Add corner radius here so that the menu extends behind the rounded corners of TaskView.
- int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height);
- measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
+ int menuViewHeight = Math.min(mHandleHeight, height);
+ measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
MeasureSpec.getMode(heightMeasureSpec)));
if (mTaskView != null) {
@@ -153,12 +149,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
// Drag handle above
- final int dragHandleBottom = t + mMenuView.getMeasuredHeight();
- mMenuView.layout(l, t, r, dragHandleBottom);
+ final int dragHandleBottom = t + mHandleView.getMeasuredHeight();
+ mHandleView.layout(l, t, r, dragHandleBottom);
if (mTaskView != null) {
- // Subtract radius so that the menu extends behind the rounded corners of TaskView.
- mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r,
+ mTaskView.layout(l, dragHandleBottom, r,
dragHandleBottom + mTaskView.getMeasuredHeight());
}
}
@@ -166,7 +162,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
public void onTaskCreated() {
setContentVisibility(true);
- updateMenuColor();
+ updateHandleAndBackgroundColor(false /* animated */);
}
@Override
@@ -218,16 +214,33 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
}
}
- /** Updates the menu bar to be the status bar color specified by the app. */
- private void updateMenuColor() {
+ /**
+ * Updates the background color to match with task view status/bg color, and sets handle color
+ * to contrast with the background
+ */
+ private void updateHandleAndBackgroundColor(boolean animated) {
if (mTaskView == null) return;
- ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo();
- final int taskBgColor = info.taskDescription.getStatusBarColor();
- final int color = Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
- if (color != -1) {
- mMenuView.setBackgroundColor(color);
+ final int color = getTaskViewColor();
+ final boolean isRegionDark = Color.luminance(color) <= 0.5;
+ mHandleView.updateHandleColor(isRegionDark, animated);
+ setBackgroundColor(color);
+ }
+
+ /**
+ * Retrieves task view status/nav bar color or background if available
+ *
+ * TODO (b/283075226): Update with color sampling when
+ * RegionSamplingHelper or alternative is available
+ */
+ private @ColorInt int getTaskViewColor() {
+ if (mTaskView == null || mTaskView.getTaskInfo() == null) return mBackgroundColor;
+ ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription;
+ if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) {
+ return taskDescription.getStatusBarColor();
+ } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) {
+ return taskDescription.getBackgroundColor();
} else {
- mMenuView.setBackgroundColor(mBackgroundColor);
+ return mBackgroundColor;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
new file mode 100644
index 000000000000..e121aa45469d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import androidx.annotation.ColorInt;
+import androidx.core.content.ContextCompat;
+
+import com.android.wm.shell.R;
+
+/**
+ * Handle view to show at the top of a bubble bar expanded view.
+ */
+public class BubbleBarHandleView extends View {
+ private static final long COLOR_CHANGE_DURATION = 120;
+
+ private final int mHandleWidth;
+ private final int mHandleHeight;
+ private final @ColorInt int mHandleLightColor;
+ private final @ColorInt int mHandleDarkColor;
+ private @Nullable ObjectAnimator mColorChangeAnim;
+
+ public BubbleBarHandleView(Context context) {
+ this(context, null);
+ }
+
+ public BubbleBarHandleView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ Resources resources = context.getResources();
+ mHandleWidth = resources.getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_handle_width);
+ mHandleHeight = resources.getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_handle_height);
+ mHandleLightColor = ContextCompat.getColor(context,
+ R.color.bubble_bar_expanded_view_handle_light);
+ mHandleDarkColor = ContextCompat.getColor(context,
+ R.color.bubble_bar_expanded_view_handle_dark);
+
+ setClipToOutline(true);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ final int handleCenterX = view.getWidth() / 2;
+ final int handleCenterY = view.getHeight() / 2;
+ final float handleRadius = mHandleHeight / 2f;
+ Rect handleBounds = new Rect(
+ handleCenterX - mHandleWidth / 2,
+ handleCenterY - mHandleHeight / 2,
+ handleCenterX + mHandleWidth / 2,
+ handleCenterY + mHandleHeight / 2);
+ outline.setRoundRect(handleBounds, handleRadius);
+ }
+ });
+ }
+
+ /**
+ * Updates the handle color.
+ *
+ * @param isRegionDark Whether the background behind the handle is dark, and thus the handle
+ * should be light (and vice versa).
+ * @param animated Whether to animate the change, or apply it immediately.
+ */
+ public void updateHandleColor(boolean isRegionDark, boolean animated) {
+ int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor;
+ if (mColorChangeAnim != null) {
+ mColorChangeAnim.cancel();
+ }
+ if (animated) {
+ mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor);
+ mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mColorChangeAnim = null;
+ }
+ });
+ mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION);
+ mColorChangeAnim.start();
+ } else {
+ setBackgroundColor(newColor);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java
deleted file mode 100644
index 9ee8a9d98aa1..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.bubbles.bar;
-
-import android.content.Context;
-import android.view.Gravity;
-import android.widget.LinearLayout;
-
-/**
- * Handle / menu view to show at the top of a bubble bar expanded view.
- */
-public class HandleView extends LinearLayout {
-
- // TODO(b/273307221): implement the manage menu in this view.
- public HandleView(Context context) {
- super(context);
- setOrientation(LinearLayout.HORIZONTAL);
- setGravity(Gravity.CENTER);
- }
-
- /**
- * The menu extends past the top of the TaskView because of the rounded corners. This means
- * to center content in the menu we must subtract the radius (i.e. the amount of space covered
- * by TaskView).
- */
- public void setCornerRadius(float radius) {
- setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index ac6e4c2a6521..53683c67d825 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -54,14 +54,6 @@ public class TabletopModeController implements
DevicePostureController.OnDevicePostureChangedListener,
DisplayController.OnDisplaysChangedListener {
/**
- * When {@code true}, floating windows like PiP would auto move to the position
- * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode.
- */
- private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
- SystemProperties.getBoolean(
- "persist.wm.debug.enable_move_floating_window_in_tabletop", true);
-
- /**
* Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
* {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise.
* See also {@link #getPreferredHalfInTabletopMode()}.
@@ -162,14 +154,6 @@ public class TabletopModeController implements
}
}
- /**
- * @return {@code true} if floating windows like PiP would auto move to the position
- * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode.
- */
- public boolean enableMoveFloatingWindowInTabletop() {
- return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP;
- }
-
/** @return Preferred half for floating windows like PiP when in tabletop mode. */
@PreferredTabletopHalf
public int getPreferredHalfInTabletopMode() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 69f0bad4fb45..7f362f3e2ab7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -222,7 +222,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = insetsState.sourceAt(i);
if (source.getType() == WindowInsets.Type.navigationBars()
- && source.insetsRoundedCornerFrame()) {
+ && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
mTempRect.inset(source.calculateVisibleInsets(mTempRect));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 753dfa7396f8..a9ccdf6a156f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -293,6 +293,9 @@ public class SplitDecorManager extends WindowlessWindowManager {
}
if (mResizingIconView == null) {
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.accept(false);
+ }
return;
}
@@ -311,6 +314,9 @@ public class SplitDecorManager extends WindowlessWindowManager {
releaseDecor(finishT);
finishT.apply();
finishT.close();
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.accept(true);
+ }
}
});
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index 65a12d629c5a..2590cab9ff2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -252,13 +252,16 @@ public class PipMediaController {
// It can be removed when min_sdk of the app is set to 31 or greater.
@SuppressLint("NewApi")
private List<RemoteAction> getMediaActions() {
- if (mMediaController == null || mMediaController.getPlaybackState() == null) {
+ // Cache the PlaybackState since it's a Binder call.
+ final PlaybackState playbackState;
+ if (mMediaController == null
+ || (playbackState = mMediaController.getPlaybackState()) == null) {
return Collections.emptyList();
}
ArrayList<RemoteAction> mediaActions = new ArrayList<>();
- boolean isPlaying = mMediaController.getPlaybackState().isActive();
- long actions = mMediaController.getPlaybackState().getActions();
+ boolean isPlaying = playbackState.isActive();
+ long actions = playbackState.getActions();
// Prev action
mPrevAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0);
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 bfc1fb905ade..3c7ce3c32a88 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
@@ -249,11 +249,6 @@ public class PipTransition extends PipTransitionController {
finishTransaction);
}
- // Fade in the fadeout PIP when the fixed rotation is finished.
- if (mPipTransitionState.isInPip() && !mInFixedRotation && mHasFadeOut) {
- fadeExistingPip(true /* show */);
- }
-
return false;
}
@@ -1056,6 +1051,12 @@ public class PipTransition extends PipTransitionController {
.crop(finishTransaction, leash, destBounds)
.round(finishTransaction, leash, isInPip)
.shadow(finishTransaction, leash, isInPip);
+ // Make sure the PiP keeps invisible if it was faded out. If it needs to fade in, that will
+ // be handled by onFixedRotationFinished().
+ if (isInPip && mHasFadeOut) {
+ startTransaction.setAlpha(leash, 0f);
+ finishTransaction.setAlpha(leash, 0f);
+ }
}
/** Hides and shows the existing PIP during fixed rotation transition of other activities. */
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 ed8dc7ded654..fc674a8aa59b 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
@@ -65,9 +65,18 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac
}
Rect pipBounds = new Rect(startingBounds);
- // move PiP towards corner if user hasn't moved it manually or the flag is on
- if (mKeepClearAreaGravityEnabled
- || (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip())) {
+ boolean shouldApplyGravity = false;
+ // if PiP is outside of screen insets, reposition using gravity
+ if (!insets.contains(pipBounds)) {
+ shouldApplyGravity = true;
+ }
+ // if user has not interacted with PiP, reposition using gravity
+ if (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip()) {
+ shouldApplyGravity = true;
+ }
+
+ // apply gravity that will position PiP in bottom left or bottom right corner within insets
+ if (mKeepClearAreaGravityEnabled || shouldApplyGravity) {
float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
int verticalGravity = Gravity.BOTTOM;
int horizontalGravity;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 63181da46b02..6a861ce97431 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -677,7 +677,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
});
mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
- if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return;
final String tag = "tabletop-mode";
if (!isInTabletopMode) {
mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 5c9709c756f7..f35eda6caef0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -371,7 +371,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
* Find the background task that match the given component.
*/
@Nullable
- public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName) {
+ public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName,
+ int userId) {
if (componentName == null) {
return null;
}
@@ -383,7 +384,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
if (task.isVisible) {
continue;
}
- if (componentName.equals(task.baseIntent.getComponent())) {
+ if (componentName.equals(task.baseIntent.getComponent()) && userId == task.userId) {
return task;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 34701f1db7b7..ea33a1f1b56d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -723,7 +723,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
// in the background with priority.
final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
.map(recentTasks -> recentTasks.findTaskInBackground(
- intent.getIntent().getComponent()))
+ intent.getIntent().getComponent(), userId1))
.orElse(null);
if (taskInfo != null) {
startTask(taskInfo.taskId, position, options);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 14ea86a8c0e9..d2b0e2800dc9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -87,6 +87,14 @@ class SplitScreenTransitions {
mStageCoordinator = stageCoordinator;
}
+ private void initTransition(@NonNull IBinder transition,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ mAnimatingTransition = transition;
+ mFinishTransaction = finishTransaction;
+ mFinishCallback = finishCallback;
+ }
+
/** Play animation for enter transition or dismiss transition. */
void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -94,9 +102,7 @@ class SplitScreenTransitions {
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
@NonNull WindowContainerToken topRoot) {
- mFinishCallback = finishCallback;
- mAnimatingTransition = transition;
- mFinishTransaction = finishTransaction;
+ initTransition(transition, finishTransaction, finishCallback);
final TransitSession pendingTransition = getPendingTransition(transition);
if (pendingTransition != null) {
@@ -220,6 +226,45 @@ class SplitScreenTransitions {
onFinish(null /* wct */, null /* wctCB */);
}
+ /** Play animation for drag divider dismiss transition. */
+ void playDragDismissAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor,
+ @NonNull WindowContainerToken topRoot) {
+ initTransition(transition, finishTransaction, finishCallback);
+
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final SurfaceControl leash = change.getLeash();
+
+ if (toTopRoot.equals(change.getContainer())) {
+ startTransaction.setAlpha(leash, 1.f);
+ startTransaction.show(leash);
+
+ ValueAnimator va = new ValueAnimator();
+ mAnimations.add(va);
+
+ toTopDecor.onResized(startTransaction, animated -> {
+ mAnimations.remove(va);
+ if (animated) {
+ mTransitions.getMainExecutor().execute(() -> {
+ onFinish(null /* wct */, null /* wctCB */);
+ });
+ }
+ });
+ } else if (topRoot.equals(change.getContainer())) {
+ // Ensure it on top of all changes in transition.
+ startTransaction.setLayer(leash, Integer.MAX_VALUE);
+ startTransaction.setAlpha(leash, 1.f);
+ startTransaction.show(leash);
+ }
+ }
+ startTransaction.apply();
+ onFinish(null /* wct */, null /* wctCB */);
+ }
+
/** Play animation for resize transition. */
void playResizeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -227,9 +272,7 @@ class SplitScreenTransitions {
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
@NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
- mFinishCallback = finishCallback;
- mAnimatingTransition = transition;
- mFinishTransaction = finishTransaction;
+ initTransition(transition, finishTransaction, finishCallback);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index bf20567834e0..e0ffffffa727 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1328,8 +1328,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mIsExiting = true;
childrenToTop.resetBounds(wct);
wct.reorder(childrenToTop.mRootTaskInfo.token, true);
- wct.setSmallestScreenWidthDp(childrenToTop.mRootTaskInfo.token,
- SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
}
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
false /* reparentLeafTaskIfRelaunch */);
@@ -1517,6 +1515,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
mSplitLayout.update(t);
+ mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash,
+ getMainStageBounds());
+ mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash,
+ getSideStageBounds());
setDividerVisibility(true, t);
// Ensure divider surface are re-parented back into the hierarchy at the end of the
// transition. See Transition#buildFinishTransaction for more detail.
@@ -1989,13 +1991,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final boolean mainStageToTop =
bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
: mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
+ final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage;
if (!ENABLE_SHELL_TRANSITIONS) {
- exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, reason);
+ exitSplitScreen(toTopStage, reason);
return;
}
final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ toTopStage.resetBounds(wct);
prepareExitSplitScreen(dismissTop, wct);
if (mRootTaskInfo != null) {
wct.setDoNotPip(mRootTaskInfo.token);
@@ -2531,8 +2535,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
shouldAnimate = startPendingEnterAnimation(
mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction);
} else if (mSplitTransitions.isPendingDismiss(transition)) {
+ final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss;
shouldAnimate = startPendingDismissAnimation(
- mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
+ dismiss, info, startTransaction, finishTransaction);
+ if (shouldAnimate && dismiss.mReason == EXIT_REASON_DRAG_DIVIDER) {
+ final StageTaskListener toTopStage =
+ dismiss.mDismissTop == STAGE_TYPE_MAIN ? mMainStage : mSideStage;
+ mSplitTransitions.playDragDismissAnimation(transition, info, startTransaction,
+ finishTransaction, finishCallback, toTopStage.mRootTaskInfo.token,
+ toTopStage.getSplitDecorManager(), mRootTaskInfo.token);
+ return true;
+ }
} else if (mSplitTransitions.isPendingResize(transition)) {
mSplitTransitions.playResizeAnimation(transition, info, startTransaction,
finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
@@ -2787,6 +2800,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions.mPendingDismiss = null;
return false;
}
+ dismissTransition.setFinishedCallback((callbackWct, callbackT) -> {
+ mMainStage.getSplitDecorManager().release(callbackT);
+ mSideStage.getSplitDecorManager().release(callbackT);
+ });
addDividerBarToTransition(info, false /* show */);
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index e2e9270cd6cc..da7d18641a97 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
@@ -201,7 +202,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
if (mRootTaskInfo.taskId == taskInfo.taskId) {
// Inflates split decor view only when the root task is visible.
- if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
+ if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) {
if (taskInfo.isVisible) {
mSplitDecorManager.inflate(mContext, mRootLeash,
taskInfo.configuration.windowConfiguration.getBounds());
@@ -385,6 +386,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
void resetBounds(WindowContainerTransaction wct) {
wct.setBounds(mRootTaskInfo.token, null);
wct.setAppBounds(mRootTaskInfo.token, null);
+ wct.setSmallestScreenWidthDp(mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
}
void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index f33b0778a1b2..3b306e793640 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -191,6 +191,12 @@ public class Transitions implements RemoteCallable<Transitions>,
*/
private static final int SYNC_ALLOWANCE_MS = 120;
+ /**
+ * Keyguard gets a more generous timeout to finish its animations, because we are always holding
+ * a sleep token during occlude/unocclude transitions and we want them to finish playing cleanly
+ */
+ private static final int SYNC_ALLOWANCE_KEYGUARD_MS = 2000;
+
/** For testing only. Disables the force-finish timeout on sync. */
private boolean mDisableForceSync = false;
@@ -492,6 +498,10 @@ public class Transitions implements RemoteCallable<Transitions>,
finishT.show(leash);
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
finishT.hide(leash);
+ } else if (isOpening && mode == TRANSIT_CHANGE) {
+ // Just in case there is a race with another animation (eg. recents finish()).
+ // Changes are visible->visible so it's a problem if it isn't visible.
+ t.show(leash);
}
}
}
@@ -669,7 +679,7 @@ public class Transitions implements RemoteCallable<Transitions>,
// Sleep starts a process of forcing all prior transitions to finish immediately
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Start finish-for-sync track %d", i);
- finishForSync(i, null /* forceFinish */);
+ finishForSync(active, i, null /* forceFinish */);
}
if (hadPreceding) {
return false;
@@ -1017,6 +1027,9 @@ public class Transitions implements RemoteCallable<Transitions>,
for (int i = 0; i < mPendingTransitions.size(); ++i) {
if (mPendingTransitions.get(i).mToken == token) return true;
}
+ for (int i = 0; i < mReadyDuringSync.size(); ++i) {
+ if (mReadyDuringSync.get(i).mToken == token) return true;
+ }
for (int t = 0; t < mTracks.size(); ++t) {
final Track tr = mTracks.get(t);
for (int i = 0; i < tr.mReadyTransitions.size(); ++i) {
@@ -1103,10 +1116,17 @@ public class Transitions implements RemoteCallable<Transitions>,
*
* This is then repeated until there are no more pending sleep transitions.
*
+ * @param reason The SLEEP transition that triggered this round of finishes. We will continue
+ * looping round finishing transitions as long as this is still waiting.
* @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
* signal to -- so it will be force-finished if it's still running.
*/
- private void finishForSync(int trackIdx, @Nullable ActiveTransition forceFinish) {
+ private void finishForSync(ActiveTransition reason,
+ int trackIdx, @Nullable ActiveTransition forceFinish) {
+ if (!isTransitionKnown(reason.mToken)) {
+ Log.d(TAG, "finishForSleep: already played sync transition " + reason);
+ return;
+ }
final Track track = mTracks.get(trackIdx);
if (forceFinish != null) {
final Track trk = mTracks.get(forceFinish.getTrack());
@@ -1150,8 +1170,11 @@ public class Transitions implements RemoteCallable<Transitions>,
if (track.mActiveTransition == playing) {
if (!mDisableForceSync) {
// Give it a short amount of time to process it before forcing.
- mMainExecutor.executeDelayed(() -> finishForSync(trackIdx, playing),
- SYNC_ALLOWANCE_MS);
+ final int tolerance = KeyguardTransitionHandler.handles(playing.mInfo)
+ ? SYNC_ALLOWANCE_KEYGUARD_MS
+ : SYNC_ALLOWANCE_MS;
+ mMainExecutor.executeDelayed(
+ () -> finishForSync(reason, trackIdx, playing), tolerance);
}
break;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index f81fc6fbea49..6bba0d1386fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -110,7 +110,7 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
for (int i = state.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = state.sourceAt(i);
if (source.getType() == WindowInsets.Type.navigationBars()
- && source.insetsRoundedCornerFrame()) {
+ && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
return source;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index a4cf149cc3b5..21994a997be5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -50,11 +50,11 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
+import dagger.Lazy;
+
import java.util.Optional;
import java.util.concurrent.Executor;
-import dagger.Lazy;
-
/**
* This helper class contains logic that calculates scaling and cropping parameters
* for the folding/unfolding animation. As an input it receives TaskInfo objects and
@@ -149,7 +149,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
for (int i = state.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = state.sourceAt(i);
if (source.getType() == WindowInsets.Type.navigationBars()
- && source.insetsRoundedCornerFrame()) {
+ && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
return source;
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index b6696c70dbb1..78a2a38ee3af 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -23,14 +23,33 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-android_test {
- name: "WMShellFlickerTests",
+filegroup {
+ name: "WMShellFlickerTestsBase-src",
+ srcs: ["src/com/android/wm/shell/flicker/*.kt"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsBubbles-src",
+ srcs: ["src/**/bubble/*.kt"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip-src",
+ srcs: ["src/**/pip/*.kt"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsSplitScreen-src",
srcs: [
- "src/**/*.java",
- "src/**/*.kt",
+ "src/**/splitscreen/*.kt",
+ "src/**/splitscreen/benchmark/*.kt",
],
- manifest: "AndroidManifest.xml",
- test_config: "AndroidTest.xml",
+}
+
+java_defaults {
+ name: "WMShellFlickerTestsDefault",
+ manifest: "manifests/AndroidManifest.xml",
+ test_config_template: "AndroidTestTemplate.xml",
platform_apis: true,
certificate: "platform",
optimize: {
@@ -40,16 +59,69 @@ android_test {
libs: ["android.test.runner"],
static_libs: [
"androidx.test.ext.junit",
+ "flickertestapplib",
"flickerlib",
- "flickerlib-apphelpers",
"flickerlib-helpers",
- "truth-prebuilt",
- "app-helpers-core",
+ "platform-test-annotations",
+ "wm-flicker-common-app-helpers",
+ "wm-flicker-common-assertions",
"launcher-helper-lib",
"launcher-aosp-tapl",
- "wm-flicker-common-assertions",
- "wm-flicker-common-app-helpers",
- "platform-test-annotations",
- "flickertestapplib",
+ ],
+ data: [
+ ":FlickerTestApp",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsOther",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestOther.xml"],
+ package_name: "com.android.wm.shell.flicker",
+ instrumentation_target_package: "com.android.wm.shell.flicker",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ exclude_srcs: [
+ ":WMShellFlickerTestsBubbles-src",
+ ":WMShellFlickerTestsPip-src",
+ ":WMShellFlickerTestsSplitScreen-src",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsBubbles",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestBubbles.xml"],
+ package_name: "com.android.wm.shell.flicker.bubbles",
+ instrumentation_target_package: "com.android.wm.shell.flicker.bubbles",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsBubbles-src",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestPip.xml"],
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsPip-src",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsSplitScreen",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
+ package_name: "com.android.wm.shell.flicker.splitscreen",
+ instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsSplitScreen-src",
],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index b5937ae80f0a..8818aa205266 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -1,8 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- * Copyright 2020 Google Inc. All Rights Reserved.
- -->
-<configuration description="Runs WindowManager Shell Flicker Tests">
+ ~ 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.
+ -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
<option name="test-tag" value="FlickerTests" />
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<!-- keeps the screen on during tests -->
@@ -36,11 +48,11 @@
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="WMShellFlickerTests.apk"/>
+ <option name="test-file-name" value="{MODULE}.apk"/>
<option name="test-file-name" value="FlickerTestApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.wm.shell.flicker"/>
+ <option name="package" value="{PACKAGE}"/>
<option name="shell-timeout" value="6600s" />
<option name="test-timeout" value="6000s" />
<option name="hidden-api-checks" value="false" />
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
index 4721741611cf..4721741611cf 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
new file mode 100644
index 000000000000..437871f1bb8f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.flicker.bubble">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.bubble"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
new file mode 100644
index 000000000000..cf642f63a41d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.flicker">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
new file mode 100644
index 000000000000..5a8155a66d30
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.flicker.pip">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.pip"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
new file mode 100644
index 000000000000..887d8db3042f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.flicker.splitscreen">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.splitscreen"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
new file mode 100644
index 000000000000..d38b848fbb4d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
@@ -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.wm.shell.bubbles.bar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.drawable.ColorDrawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.core.content.ContextCompat;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class BubbleBarHandleViewTest extends ShellTestCase {
+ private BubbleBarHandleView mHandleView;
+
+ @Before
+ public void setup() {
+ mHandleView = new BubbleBarHandleView(mContext);
+ }
+
+ @Test
+ public void testUpdateHandleColor_lightBg() {
+ mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */);
+
+ assertTrue(mHandleView.getClipToOutline());
+ assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
+ ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
+ assertEquals(bgDrawable.getColor(),
+ ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark));
+ }
+
+ @Test
+ public void testUpdateHandleColor_darkBg() {
+ mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */);
+
+ assertTrue(mHandleView.getClipToOutline());
+ assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
+ ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
+ assertEquals(bgDrawable.getColor(),
+ ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 9189d3dd0327..fb17d8799bda 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -223,7 +223,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
- doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
+ doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null);
@@ -247,7 +247,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
SPLIT_POSITION_BOTTOM_OR_RIGHT);
// Put the same component into a task in the background
doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
- .findTaskInBackground(any());
+ .findTaskInBackground(any(), anyInt());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index ae69b3ddd042..4e446c684d86 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -44,11 +44,15 @@ public class SplitTestUtils {
static SplitLayout createMockSplitLayout() {
final Rect dividerBounds = new Rect(48, 0, 52, 100);
+ final Rect bounds1 = new Rect(0, 0, 40, 100);
+ final Rect bounds2 = new Rect(60, 0, 100, 100);
final SurfaceControl leash = createMockSurface();
SplitLayout out = mock(SplitLayout.class);
doReturn(dividerBounds).when(out).getDividerBounds();
doReturn(dividerBounds).when(out).getRefDividerBounds();
doReturn(leash).when(out).getDividerLeash();
+ doReturn(bounds1).when(out).getBounds1();
+ doReturn(bounds2).when(out).getBounds2();
return out;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 80384531e9ae..60c0e5535568 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -42,6 +42,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -72,6 +73,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.transition.Transitions;
@@ -117,13 +119,13 @@ public class SplitTransitionTests extends ShellTestCase {
doReturn(mockExecutor).when(mTransitions).getAnimExecutor();
doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire();
mSplitLayout = SplitTestUtils.createMockSplitLayout();
- mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
+ mMainStage = spy(new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
- mIconProvider);
+ mIconProvider));
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
- mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
+ mSideStage = spy(new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
- mIconProvider);
+ mIconProvider));
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
@@ -137,6 +139,8 @@ public class SplitTransitionTests extends ShellTestCase {
.setParentTaskId(mMainStage.mRootTaskInfo.taskId).build();
mSideChild = new TestRunningTaskInfoBuilder()
.setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
+ doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager();
+ doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager();
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 6621ab8ce0d8..66b6c62f1dd6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -68,6 +68,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
@@ -145,6 +146,8 @@ public class StageCoordinatorTests extends ShellTestCase {
mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
+ doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager();
+ doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager();
}
@Test
diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp
index ca1312e75f4c..21f4ca79b68c 100644
--- a/libs/hwui/MemoryPolicy.cpp
+++ b/libs/hwui/MemoryPolicy.cpp
@@ -28,7 +28,10 @@ namespace android::uirenderer {
constexpr static MemoryPolicy sDefaultMemoryPolicy;
constexpr static MemoryPolicy sPersistentOrSystemPolicy{
.contextTimeout = 10_s,
+ .minimumResourceRetention = 1_s,
+ .maximumResourceRetention = 10_s,
.useAlternativeUiHidden = true,
+ .purgeScratchOnly = false,
};
constexpr static MemoryPolicy sLowRamPolicy{
.useAlternativeUiHidden = true,
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 347daf34f52a..e10dda990dec 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -53,6 +53,8 @@ struct MemoryPolicy {
// The minimum amount of time to hold onto items in the resource cache
// The actual time used will be the max of this & when frames were actually rendered
nsecs_t minimumResourceRetention = 10_s;
+ // The maximum amount of time to hold onto items in the resource cache
+ nsecs_t maximumResourceRetention = 100000_s;
// If false, use only TRIM_UI_HIDDEN to drive background cache limits;
// If true, use all signals (such as all contexts are stopped) to drive the limits
bool useAlternativeUiHidden = true;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index babce88b8e1e..8f81dbad2320 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -277,12 +277,13 @@ void CacheManager::onThreadIdle() {
const nsecs_t now = systemTime(CLOCK_MONOTONIC);
// Rate limiting
- if ((now - mLastDeferredCleanup) < 25_ms) {
+ if ((now - mLastDeferredCleanup) > 25_ms) {
mLastDeferredCleanup = now;
const nsecs_t frameCompleteNanos = mFrameCompletions[0];
const nsecs_t frameDiffNanos = now - frameCompleteNanos;
const nsecs_t cleanupMillis =
- ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention));
+ ns2ms(std::clamp(frameDiffNanos, mMemoryPolicy.minimumResourceRetention,
+ mMemoryPolicy.maximumResourceRetention));
mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
mMemoryPolicy.purgeScratchOnly);
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 2541a506760c..32680dae75b9 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -3181,7 +3181,10 @@ final public class MediaCodec {
mValid = false;
mNativeContext = 0;
}
- sPool.offer(this);
+
+ if (!mInternal) {
+ sPool.offer(this);
+ }
}
private native void native_recycle();
@@ -3245,6 +3248,7 @@ final public class MediaCodec {
mNativeContext = context;
mMappable = isMappable;
mValid = (context != 0);
+ mInternal = true;
}
private static final BlockingQueue<LinearBlock> sPool =
@@ -3255,6 +3259,7 @@ final public class MediaCodec {
private boolean mMappable = false;
private ByteBuffer mMapped = null;
private long mNativeContext = 0;
+ private boolean mInternal = false;
}
/**
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index f614b1720ee4..62af39ffcf98 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -88,6 +88,8 @@ public final class MediaRouter2 {
private final CopyOnWriteArrayList<RouteCallbackRecord> mRouteCallbackRecords =
new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<RouteListingPreferenceCallbackRecord>
+ mListingPreferenceCallbackRecords = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<TransferCallbackRecord> mTransferCallbackRecords =
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<ControllerCallbackRecord> mControllerCallbackRecords =
@@ -384,6 +386,43 @@ public final class MediaRouter2 {
}
/**
+ * Registers callback to be invoked when the {@link RouteListingPreference} of the target
+ * router changes.
+ *
+ * <p>Calls using a previously registered callback will overwrite the callback record.
+ *
+ * @see #setRouteListingPreference(RouteListingPreference)
+ * @hide
+ */
+ public void registerRouteListingPreferenceCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(routeListingPreferenceCallback, "callback must not be null");
+
+ RouteListingPreferenceCallbackRecord record =
+ new RouteListingPreferenceCallbackRecord(executor, routeListingPreferenceCallback);
+
+ mListingPreferenceCallbackRecords.remove(record);
+ mListingPreferenceCallbackRecords.add(record);
+ }
+
+ /**
+ * Unregisters the given callback to not receive {@link RouteListingPreference} change events.
+ *
+ * @hide
+ */
+ public void unregisterRouteListingPreferenceCallback(
+ @NonNull RouteListingPreferenceCallback callback) {
+ Objects.requireNonNull(callback, "callback must not be null");
+
+ if (!mListingPreferenceCallbackRecords.remove(
+ new RouteListingPreferenceCallbackRecord(/* executor */ null, callback))) {
+ Log.w(TAG, "unregisterRouteListingPreferenceCallback: Ignoring an unknown callback");
+ }
+ }
+
+ /**
* Shows the system output switcher dialog.
*
* <p>Should only be called when the context of MediaRouter2 is in the foreground and visible on
@@ -453,6 +492,20 @@ public final class MediaRouter2 {
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
+ notifyRouteListingPreferenceUpdated(routeListingPreference);
+ }
+ }
+
+ /**
+ * Returns the current {@link RouteListingPreference} of the target router.
+ *
+ * @see #setRouteListingPreference(RouteListingPreference)
+ * @hide
+ */
+ @Nullable
+ public RouteListingPreference getRouteListingPreference() {
+ synchronized (mLock) {
+ return mRouteListingPreference;
}
}
@@ -1078,6 +1131,15 @@ public final class MediaRouter2 {
}
}
+ private void notifyRouteListingPreferenceUpdated(@Nullable RouteListingPreference preference) {
+ for (RouteListingPreferenceCallbackRecord record : mListingPreferenceCallbackRecords) {
+ record.mExecutor.execute(
+ () ->
+ record.mRouteListingPreferenceCallback.onRouteListingPreferenceChanged(
+ preference));
+ }
+ }
+
private void notifyTransfer(RoutingController oldController, RoutingController newController) {
for (TransferCallbackRecord record : mTransferCallbackRecords) {
record.mExecutor.execute(
@@ -1091,6 +1153,12 @@ public final class MediaRouter2 {
}
}
+ private void notifyRequestFailed(int reason) {
+ for (TransferCallbackRecord record : mTransferCallbackRecords) {
+ record.mExecutor.execute(() -> record.mTransferCallback.onRequestFailed(reason));
+ }
+ }
+
private void notifyStop(RoutingController controller) {
for (TransferCallbackRecord record : mTransferCallbackRecords) {
record.mExecutor.execute(() -> record.mTransferCallback.onStop(controller));
@@ -1155,6 +1223,12 @@ public final class MediaRouter2 {
public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
}
+ /** @hide */
+ public abstract static class RouteListingPreferenceCallback {
+ /** @hide */
+ public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {}
+ }
+
/** Callback for receiving events on media transfer. */
public abstract static class TransferCallback {
/**
@@ -1191,6 +1265,15 @@ public final class MediaRouter2 {
* @param controller the controller that controlled the stopped media routing
*/
public void onStop(@NonNull RoutingController controller) {}
+
+ /**
+ * Called when a routing request fails.
+ *
+ * @param reason Reason for failure as per {@link
+ * android.media.MediaRoute2ProviderService.Reason}
+ * @hide
+ */
+ public void onRequestFailed(int reason) {}
}
/**
@@ -1684,6 +1767,35 @@ public final class MediaRouter2 {
}
}
+ private static final class RouteListingPreferenceCallbackRecord {
+ public final Executor mExecutor;
+ public final RouteListingPreferenceCallback mRouteListingPreferenceCallback;
+
+ /* package */ RouteListingPreferenceCallbackRecord(
+ @NonNull Executor executor,
+ @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+ mExecutor = executor;
+ mRouteListingPreferenceCallback = routeListingPreferenceCallback;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof RouteListingPreferenceCallbackRecord)) {
+ return false;
+ }
+ return mRouteListingPreferenceCallback
+ == ((RouteListingPreferenceCallbackRecord) obj).mRouteListingPreferenceCallback;
+ }
+
+ @Override
+ public int hashCode() {
+ return mRouteListingPreferenceCallback.hashCode();
+ }
+ }
+
static final class TransferCallbackRecord {
public final Executor mExecutor;
public final TransferCallback mTransferCallback;
@@ -2549,6 +2661,24 @@ public final class MediaRouter2 {
notifyPreferredFeaturesChanged(preference.getPreferredFeatures());
}
+ private void onRouteListingPreferenceChangedOnHandler(
+ @NonNull String packageName,
+ @Nullable RouteListingPreference routeListingPreference) {
+ if (!TextUtils.equals(getClientPackageName(), packageName)) {
+ return;
+ }
+
+ synchronized (mLock) {
+ if (Objects.equals(mRouteListingPreference, routeListingPreference)) {
+ return;
+ }
+
+ mRouteListingPreference = routeListingPreference;
+ }
+
+ notifyRouteListingPreferenceUpdated(routeListingPreference);
+ }
+
private void onRoutesUpdatedOnHandler(@NonNull List<MediaRoute2Info> routes) {
synchronized (mLock) {
mRoutes.clear();
@@ -2572,7 +2702,7 @@ public final class MediaRouter2 {
mTransferRequests.remove(matchingRequest);
onTransferFailed(matchingRequest.mOldSessionInfo, matchingRequest.mTargetRoute);
} else {
- // Does nothing.
+ notifyRequestFailed(reason);
}
}
@@ -2620,7 +2750,12 @@ public final class MediaRouter2 {
@Override
public void notifyRouteListingPreferenceChange(
String packageName, RouteListingPreference routeListingPreference) {
- // TODO(b/281067101): Add callback and getter for RouteListingPreference.
+ mHandler.sendMessage(
+ obtainMessage(
+ ProxyMediaRouter2Impl::onRouteListingPreferenceChangedOnHandler,
+ ProxyMediaRouter2Impl.this,
+ packageName,
+ routeListingPreference));
}
@Override
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
index 67d218413e74..d2c1c04265bf 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
@@ -20,7 +20,7 @@
<meta charset="UTF-8">
<meta name="description" content="
This is a HTML page that calls and verifies responses from the @JavascriptInterface functions of
- SlicePurchaseWebInterface. Test slice purchase application behavior using ADB shell commands and
+ DataBoostWebServiceFlow. Test slice purchase application behavior using ADB shell commands and
the APIs below:
FROM TERMINAL:
@@ -65,8 +65,8 @@
<p id="requested_capability"></p>
<h2>Notify purchase successful</h2>
- <button type="button" onclick="testNotifyPurchaseSuccessful(60000)">
- Notify purchase successful for 1 minute
+ <button type="button" onclick="testNotifyPurchaseSuccessful()">
+ Notify purchase successful
</button>
<p id="purchase_successful"></p>
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.js b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
index 02c4feac7ee4..84ab1f933838 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.js
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
@@ -15,19 +15,19 @@
*/
function testGetRequestedCapability() {
- let capability = SlicePurchaseWebInterface.getRequestedCapability();
+ let capability = DataBoostWebServiceFlow.getRequestedCapability();
document.getElementById("requested_capability").innerHTML =
"Premium capability requested: " + capability;
}
-function testNotifyPurchaseSuccessful(duration_ms_long = 0) {
- SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+function testNotifyPurchaseSuccessful() {
+ DataBoostWebServiceFlow.notifyPurchaseSuccessful();
document.getElementById("purchase_successful").innerHTML =
- "Notified purchase success for duration: " + duration_ms_long;
+ "Notified purchase successful.";
}
function testNotifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
- SlicePurchaseWebInterface.notifyPurchaseFailed(failure_code, failure_reason);
+ DataBoostWebServiceFlow.notifyPurchaseFailed(failure_code, failure_reason);
document.getElementById("purchase_failed").innerHTML =
"Notified purchase failed.";
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
index 8547898df678..0aadd31b6e18 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
@@ -24,13 +24,13 @@ import android.webkit.JavascriptInterface;
import com.android.phone.slice.SlicePurchaseController;
/**
- * Slice purchase web interface class allowing carrier websites to send responses back to the
+ * Data boost web service flow interface allowing carrier websites to send responses back to the
* slice purchase application using JavaScript.
*/
-public class SlicePurchaseWebInterface {
+public class DataBoostWebServiceFlow {
@NonNull SlicePurchaseActivity mActivity;
- public SlicePurchaseWebInterface(@NonNull SlicePurchaseActivity activity) {
+ public DataBoostWebServiceFlow(@NonNull SlicePurchaseActivity activity) {
mActivity = activity;
}
@@ -41,7 +41,7 @@ public class SlicePurchaseWebInterface {
* This can be called using the JavaScript below:
* <script type="text/javascript">
* function getRequestedCapability(duration) {
- * SlicePurchaseWebInterface.getRequestedCapability();
+ * DataBoostWebServiceFlow.getRequestedCapability();
* }
* </script>
*/
@@ -57,16 +57,14 @@ public class SlicePurchaseWebInterface {
*
* This can be called using the JavaScript below:
* <script type="text/javascript">
- * function notifyPurchaseSuccessful(duration_ms_long = 0) {
- * SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+ * function notifyPurchaseSuccessful() {
+ * DataBoostWebServiceFlow.notifyPurchaseSuccessful();
* }
* </script>
- *
- * @param duration The duration for which the premium capability is purchased in milliseconds.
*/
@JavascriptInterface
- public void notifyPurchaseSuccessful(long duration) {
- mActivity.onPurchaseSuccessful(duration);
+ public void notifyPurchaseSuccessful() {
+ mActivity.onPurchaseSuccessful();
}
/**
@@ -76,7 +74,7 @@ public class SlicePurchaseWebInterface {
* This can be called using the JavaScript below:
* <script type="text/javascript">
* function notifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
- * SlicePurchaseWebInterface.notifyPurchaseFailed();
+ * DataBoostWebServiceFlow.notifyPurchaseFailed();
* }
* </script>
*
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 946185a3c420..d304394dacb7 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -32,21 +32,19 @@ import android.webkit.WebView;
import com.android.phone.slice.SlicePurchaseController;
import java.net.URL;
-import java.util.concurrent.TimeUnit;
/**
* Activity that launches when the user clicks on the performance boost notification.
* This will open a {@link WebView} for the carrier website to allow the user to complete the
* premium capability purchase.
* The carrier website can get the requested premium capability using the JavaScript interface
- * method {@code SlicePurchaseWebInterface.getRequestedCapability()}.
+ * method {@code DataBoostWebServiceFlow.getRequestedCapability()}.
* If the purchase is successful, the carrier website shall notify the slice purchase application
* using the JavaScript interface method
- * {@code SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration)}, where {@code duration} is
- * the optional duration of the performance boost.
+ * {@code DataBoostWebServiceFlow.notifyPurchaseSuccessful()}.
* If the purchase was not successful, the carrier website shall notify the slice purchase
* application using the JavaScript interface method
- * {@code SlicePurchaseWebInterface.notifyPurchaseFailed(code, reason)}, where {@code code} is the
+ * {@code DataBoostWebServiceFlow.notifyPurchaseFailed(code, reason)}, where {@code code} is the
* {@link SlicePurchaseController.FailureCode} indicating the reason for failure and {@code reason}
* is the human-readable reason for failure if the failure code is
* {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN}.
@@ -118,15 +116,11 @@ public class SlicePurchaseActivity extends Activity {
setupWebView();
}
- protected void onPurchaseSuccessful(long duration) {
+ protected void onPurchaseSuccessful() {
logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium "
- + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)
- + (duration > 0 ? " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes."
- : "."));
- Intent intent = new Intent();
- intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_DURATION, duration);
- SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
- mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
+ + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability));
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+ mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS);
finishAndRemoveTask();
}
@@ -178,7 +172,7 @@ public class SlicePurchaseActivity extends Activity {
// the slice purchase application.
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(
- new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
+ new DataBoostWebServiceFlow(this), "DataBoostWebServiceFlow");
// Display WebView
setContentView(mWebView);
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
index daf0b42dbd7c..e7a75e58105d 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -53,6 +53,7 @@ public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchas
private static final int PHONE_ID = 0;
@Mock PendingIntent mPendingIntent;
+ @Mock PendingIntent mSuccessfulIntent;
@Mock PendingIntent mCanceledIntent;
@Mock CarrierConfigManager mCarrierConfigManager;
@Mock NotificationManager mNotificationManager;
@@ -107,20 +108,18 @@ public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchas
doReturn(true).when(mCanceledIntent).isBroadcast();
doReturn(mCanceledIntent).when(spiedIntent).getParcelableExtra(
eq(SlicePurchaseController.EXTRA_INTENT_CANCELED), eq(PendingIntent.class));
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mSuccessfulIntent).getCreatorPackage();
+ doReturn(true).when(mSuccessfulIntent).isBroadcast();
+ doReturn(mSuccessfulIntent).when(spiedIntent).getParcelableExtra(
+ eq(SlicePurchaseController.EXTRA_INTENT_SUCCESS), eq(PendingIntent.class));
mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
}
@Test
public void testOnPurchaseSuccessful() throws Exception {
- int duration = 5 * 60 * 1000; // 5 minutes
- int invalidDuration = -1;
- mSlicePurchaseActivity.onPurchaseSuccessful(duration);
- ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
- Intent intent = intentCaptor.getValue();
- assertEquals(duration, intent.getLongExtra(
- SlicePurchaseController.EXTRA_PURCHASE_DURATION, invalidDuration));
+ mSlicePurchaseActivity.onPurchaseSuccessful();
+ verify(mSuccessfulIntent).send();
}
@Test
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 0db88af92a56..0c1b793102bf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -51,6 +51,7 @@ public class CachedBluetoothDeviceManager {
CsipDeviceManager mCsipDeviceManager;
BluetoothDevice mOngoingSetMemberPair;
boolean mIsLateBonding;
+ int mGroupIdOfLateBonding;
public CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
mContext = context;
@@ -213,6 +214,14 @@ public class CachedBluetoothDeviceManager {
* @return The name, or if unavailable, the address.
*/
public String getName(BluetoothDevice device) {
+ if (isOngoingPairByCsip(device)) {
+ CachedBluetoothDevice firstDevice =
+ mCsipDeviceManager.getFirstMemberDevice(mGroupIdOfLateBonding);
+ if (firstDevice != null && firstDevice.getName() != null) {
+ return firstDevice.getName();
+ }
+ }
+
CachedBluetoothDevice cachedDevice = findDevice(device);
if (cachedDevice != null && cachedDevice.getName() != null) {
return cachedDevice.getName();
@@ -314,6 +323,7 @@ public class CachedBluetoothDeviceManager {
// To clear the SetMemberPair flag when the Bluetooth is turning off.
mOngoingSetMemberPair = null;
mIsLateBonding = false;
+ mGroupIdOfLateBonding = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
}
}
@@ -426,6 +436,7 @@ public class CachedBluetoothDeviceManager {
return false;
}
+ Log.d(TAG, "isLateBonding: " + mIsLateBonding);
return mIsLateBonding;
}
@@ -444,11 +455,13 @@ public class CachedBluetoothDeviceManager {
Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " groupId=" + groupId + " by CSIP ");
mOngoingSetMemberPair = device;
mIsLateBonding = checkLateBonding(groupId);
+ mGroupIdOfLateBonding = groupId;
syncConfigFromMainDevice(device, groupId);
if (!device.createBond(BluetoothDevice.TRANSPORT_LE)) {
Log.d(TAG, "Bonding could not be started");
mOngoingSetMemberPair = null;
mIsLateBonding = false;
+ mGroupIdOfLateBonding = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
}
}
@@ -494,6 +507,7 @@ public class CachedBluetoothDeviceManager {
mOngoingSetMemberPair = null;
mIsLateBonding = false;
+ mGroupIdOfLateBonding = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
if (bondState != BluetoothDevice.BOND_NONE) {
if (findDevice(device) == null) {
final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index c4f09cecfa1f..f911d35757f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -111,6 +111,9 @@ public class DreamBackend {
public static final int COMPLICATION_TYPE_SMARTSPACE = 7;
public static final int COMPLICATION_TYPE_MEDIA_ENTRY = 8;
+ private static final int SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT = 1;
+ private static final int LOCKSCREEN_SHOW_CONTROLS_DEFAULT = 0;
+
private final Context mContext;
private final IDreamManager mDreamManager;
private final DreamInfoComparator mComparator;
@@ -311,8 +314,14 @@ public class DreamBackend {
/** Gets whether home controls button is enabled on the dream */
private boolean getHomeControlsEnabled() {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, 1) == 1;
+ return Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ LOCKSCREEN_SHOW_CONTROLS_DEFAULT) == 1
+ && Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
+ SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT) == 1;
}
/**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
index 22ec12d44d6d..2edf403e5c00 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.when;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
+import android.provider.Settings;
import org.junit.After;
import org.junit.Before;
@@ -84,6 +85,7 @@ public final class DreamBackendTest {
@Test
public void testComplicationsEnabledByDefault() {
+ setControlsEnabledOnLockscreen(true);
assertThat(mBackend.getComplicationsEnabled()).isTrue();
assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn(
SUPPORTED_DREAM_COMPLICATIONS_LIST);
@@ -91,6 +93,7 @@ public final class DreamBackendTest {
@Test
public void testEnableComplicationExplicitly() {
+ setControlsEnabledOnLockscreen(true);
mBackend.setComplicationsEnabled(true);
assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn(
SUPPORTED_DREAM_COMPLICATIONS_LIST);
@@ -99,6 +102,7 @@ public final class DreamBackendTest {
@Test
public void testDisableComplications() {
+ setControlsEnabledOnLockscreen(true);
mBackend.setComplicationsEnabled(false);
assertThat(mBackend.getEnabledComplications())
.containsExactly(COMPLICATION_TYPE_HOME_CONTROLS);
@@ -107,6 +111,7 @@ public final class DreamBackendTest {
@Test
public void testHomeControlsDisabled_ComplicationsEnabled() {
+ setControlsEnabledOnLockscreen(true);
mBackend.setComplicationsEnabled(true);
mBackend.setHomeControlsEnabled(false);
// Home controls should not be enabled, only date and time.
@@ -118,6 +123,7 @@ public final class DreamBackendTest {
@Test
public void testHomeControlsDisabled_ComplicationsDisabled() {
+ setControlsEnabledOnLockscreen(true);
mBackend.setComplicationsEnabled(false);
mBackend.setHomeControlsEnabled(false);
assertThat(mBackend.getEnabledComplications()).isEmpty();
@@ -125,9 +131,9 @@ public final class DreamBackendTest {
@Test
public void testHomeControlsEnabled_ComplicationsDisabled() {
+ setControlsEnabledOnLockscreen(true);
mBackend.setComplicationsEnabled(false);
mBackend.setHomeControlsEnabled(true);
- // Home controls should not be enabled, only date and time.
final List<Integer> enabledComplications =
Collections.singletonList(COMPLICATION_TYPE_HOME_CONTROLS);
assertThat(mBackend.getEnabledComplications())
@@ -136,9 +142,9 @@ public final class DreamBackendTest {
@Test
public void testHomeControlsEnabled_ComplicationsEnabled() {
+ setControlsEnabledOnLockscreen(true);
mBackend.setComplicationsEnabled(true);
mBackend.setHomeControlsEnabled(true);
- // Home controls should not be enabled, only date and time.
final List<Integer> enabledComplications =
Arrays.asList(
COMPLICATION_TYPE_HOME_CONTROLS,
@@ -148,4 +154,26 @@ public final class DreamBackendTest {
assertThat(mBackend.getEnabledComplications())
.containsExactlyElementsIn(enabledComplications);
}
+
+ @Test
+ public void testHomeControlsEnabled_lockscreenDisabled() {
+ setControlsEnabledOnLockscreen(false);
+ mBackend.setComplicationsEnabled(true);
+ mBackend.setHomeControlsEnabled(true);
+ // Home controls should not be enabled, only date and time.
+ final List<Integer> enabledComplications =
+ Arrays.asList(
+ COMPLICATION_TYPE_DATE,
+ COMPLICATION_TYPE_TIME
+ );
+ assertThat(mBackend.getEnabledComplications())
+ .containsExactlyElementsIn(enabledComplications);
+ }
+
+ private void setControlsEnabledOnLockscreen(boolean enabled) {
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ enabled ? 1 : 0);
+ }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 5544b09e5d0b..1192e0086123 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -785,12 +785,6 @@ class SettingsProtoDumpUtil {
Settings.Global.ANGLE_EGL_FEATURES,
GlobalSettingsProto.Gpu.ANGLE_EGL_FEATURES);
dumpSetting(s, p,
- Settings.Global.ANGLE_DEFERLIST,
- GlobalSettingsProto.Gpu.ANGLE_DEFERLIST);
- dumpSetting(s, p,
- Settings.Global.ANGLE_DEFERLIST_MODE,
- GlobalSettingsProto.Gpu.ANGLE_DEFERLIST_MODE);
- dumpSetting(s, p,
Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX,
GlobalSettingsProto.Gpu.SHOW_ANGLE_IN_USE_DIALOG);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c2ab87ab0e78..a64cf11d3bf7 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -519,8 +519,6 @@ public class SettingsBackupTest {
Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS,
Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES,
Settings.Global.ANGLE_EGL_FEATURES,
- Settings.Global.ANGLE_DEFERLIST,
- Settings.Global.ANGLE_DEFERLIST_MODE,
Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
index d1ee18adaf6e..25269dc0d7d8 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
@@ -85,7 +85,7 @@ private val ButtonPaddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
@Composable
private fun filledButtonColors(): ButtonColors {
- val colors = LocalAndroidColorScheme.current
+ val colors = LocalAndroidColorScheme.current.deprecated
return ButtonDefaults.buttonColors(
containerColor = colors.colorAccentPrimary,
contentColor = colors.textColorOnAccent,
@@ -94,7 +94,7 @@ private fun filledButtonColors(): ButtonColors {
@Composable
private fun outlineButtonColors(): ButtonColors {
- val colors = LocalAndroidColorScheme.current
+ val colors = LocalAndroidColorScheme.current.deprecated
return ButtonDefaults.outlinedButtonColors(
contentColor = colors.textColorPrimary,
)
@@ -102,7 +102,7 @@ private fun outlineButtonColors(): ButtonColors {
@Composable
private fun outlineButtonBorder(): BorderStroke {
- val colors = LocalAndroidColorScheme.current
+ val colors = LocalAndroidColorScheme.current.deprecated
return BorderStroke(
width = 1.dp,
color = colors.colorAccentPrimaryVariant,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
index 4f1657faddee..1d6f813cfbdf 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
@@ -37,33 +37,83 @@ val LocalAndroidColorScheme =
* Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
* most of the colors in this class will be removed in favor of their M3 counterpart.
*/
-class AndroidColorScheme internal constructor(context: Context) {
- val colorPrimary = getColor(context, R.attr.colorPrimary)
- val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
- val colorAccent = getColor(context, R.attr.colorAccent)
- val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary)
- val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary)
- val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary)
- val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant)
- val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant)
- val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant)
- val colorSurface = getColor(context, R.attr.colorSurface)
- val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight)
- val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant)
- val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader)
- val colorError = getColor(context, R.attr.colorError)
- val colorBackground = getColor(context, R.attr.colorBackground)
- val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating)
- val panelColorBackground = getColor(context, R.attr.panelColorBackground)
- val textColorPrimary = getColor(context, R.attr.textColorPrimary)
- val textColorSecondary = getColor(context, R.attr.textColorSecondary)
- val textColorTertiary = getColor(context, R.attr.textColorTertiary)
- val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse)
- val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse)
- val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse)
- val textColorOnAccent = getColor(context, R.attr.textColorOnAccent)
- val colorForeground = getColor(context, R.attr.colorForeground)
- val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
+class AndroidColorScheme(context: Context) {
+ val onSecondaryFixedVariant = getColor(context, R.attr.materialColorOnSecondaryFixedVariant)
+ val onTertiaryFixedVariant = getColor(context, R.attr.materialColorOnTertiaryFixedVariant)
+ val surfaceContainerLowest = getColor(context, R.attr.materialColorSurfaceContainerLowest)
+ val onPrimaryFixedVariant = getColor(context, R.attr.materialColorOnPrimaryFixedVariant)
+ val onSecondaryContainer = getColor(context, R.attr.materialColorOnSecondaryContainer)
+ val onTertiaryContainer = getColor(context, R.attr.materialColorOnTertiaryContainer)
+ val surfaceContainerLow = getColor(context, R.attr.materialColorSurfaceContainerLow)
+ val onPrimaryContainer = getColor(context, R.attr.materialColorOnPrimaryContainer)
+ val secondaryFixedDim = getColor(context, R.attr.materialColorSecondaryFixedDim)
+ val onErrorContainer = getColor(context, R.attr.materialColorOnErrorContainer)
+ val onSecondaryFixed = getColor(context, R.attr.materialColorOnSecondaryFixed)
+ val onSurfaceInverse = getColor(context, R.attr.materialColorOnSurfaceInverse)
+ val tertiaryFixedDim = getColor(context, R.attr.materialColorTertiaryFixedDim)
+ val onTertiaryFixed = getColor(context, R.attr.materialColorOnTertiaryFixed)
+ val primaryFixedDim = getColor(context, R.attr.materialColorPrimaryFixedDim)
+ val secondaryContainer = getColor(context, R.attr.materialColorSecondaryContainer)
+ val errorContainer = getColor(context, R.attr.materialColorErrorContainer)
+ val onPrimaryFixed = getColor(context, R.attr.materialColorOnPrimaryFixed)
+ val primaryInverse = getColor(context, R.attr.materialColorPrimaryInverse)
+ val secondaryFixed = getColor(context, R.attr.materialColorSecondaryFixed)
+ val surfaceInverse = getColor(context, R.attr.materialColorSurfaceInverse)
+ val surfaceVariant = getColor(context, R.attr.materialColorSurfaceVariant)
+ val tertiaryContainer = getColor(context, R.attr.materialColorTertiaryContainer)
+ val tertiaryFixed = getColor(context, R.attr.materialColorTertiaryFixed)
+ val primaryContainer = getColor(context, R.attr.materialColorPrimaryContainer)
+ val onBackground = getColor(context, R.attr.materialColorOnBackground)
+ val primaryFixed = getColor(context, R.attr.materialColorPrimaryFixed)
+ val onSecondary = getColor(context, R.attr.materialColorOnSecondary)
+ val onTertiary = getColor(context, R.attr.materialColorOnTertiary)
+ val surfaceDim = getColor(context, R.attr.materialColorSurfaceDim)
+ val surfaceBright = getColor(context, R.attr.materialColorSurfaceBright)
+ val onError = getColor(context, R.attr.materialColorOnError)
+ val surface = getColor(context, R.attr.materialColorSurface)
+ val surfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh)
+ val surfaceContainerHighest = getColor(context, R.attr.materialColorSurfaceContainerHighest)
+ val onSurfaceVariant = getColor(context, R.attr.materialColorOnSurfaceVariant)
+ val outline = getColor(context, R.attr.materialColorOutline)
+ val outlineVariant = getColor(context, R.attr.materialColorOutlineVariant)
+ val onPrimary = getColor(context, R.attr.materialColorOnPrimary)
+ val onSurface = getColor(context, R.attr.materialColorOnSurface)
+ val surfaceContainer = getColor(context, R.attr.materialColorSurfaceContainer)
+ val primary = getColor(context, R.attr.materialColorPrimary)
+ val secondary = getColor(context, R.attr.materialColorSecondary)
+ val tertiary = getColor(context, R.attr.materialColorTertiary)
+
+ @Deprecated("Use the new android tokens: go/sysui-colors")
+ val deprecated = DeprecatedValues(context)
+
+ class DeprecatedValues(context: Context) {
+ val colorPrimary = getColor(context, R.attr.colorPrimary)
+ val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
+ val colorAccent = getColor(context, R.attr.colorAccent)
+ val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary)
+ val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary)
+ val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary)
+ val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant)
+ val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant)
+ val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant)
+ val colorSurface = getColor(context, R.attr.colorSurface)
+ val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight)
+ val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant)
+ val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader)
+ val colorError = getColor(context, R.attr.colorError)
+ val colorBackground = getColor(context, R.attr.colorBackground)
+ val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating)
+ val panelColorBackground = getColor(context, R.attr.panelColorBackground)
+ val textColorPrimary = getColor(context, R.attr.textColorPrimary)
+ val textColorSecondary = getColor(context, R.attr.textColorSecondary)
+ val textColorTertiary = getColor(context, R.attr.textColorTertiary)
+ val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse)
+ val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse)
+ val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse)
+ val textColorOnAccent = getColor(context, R.attr.textColorOnAccent)
+ val colorForeground = getColor(context, R.attr.colorForeground)
+ val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
+ }
companion object {
fun getColor(context: Context, attr: Int): Color {
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/SystemUIThemeTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/SystemUIThemeTest.kt
index 9bc68564de90..fe340174d56b 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/SystemUIThemeTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/SystemUIThemeTest.kt
@@ -40,7 +40,9 @@ class SystemUIThemeTest {
@Test
fun testAndroidColorsAreAvailableInsideTheme() {
composeRule.setContent {
- PlatformTheme { Text("foo", color = LocalAndroidColorScheme.current.colorAccent) }
+ PlatformTheme {
+ Text("foo", color = LocalAndroidColorScheme.current.deprecated.colorAccent)
+ }
}
composeRule.onNodeWithText("foo").assertIsDisplayed()
@@ -50,7 +52,7 @@ class SystemUIThemeTest {
fun testAccessingAndroidColorsWithoutThemeThrows() {
assertThrows(IllegalStateException::class.java) {
composeRule.setContent {
- Text("foo", color = LocalAndroidColorScheme.current.colorAccent)
+ Text("foo", color = LocalAndroidColorScheme.current.deprecated.colorAccent)
}
}
}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
index 24064b1261b7..5413f9097c5b 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
@@ -17,10 +17,12 @@
package com.android.systemui.scene.ui.composable
import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneContainerNames
import dagger.Module
import dagger.multibindings.Multibinds
+import javax.inject.Named
@Module
interface SceneModule {
- @Multibinds fun scenes(): Set<Scene>
+ @Multibinds @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) fun scenes(): Set<Scene>
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
index ee53ece5c944..954bad56bcc2 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
@@ -17,22 +17,32 @@
package com.android.systemui.scene.ui.composable
import com.android.systemui.bouncer.ui.composable.BouncerScene
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.composable.LockscreenScene
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.qs.ui.composable.QuickSettingsScene
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.shade.ui.composable.ShadeScene
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
import dagger.Module
import dagger.Provides
+import javax.inject.Named
+import kotlinx.coroutines.CoroutineScope
@Module
object SceneModule {
@Provides
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
fun scenes(
- bouncer: BouncerScene,
- gone: GoneScene,
- lockScreen: LockscreenScene,
- qs: QuickSettingsScene,
- shade: ShadeScene,
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) bouncer: BouncerScene,
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) gone: GoneScene,
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) lockScreen: LockscreenScene,
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) qs: QuickSettingsScene,
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) shade: ShadeScene,
): Set<Scene> {
return setOf(
bouncer,
@@ -42,4 +52,71 @@ object SceneModule {
shade,
)
}
+
+ @Provides
+ @SysUISingleton
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+ fun bouncerScene(
+ viewModelFactory: BouncerViewModel.Factory,
+ ): BouncerScene {
+ return BouncerScene(
+ viewModel =
+ viewModelFactory.create(
+ containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
+ ),
+ )
+ }
+
+ @Provides
+ @SysUISingleton
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+ fun goneScene(): GoneScene {
+ return GoneScene()
+ }
+
+ @Provides
+ @SysUISingleton
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+ fun lockscreenScene(
+ @Application applicationScope: CoroutineScope,
+ viewModelFactory: LockscreenSceneViewModel.Factory,
+ ): LockscreenScene {
+ return LockscreenScene(
+ applicationScope = applicationScope,
+ viewModel =
+ viewModelFactory.create(
+ containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
+ ),
+ )
+ }
+
+ @Provides
+ @SysUISingleton
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+ fun quickSettingsScene(
+ viewModelFactory: QuickSettingsSceneViewModel.Factory,
+ ): QuickSettingsScene {
+ return QuickSettingsScene(
+ viewModel =
+ viewModelFactory.create(
+ containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
+ ),
+ )
+ }
+
+ @Provides
+ @SysUISingleton
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+ fun shadeScene(
+ @Application applicationScope: CoroutineScope,
+ viewModelFactory: ShadeSceneViewModel.Factory,
+ ): ShadeScene {
+ return ShadeScene(
+ applicationScope = applicationScope,
+ viewModel =
+ viewModelFactory.create(
+ containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
+ ),
+ )
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index f48bab90ba42..3c74ef5adfeb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -40,22 +40,17 @@ import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.ui.composable.ComposableScene
-import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
-@SysUISingleton
-class BouncerScene
-@Inject
-constructor(
- private val viewModelFactory: BouncerViewModel.Factory,
+class BouncerScene(
+ private val viewModel: BouncerViewModel,
) : ComposableScene {
override val key = SceneKey.Bouncer
@@ -73,7 +68,7 @@ constructor(
override fun Content(
containerName: String,
modifier: Modifier,
- ) = BouncerScene(viewModelFactory.create(containerName), modifier)
+ ) = BouncerScene(viewModel, modifier)
}
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 7c07a8b3d054..10652678739c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -31,7 +31,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.scene.shared.model.Direction
@@ -39,7 +38,6 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.ui.composable.ComposableScene
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -47,29 +45,22 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** The lock screen scene shows when the device is locked. */
-@SysUISingleton
-class LockscreenScene
-@Inject
-constructor(
+class LockscreenScene(
@Application private val applicationScope: CoroutineScope,
- private val viewModelFactory: LockscreenSceneViewModel.Factory,
+ private val viewModel: LockscreenSceneViewModel,
) : ComposableScene {
override val key = SceneKey.Lockscreen
- private var unsafeViewModel: LockscreenSceneViewModel? = null
-
override fun destinationScenes(
containerName: String,
): StateFlow<Map<UserAction, SceneModel>> =
- getOrCreateViewModelSingleton(containerName).let { viewModel ->
- viewModel.upDestinationSceneKey
- .map { pageKey -> destinationScenes(up = pageKey) }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value)
- )
- }
+ viewModel.upDestinationSceneKey
+ .map { pageKey -> destinationScenes(up = pageKey) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value)
+ )
@Composable
override fun Content(
@@ -77,7 +68,7 @@ constructor(
modifier: Modifier,
) {
LockscreenScene(
- viewModel = getOrCreateViewModelSingleton(containerName),
+ viewModel = viewModel,
modifier = modifier,
)
}
@@ -90,13 +81,6 @@ constructor(
UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade)
)
}
-
- private fun getOrCreateViewModelSingleton(
- containerName: String,
- ): LockscreenSceneViewModel {
- return unsafeViewModel
- ?: viewModelFactory.create(containerName).also { unsafeViewModel = it }
- }
}
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index a74e56b6e2f2..f88fc21addff 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -91,7 +91,7 @@ fun PeopleScreen(
// Make sure to use the Android colors and not the default Material3 colors to have the exact
// same colors as the View implementation.
- val androidColors = LocalAndroidColorScheme.current
+ val androidColors = LocalAndroidColorScheme.current.deprecated
Surface(
color = androidColors.colorBackground,
contentColor = androidColors.textColorPrimary,
@@ -170,7 +170,7 @@ private fun LazyListScope.ConversationList(
stringResource(headerTextResource),
Modifier.padding(start = 16.dp),
style = MaterialTheme.typography.labelLarge,
- color = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+ color = LocalAndroidColorScheme.current.deprecated.colorAccentPrimaryVariant,
)
Spacer(Modifier.height(10.dp))
@@ -180,7 +180,7 @@ private fun LazyListScope.ConversationList(
if (index > 0) {
item {
Divider(
- color = LocalAndroidColorScheme.current.colorBackground,
+ color = LocalAndroidColorScheme.current.deprecated.colorBackground,
thickness = 2.dp,
)
}
@@ -204,7 +204,7 @@ private fun Tile(
withTopCornerRadius: Boolean,
withBottomCornerRadius: Boolean,
) {
- val androidColors = LocalAndroidColorScheme.current
+ val androidColors = LocalAndroidColorScheme.current.deprecated
val cornerRadius = dimensionResource(R.dimen.people_space_widget_radius)
val topCornerRadius = if (withTopCornerRadius) cornerRadius else 0.dp
val bottomCornerRadius = if (withBottomCornerRadius) cornerRadius else 0.dp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
index 0484ff475cdf..1e6f4a2f5b23 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
@@ -76,8 +76,8 @@ internal fun PeopleScreenEmpty(
Modifier.fillMaxWidth().defaultMinSize(minHeight = 56.dp),
colors =
ButtonDefaults.buttonColors(
- containerColor = androidColors.colorAccentPrimary,
- contentColor = androidColors.textColorOnAccent,
+ containerColor = androidColors.deprecated.colorAccentPrimary,
+ contentColor = androidColors.deprecated.textColorOnAccent,
)
) {
Text(stringResource(R.string.got_it))
@@ -90,8 +90,8 @@ private fun ExampleTile() {
val androidColors = LocalAndroidColorScheme.current
Surface(
shape = RoundedCornerShape(28.dp),
- color = androidColors.colorSurface,
- contentColor = androidColors.textColorPrimary,
+ color = androidColors.deprecated.colorSurface,
+ contentColor = androidColors.deprecated.textColorPrimary,
) {
Row(
Modifier.padding(vertical = 20.dp, horizontal = 16.dp),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 349f5c333116..75bf2813a321 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -122,7 +122,7 @@ fun FooterActions(
}
val backgroundColor = colorAttr(R.attr.underSurfaceColor)
- val contentColor = LocalAndroidColorScheme.current.textColorPrimary
+ val contentColor = LocalAndroidColorScheme.current.deprecated.textColorPrimary
val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
val backgroundModifier =
remember(
@@ -287,7 +287,7 @@ private fun NumberButton(
number.toString(),
modifier = Modifier.align(Alignment.Center),
style = MaterialTheme.typography.bodyLarge,
- color = LocalAndroidColorScheme.current.textColorPrimary,
+ color = LocalAndroidColorScheme.current.deprecated.textColorPrimary,
// TODO(b/242040009): This should only use a standard text style instead and
// should not override the text size.
fontSize = 18.sp,
@@ -305,7 +305,7 @@ private fun NumberButton(
@Composable
private fun NewChangesDot(modifier: Modifier = Modifier) {
val contentDescription = stringResource(R.string.fgs_dot_content_description)
- val color = LocalAndroidColorScheme.current.colorAccentTertiary
+ val color = LocalAndroidColorScheme.current.deprecated.colorAccentTertiary
Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) {
drawCircle(color)
@@ -324,8 +324,9 @@ private fun TextButton(
Expandable(
shape = CircleShape,
color = colorAttr(R.attr.underSurfaceColor),
- contentColor = LocalAndroidColorScheme.current.textColorSecondary,
- borderStroke = BorderStroke(1.dp, LocalAndroidColorScheme.current.colorBackground),
+ contentColor = LocalAndroidColorScheme.current.deprecated.textColorSecondary,
+ borderStroke =
+ BorderStroke(1.dp, LocalAndroidColorScheme.current.deprecated.colorBackground),
modifier = modifier.padding(horizontal = 4.dp),
onClick = onClick,
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 58db37eca8d5..30b80ca7fc1e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -27,24 +27,19 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.ui.composable.ComposableScene
-import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
-@SysUISingleton
-class QuickSettingsScene
-@Inject
-constructor(
- private val viewModelFactory: QuickSettingsSceneViewModel.Factory,
+class QuickSettingsScene(
+ private val viewModel: QuickSettingsSceneViewModel,
) : ComposableScene {
override val key = SceneKey.QuickSettings
@@ -64,7 +59,7 @@ constructor(
modifier: Modifier,
) {
QuickSettingsScene(
- viewModel = viewModelFactory.create(containerName),
+ viewModel = viewModel,
modifier = modifier,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index b387463d3d90..0a4da1d6ba1e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -23,12 +23,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
-import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -37,8 +35,7 @@ import kotlinx.coroutines.flow.asStateFlow
* "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
* content from the scene framework.
*/
-@SysUISingleton
-class GoneScene @Inject constructor() : ComposableScene {
+class GoneScene : ComposableScene {
override val key = SceneKey.Gone
override fun destinationScenes(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index e4513d01ea37..20e175160aa6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -27,7 +27,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -35,7 +34,6 @@ import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -43,29 +41,22 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
-@SysUISingleton
-class ShadeScene
-@Inject
-constructor(
+class ShadeScene(
@Application private val applicationScope: CoroutineScope,
- private val viewModelFactory: ShadeSceneViewModel.Factory,
+ private val viewModel: ShadeSceneViewModel,
) : ComposableScene {
override val key = SceneKey.Shade
- private var unsafeViewModel: ShadeSceneViewModel? = null
-
override fun destinationScenes(
containerName: String,
): StateFlow<Map<UserAction, SceneModel>> =
- getOrCreateViewModelSingleton(containerName).let { viewModel ->
- viewModel.upDestinationSceneKey
- .map { sceneKey -> destinationScenes(up = sceneKey) }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value),
- )
- }
+ viewModel.upDestinationSceneKey
+ .map { sceneKey -> destinationScenes(up = sceneKey) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value),
+ )
@Composable
override fun Content(
@@ -73,7 +64,7 @@ constructor(
modifier: Modifier,
) {
ShadeScene(
- viewModel = getOrCreateViewModelSingleton(containerName),
+ viewModel = viewModel,
modifier = modifier,
)
}
@@ -86,13 +77,6 @@ constructor(
UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.QuickSettings),
)
}
-
- private fun getOrCreateViewModelSingleton(
- containerName: String,
- ): ShadeSceneViewModel {
- return unsafeViewModel
- ?: viewModelFactory.create(containerName).also { unsafeViewModel = it }
- }
}
@Composable
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 8dd2c39e7a58..465b73e6de19 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -272,6 +272,7 @@ class AnimatableClockView @JvmOverloads constructor(
color = lockScreenColor,
animate = isAnimationEnabled,
duration = APPEAR_ANIM_DURATION,
+ interpolator = Interpolators.EMPHASIZED_DECELERATE,
delay = 0,
onAnimationEnd = null
)
@@ -562,7 +563,7 @@ class AnimatableClockView @JvmOverloads constructor(
private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
private const val DOZE_ANIM_DURATION: Long = 300
- private const val APPEAR_ANIM_DURATION: Long = 350
+ private const val APPEAR_ANIM_DURATION: Long = 833
private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
private const val COLOR_ANIM_DURATION: Long = 400
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index cad2c162a589..4b7968953420 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -96,6 +96,7 @@
<!-- additional offset for clock switch area items -->
<dimen name="small_clock_height">114dp</dimen>
+ <dimen name="small_clock_padding_top">28dp</dimen>
<dimen name="clock_padding_start">28dp</dimen>
<dimen name="below_clock_padding_start">32dp</dimen>
<dimen name="below_clock_padding_end">16dp</dimen>
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index b3b40f340ecb..81691898dfe5 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
+<!-- TODO(b/251476085): inline in biometric_prompt_layout after Biometric*Views are un-flagged -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
diff --git a/packages/SystemUI/res/layout/auth_biometric_face_view.xml b/packages/SystemUI/res/layout/auth_biometric_face_view.xml
index be30f21af536..e3d073276369 100644
--- a/packages/SystemUI/res/layout/auth_biometric_face_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_face_view.xml
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
+<!-- TODO(b/251476085): remove after BiometricFaceView is un-flagged -->
<com.android.systemui.biometrics.AuthBiometricFaceView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contents"
diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
index 05ca2a786e3a..896d8362f5b9 100644
--- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
+<!-- TODO(b/251476085): remove after BiometricFingerprintAndFaceView is un-flagged -->
<com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contents"
diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
index 01ea31f8bdd2..e36f9796be47 100644
--- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
+<!-- TODO(b/251476085): remove after BiometricFingerprintView is un-flagged -->
<com.android.systemui.biometrics.AuthBiometricFingerprintView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contents"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
new file mode 100644
index 000000000000..05ff1b1c2e6f
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -0,0 +1,176 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.systemui.biometrics.ui.BiometricPromptLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/contents"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:singleLine="true"
+ android:marqueeRepeatLimit="1"
+ android:ellipsize="marquee"
+ android:importantForAccessibility="no"
+ style="@style/TextAppearance.AuthCredential.Title"/>
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:singleLine="true"
+ android:marqueeRepeatLimit="1"
+ android:ellipsize="marquee"
+ style="@style/TextAppearance.AuthCredential.Subtitle"/>
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars ="vertical"
+ android:importantForAccessibility="no"
+ style="@style/TextAppearance.AuthCredential.Description"/>
+
+ <Space android:id="@+id/space_above_icon"
+ android:layout_width="match_parent"
+ android:layout_height="48dp" />
+
+ <FrameLayout
+ android:id="@+id/biometric_icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center">
+
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/biometric_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:contentDescription="@null"
+ android:scaleType="fitXY" />
+
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/biometric_icon_overlay"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:contentDescription="@null"
+ android:scaleType="fitXY" />
+ </FrameLayout>
+
+ <!-- For sensors such as UDFPS, this view is used during custom measurement/layout to add extra
+ padding so that the biometric icon is always in the right physical position. -->
+ <Space android:id="@+id/space_below_icon"
+ android:layout_width="match_parent"
+ android:layout_height="12dp" />
+
+ <TextView
+ android:id="@+id/indicator"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="24dp"
+ android:textSize="12sp"
+ android:gravity="center_horizontal"
+ android:accessibilityLiveRegion="polite"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:scrollHorizontally="true"
+ android:fadingEdge="horizontal"
+ android:textColor="@color/biometric_dialog_gray"/>
+
+ <LinearLayout
+ android:id="@+id/button_bar"
+ android:layout_width="match_parent"
+ android:layout_height="88dp"
+ style="?android:attr/buttonBarStyle"
+ android:orientation="horizontal"
+ android:paddingTop="24dp">
+
+ <Space android:id="@+id/leftSpacer"
+ android:layout_width="8dp"
+ android:layout_height="match_parent"
+ android:visibility="visible" />
+
+ <!-- Negative Button, reserved for app -->
+ <Button android:id="@+id/button_negative"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_gravity="center_vertical"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"
+ android:visibility="gone"/>
+ <!-- Cancel Button, replaces negative button when biometric is accepted -->
+ <Button android:id="@+id/button_cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_gravity="center_vertical"
+ android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"
+ android:text="@string/cancel"
+ android:visibility="gone"/>
+ <!-- "Use Credential" Button, replaces if device credential is allowed -->
+ <Button android:id="@+id/button_use_credential"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_gravity="center_vertical"
+ android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"
+ android:visibility="gone"/>
+
+ <Space android:id="@+id/middleSpacer"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:visibility="visible"/>
+
+ <!-- Positive Button -->
+ <Button android:id="@+id/button_confirm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ android:layout_gravity="center_vertical"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:maxWidth="@dimen/biometric_dialog_button_positive_max_width"
+ android:text="@string/biometric_dialog_confirm"
+ android:visibility="gone"/>
+ <!-- Try Again Button -->
+ <Button android:id="@+id/button_try_again"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ android:layout_gravity="center_vertical"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:maxWidth="@dimen/biometric_dialog_button_positive_max_width"
+ android:text="@string/biometric_dialog_try_again"
+ android:visibility="gone"/>
+
+ <Space android:id="@+id/rightSpacer"
+ android:layout_width="8dp"
+ android:layout_height="match_parent"
+ android:visibility="visible" />
+ </LinearLayout>
+
+</com.android.systemui.biometrics.ui.BiometricPromptLayout>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index ae052502a110..bbf3adfb8c67 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -73,7 +73,7 @@
android:tint="?android:attr/textColorSecondary"
android:layout_gravity="center"
android:layout_weight="0"
- android:layout_marginRight="@dimen/screenrecord_option_padding"/>
+ android:layout_marginEnd="@dimen/screenrecord_option_padding"/>
<Spinner
android:id="@+id/screen_recording_options"
android:layout_width="0dp"
@@ -106,7 +106,7 @@
android:src="@drawable/ic_touch"
android:tint="?android:attr/textColorSecondary"
android:layout_gravity="center"
- android:layout_marginRight="@dimen/screenrecord_option_padding"/>
+ android:layout_marginEnd="@dimen/screenrecord_option_padding"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 1112bcdbd14d..9b1fa23081b4 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -85,7 +85,7 @@
android:layout_height="@dimen/volume_ringer_drawer_icon_size"
android:layout_gravity="center"
android:tint="?android:attr/textColorPrimary"
- android:src="@drawable/ic_volume_ringer_mute" />
+ android:src="@drawable/ic_speaker_mute" />
</FrameLayout>
@@ -102,7 +102,7 @@
android:layout_height="@dimen/volume_ringer_drawer_icon_size"
android:layout_gravity="center"
android:tint="?android:attr/textColorPrimary"
- android:src="@drawable/ic_volume_ringer" />
+ android:src="@drawable/ic_speaker_on" />
</FrameLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a62dead7834b..8d3ba364da06 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -716,7 +716,7 @@
<!-- Minimum margin between clock and status bar -->
<dimen name="keyguard_clock_top_margin">18dp</dimen>
<!-- The amount to shift the clocks during a small/large transition -->
- <dimen name="keyguard_clock_switch_y_shift">10dp</dimen>
+ <dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
<!-- When large clock is showing, offset the smartspace by this amount -->
<dimen name="keyguard_smartspace_top_offset">12dp</dimen>
<!-- With the large clock, move up slightly from the center -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 16c86482b9c7..e8b9f2a35659 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -353,7 +353,7 @@
<!-- Message shown when a biometric is authenticated, waiting for the user to confirm authentication [CHAR LIMIT=40]-->
<string name="biometric_dialog_tap_confirm">Tap Confirm to complete</string>
<!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]-->
- <string name="biometric_dialog_tap_confirm_with_face">Unlocked by face. Press the unlock icon to continue.</string>
+ <string name="biometric_dialog_tap_confirm_with_face">Unlocked by face.</string>
<!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]-->
<string name="biometric_dialog_tap_confirm_with_face_alt_1">Unlocked by face. Press to continue.</string>
<!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]-->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 7cce75fa71c6..a90a34c560cf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,5 +1,8 @@
package com.android.keyguard;
+import static android.view.View.ALPHA;
+import static android.view.View.TRANSLATION_Y;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -35,11 +38,12 @@ public class KeyguardClockSwitch extends RelativeLayout {
private static final String TAG = "KeyguardClockSwitch";
- private static final long CLOCK_OUT_MILLIS = 150;
- private static final long CLOCK_IN_MILLIS = 200;
- public static final long CLOCK_IN_START_DELAY_MILLIS = CLOCK_OUT_MILLIS / 2;
- private static final long STATUS_AREA_START_DELAY_MILLIS = 50;
- private static final long STATUS_AREA_MOVE_MILLIS = 350;
+ private static final long CLOCK_OUT_MILLIS = 133;
+ private static final long CLOCK_IN_MILLIS = 167;
+ public static final long CLOCK_IN_START_DELAY_MILLIS = 133;
+ private static final long STATUS_AREA_START_DELAY_MILLIS = 0;
+ private static final long STATUS_AREA_MOVE_UP_MILLIS = 967;
+ private static final long STATUS_AREA_MOVE_DOWN_MILLIS = 467;
@IntDef({LARGE, SMALL})
@Retention(RetentionPolicy.SOURCE)
@@ -66,6 +70,17 @@ public class KeyguardClockSwitch extends RelativeLayout {
top + targetHeight);
}
+ /** Returns a region for the small clock to position itself, based on the given parent. */
+ public static Rect getSmallClockRegion(ViewGroup parent) {
+ int targetHeight = parent.getResources()
+ .getDimensionPixelSize(R.dimen.small_clock_text_size);
+ return new Rect(
+ parent.getLeft(),
+ parent.getTop(),
+ parent.getRight(),
+ parent.getTop() + targetHeight);
+ }
+
/**
* Frame for small/large clocks
*/
@@ -90,7 +105,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
@VisibleForTesting AnimatorSet mClockInAnim = null;
@VisibleForTesting AnimatorSet mClockOutAnim = null;
- private ObjectAnimator mStatusAreaAnim = null;
+ private AnimatorSet mStatusAreaAnim = null;
private int mClockSwitchYAmount;
@VisibleForTesting boolean mChildrenAreLaidOut = false;
@@ -172,13 +187,8 @@ public class KeyguardClockSwitch extends RelativeLayout {
void updateClockTargetRegions() {
if (mClock != null) {
if (mSmallClockFrame.isLaidOut()) {
- int targetHeight = getResources()
- .getDimensionPixelSize(R.dimen.small_clock_text_size);
- mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect(
- mSmallClockFrame.getLeft(),
- mSmallClockFrame.getTop(),
- mSmallClockFrame.getRight(),
- mSmallClockFrame.getTop() + targetHeight));
+ Rect targetRegion = getSmallClockRegion(mSmallClockFrame);
+ mClock.getSmallClock().getEvents().onTargetRegionChanged(targetRegion);
}
if (mLargeClockFrame.isLaidOut()) {
@@ -220,28 +230,34 @@ public class KeyguardClockSwitch extends RelativeLayout {
mStatusAreaAnim = null;
View in, out;
- int direction = 1;
- float statusAreaYTranslation;
+ float statusAreaYTranslation, clockInYTranslation, clockOutYTranslation;
if (useLargeClock) {
out = mSmallClockFrame;
in = mLargeClockFrame;
if (indexOfChild(in) == -1) addView(in, 0);
- direction = -1;
statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
+ mSmartspaceTopOffset;
+ clockInYTranslation = 0;
+ clockOutYTranslation = 0; // Small clock translation is handled with statusArea
} else {
in = mSmallClockFrame;
out = mLargeClockFrame;
statusAreaYTranslation = 0f;
+ clockInYTranslation = 0f;
+ clockOutYTranslation = mClockSwitchYAmount * -1f;
- // Must remove in order for notifications to appear in the proper place
+ // Must remove in order for notifications to appear in the proper place, ideally this
+ // would happen after the out animation runs, but we can't guarantee that the
+ // nofications won't enter only after the out animation runs.
removeView(out);
}
if (!animate) {
out.setAlpha(0f);
+ out.setTranslationY(clockOutYTranslation);
out.setVisibility(INVISIBLE);
in.setAlpha(1f);
+ in.setTranslationY(clockInYTranslation);
in.setVisibility(VISIBLE);
mStatusArea.setTranslationY(statusAreaYTranslation);
return;
@@ -249,11 +265,10 @@ public class KeyguardClockSwitch extends RelativeLayout {
mClockOutAnim = new AnimatorSet();
mClockOutAnim.setDuration(CLOCK_OUT_MILLIS);
- mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ mClockOutAnim.setInterpolator(Interpolators.LINEAR);
mClockOutAnim.playTogether(
- ObjectAnimator.ofFloat(out, View.ALPHA, 0f),
- ObjectAnimator.ofFloat(out, View.TRANSLATION_Y, 0,
- direction * -mClockSwitchYAmount));
+ ObjectAnimator.ofFloat(out, ALPHA, 0f),
+ ObjectAnimator.ofFloat(out, TRANSLATION_Y, clockOutYTranslation));
mClockOutAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
if (mClockOutAnim == animation) {
@@ -268,8 +283,9 @@ public class KeyguardClockSwitch extends RelativeLayout {
mClockInAnim = new AnimatorSet();
mClockInAnim.setDuration(CLOCK_IN_MILLIS);
mClockInAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- mClockInAnim.playTogether(ObjectAnimator.ofFloat(in, View.ALPHA, 1f),
- ObjectAnimator.ofFloat(in, View.TRANSLATION_Y, direction * mClockSwitchYAmount, 0));
+ mClockInAnim.playTogether(
+ ObjectAnimator.ofFloat(in, ALPHA, 1f),
+ ObjectAnimator.ofFloat(in, TRANSLATION_Y, clockInYTranslation));
mClockInAnim.setStartDelay(CLOCK_IN_START_DELAY_MILLIS);
mClockInAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
@@ -279,14 +295,14 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
});
- mClockInAnim.start();
- mClockOutAnim.start();
-
- mStatusAreaAnim = ObjectAnimator.ofFloat(mStatusArea, View.TRANSLATION_Y,
- statusAreaYTranslation);
- mStatusAreaAnim.setStartDelay(useLargeClock ? STATUS_AREA_START_DELAY_MILLIS : 0L);
- mStatusAreaAnim.setDuration(STATUS_AREA_MOVE_MILLIS);
- mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mStatusAreaAnim = new AnimatorSet();
+ mStatusAreaAnim.setStartDelay(STATUS_AREA_START_DELAY_MILLIS);
+ mStatusAreaAnim.setDuration(
+ useLargeClock ? STATUS_AREA_MOVE_UP_MILLIS : STATUS_AREA_MOVE_DOWN_MILLIS);
+ mStatusAreaAnim.setInterpolator(Interpolators.EMPHASIZED);
+ mStatusAreaAnim.playTogether(
+ ObjectAnimator.ofFloat(mStatusArea, TRANSLATION_Y, statusAreaYTranslation),
+ ObjectAnimator.ofFloat(mSmallClockFrame, TRANSLATION_Y, statusAreaYTranslation));
mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
if (mStatusAreaAnim == animation) {
@@ -294,6 +310,9 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
}
});
+
+ mClockInAnim.start();
+ mClockOutAnim.start();
mStatusAreaAnim.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 9d9a87d72e46..c684dc54c6fd 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -51,6 +51,12 @@ interface AuthenticationRepository {
*/
val isBypassEnabled: StateFlow<Boolean>
+ /**
+ * Number of consecutively failed authentication attempts. This resets to `0` when
+ * authentication succeeds.
+ */
+ val failedAuthenticationAttempts: StateFlow<Int>
+
/** See [isUnlocked]. */
fun setUnlocked(isUnlocked: Boolean)
@@ -59,6 +65,9 @@ interface AuthenticationRepository {
/** See [isBypassEnabled]. */
fun setBypassEnabled(isBypassEnabled: Boolean)
+
+ /** See [failedAuthenticationAttempts]. */
+ fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int)
}
class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationRepository {
@@ -75,6 +84,10 @@ class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationReposit
private val _isBypassEnabled = MutableStateFlow(false)
override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
+ private val _failedAuthenticationAttempts = MutableStateFlow(0)
+ override val failedAuthenticationAttempts: StateFlow<Int> =
+ _failedAuthenticationAttempts.asStateFlow()
+
override fun setUnlocked(isUnlocked: Boolean) {
_isUnlocked.value = isUnlocked
}
@@ -86,6 +99,10 @@ class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationReposit
override fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
_authenticationMethod.value = authenticationMethod
}
+
+ override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) {
+ _failedAuthenticationAttempts.value = failedAuthenticationAttempts
+ }
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 5aea930401d9..3984627a181d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -75,6 +75,12 @@ constructor(
*/
val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
+ /**
+ * Number of consecutively failed authentication attempts. This resets to `0` when
+ * authentication succeeds.
+ */
+ val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts
+
init {
// UNLOCKS WHEN AUTH METHOD REMOVED.
//
@@ -130,7 +136,12 @@ constructor(
}
if (isSuccessful) {
+ repository.setFailedAuthenticationAttempts(0)
repository.setUnlocked(true)
+ } else {
+ repository.setFailedAuthenticationAttempts(
+ repository.failedAuthenticationAttempts.value + 1
+ )
}
return isSuccessful
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
index 83250b638424..6f008c3017b9 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
@@ -36,8 +36,10 @@ sealed class AuthenticationMethodModel(
data class Password(val password: String) : AuthenticationMethodModel(isSecure = true)
- data class Pattern(val coordinates: List<PatternCoordinate>) :
- AuthenticationMethodModel(isSecure = true) {
+ data class Pattern(
+ val coordinates: List<PatternCoordinate>,
+ val isPatternVisible: Boolean = true,
+ ) : AuthenticationMethodModel(isSecure = true) {
data class PatternCoordinate(
val x: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
index 1404053e4618..682888fc39b5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -21,42 +21,43 @@ import android.content.Context
import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
-import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
/** Face/Fingerprint combined icon animator for BiometricPrompt. */
-class AuthBiometricFingerprintAndFaceIconController(
- context: Context,
- iconView: LottieAnimationView,
- iconViewOverlay: LottieAnimationView
+open class AuthBiometricFingerprintAndFaceIconController(
+ context: Context,
+ iconView: LottieAnimationView,
+ iconViewOverlay: LottieAnimationView,
) : AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) {
override val actsAsConfirmButton: Boolean = true
override fun shouldAnimateIconViewForTransition(
- @BiometricState oldState: Int,
- @BiometricState newState: Int
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
): Boolean = when (newState) {
STATE_PENDING_CONFIRMATION -> true
- STATE_AUTHENTICATED -> false
else -> super.shouldAnimateIconViewForTransition(oldState, newState)
}
@RawRes
override fun getAnimationForTransition(
- @BiometricState oldState: Int,
- @BiometricState newState: Int
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
): Int? = when (newState) {
STATE_PENDING_CONFIRMATION -> {
if (oldState == STATE_ERROR || oldState == STATE_HELP) {
R.raw.fingerprint_dialogue_error_to_unlock_lottie
+ } else if (oldState == STATE_PENDING_CONFIRMATION) {
+ // TODO(jbolinger): missing asset for this transition
+ // (unlocked icon to success checkmark)
+ R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
} else {
R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
}
}
- STATE_AUTHENTICATED -> null
else -> super.getAnimationForTransition(oldState, newState)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
index 57ffd240065d..7ce74dbe91b4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
@@ -40,11 +40,11 @@ class AuthBiometricFingerprintAndFaceView(
override fun ignoreUnsuccessfulEventsFrom(@Modality modality: Int, unsuccessfulReason: String) =
modality == TYPE_FACE && !(isFaceClass3 && isLockoutErrorString(unsuccessfulReason))
- override fun onPointerDown(failedModalities: Set<Int>) = failedModalities.contains(TYPE_FACE)
-
override fun createIconController(): AuthIconController =
AuthBiometricFingerprintAndFaceIconController(mContext, mIconView, mIconViewOverlay)
+ override fun isCoex() = true
+
private fun isLockoutErrorString(unsuccessfulReason: String) =
unsuccessfulReason == FaceManager.getErrorString(
mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index f04fdfff67f1..9807b9e6f1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -122,7 +122,9 @@ open class AuthBiometricFingerprintIconController(
if (shouldAnimateIconViewForTransition(lastState, newState)) {
iconView.playAnimation()
}
- LottieColorUtils.applyDynamicColors(context, iconView)
+ if (isSideFps) {
+ LottieColorUtils.applyDynamicColors(context, iconView)
+ }
}
override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 4db371bed517..fb160f2a2f00 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -56,43 +56,42 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
/**
* Contains the Biometric views (title, subtitle, icon, buttons, etc.) and its controllers.
*/
-public abstract class AuthBiometricView extends LinearLayout {
+public abstract class AuthBiometricView extends LinearLayout implements AuthBiometricViewAdapter {
private static final String TAG = "AuthBiometricView";
/**
* Authentication hardware idle.
*/
- protected static final int STATE_IDLE = 0;
+ public static final int STATE_IDLE = 0;
/**
* UI animating in, authentication hardware active.
*/
- protected static final int STATE_AUTHENTICATING_ANIMATING_IN = 1;
+ public static final int STATE_AUTHENTICATING_ANIMATING_IN = 1;
/**
* UI animated in, authentication hardware active.
*/
- protected static final int STATE_AUTHENTICATING = 2;
+ public static final int STATE_AUTHENTICATING = 2;
/**
* UI animated in, authentication hardware active.
*/
- protected static final int STATE_HELP = 3;
+ public static final int STATE_HELP = 3;
/**
* Hard error, e.g. ERROR_TIMEOUT. Authentication hardware idle.
*/
- protected static final int STATE_ERROR = 4;
+ public static final int STATE_ERROR = 4;
/**
* Authenticated, waiting for user confirmation. Authentication hardware idle.
*/
- protected static final int STATE_PENDING_CONFIRMATION = 5;
+ public static final int STATE_PENDING_CONFIRMATION = 5;
/**
* Authenticated, dialog animating away soon.
*/
- protected static final int STATE_AUTHENTICATED = 6;
+ public static final int STATE_AUTHENTICATED = 6;
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_IDLE, STATE_AUTHENTICATING_ANIMATING_IN, STATE_AUTHENTICATING, STATE_HELP,
@@ -102,13 +101,14 @@ public abstract class AuthBiometricView extends LinearLayout {
/**
* Callback to the parent when a user action has occurred.
*/
- interface Callback {
+ public interface Callback {
int ACTION_AUTHENTICATED = 1;
int ACTION_USER_CANCELED = 2;
int ACTION_BUTTON_NEGATIVE = 3;
int ACTION_BUTTON_TRY_AGAIN = 4;
int ACTION_ERROR = 5;
int ACTION_USE_DEVICE_CREDENTIAL = 6;
+ int ACTION_START_DELAYED_FINGERPRINT_SENSOR = 7;
/**
* When an action has occurred. The caller will only invoke this when the callback should
@@ -268,6 +268,27 @@ public abstract class AuthBiometricView extends LinearLayout {
/** Create the controller for managing the icons transitions during the prompt.*/
@NonNull
protected abstract AuthIconController createIconController();
+
+ @Override
+ public AuthIconController getLegacyIconController() {
+ return mIconController;
+ }
+
+ @Override
+ public void cancelAnimation() {
+ animate().cancel();
+ }
+
+ @Override
+ public View asView() {
+ return this;
+ }
+
+ @Override
+ public boolean isCoex() {
+ return false;
+ }
+
void setPanelController(AuthPanelController panelController) {
mPanelController = panelController;
}
@@ -544,12 +565,12 @@ public abstract class AuthBiometricView extends LinearLayout {
mState = newState;
}
- void onOrientationChanged() {
+ public void onOrientationChanged() {
// Update padding and AuthPanel outline by calling updateSize when the orientation changed.
updateSize(mSize);
}
- public void onDialogAnimatedIn() {
+ public void onDialogAnimatedIn(boolean fingerprintWasStarted) {
updateState(STATE_AUTHENTICATING);
}
@@ -597,18 +618,6 @@ public abstract class AuthBiometricView extends LinearLayout {
}
/**
- * Fingerprint pointer down event. This does nothing by default and will not be called if the
- * device does not have an appropriate sensor (UDFPS), but it may be used as an alternative
- * to the "retry" button when fingerprint is used with other modalities.
- *
- * @param failedModalities the set of modalities that have failed
- * @return true if a retry was initiated as a result of this event
- */
- public boolean onPointerDown(Set<Integer> failedModalities) {
- return false;
- }
-
- /**
* Show a help message to the user.
*
* @param modality sensor modality
@@ -752,7 +761,8 @@ public abstract class AuthBiometricView extends LinearLayout {
/**
* Kicks off the animation process and invokes the callback.
*/
- void startTransitionToCredentialUI() {
+ @Override
+ public void startTransitionToCredentialUI() {
updateSize(AuthDialog.SIZE_LARGE);
mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
new file mode 100644
index 000000000000..631511c231e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.hardware.biometrics.BiometricAuthenticator
+import android.os.Bundle
+import android.view.View
+
+/** TODO(b/251476085): Temporary interface while legacy biometric prompt is around. */
+@Deprecated("temporary adapter while migrating biometric prompt - do not expand")
+interface AuthBiometricViewAdapter {
+ val legacyIconController: AuthIconController?
+
+ fun onDialogAnimatedIn(fingerprintWasStarted: Boolean)
+
+ fun onAuthenticationSucceeded(@BiometricAuthenticator.Modality modality: Int)
+
+ fun onAuthenticationFailed(
+ @BiometricAuthenticator.Modality modality: Int,
+ failureReason: String
+ )
+
+ fun onError(@BiometricAuthenticator.Modality modality: Int, error: String)
+
+ fun onHelp(@BiometricAuthenticator.Modality modality: Int, help: String)
+
+ fun startTransitionToCredentialUI()
+
+ fun requestLayout()
+
+ fun onSaveState(bundle: Bundle?)
+
+ fun restoreState(bundle: Bundle?)
+
+ fun onOrientationChanged()
+
+ fun cancelAnimation()
+
+ fun isCoex(): Boolean
+
+ fun asView(): View
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index e775c2e2a3fa..49ac26411d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -16,15 +16,13 @@
package com.android.systemui.biometrics;
-import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
-import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
import android.animation.Animator;
-import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -66,12 +64,19 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
-import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
+import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
+import com.android.systemui.biometrics.domain.model.BiometricModalities;
+import com.android.systemui.biometrics.ui.BiometricPromptLayout;
import com.android.systemui.biometrics.ui.CredentialView;
import com.android.systemui.biometrics.ui.binder.AuthBiometricFingerprintViewBinder;
+import com.android.systemui.biometrics.ui.binder.BiometricViewBinder;
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -84,6 +89,8 @@ import java.util.Set;
import javax.inject.Provider;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* Top level container/controller for the BiometricPrompt UI.
*/
@@ -126,16 +133,20 @@ public class AuthContainerView extends LinearLayout
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final AuthDialogPanelInteractionDetector mPanelInteractionDetector;
private final InteractionJankMonitor mInteractionJankMonitor;
+ private final CoroutineScope mApplicationCoroutineScope;
// TODO: these should be migrated out once ready
- private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
+ private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor;
private final Provider<AuthBiometricFingerprintViewModel>
mAuthBiometricFingerprintViewModelProvider;
+ private final @NonNull Provider<PromptSelectorInteractor> mPromptSelectorInteractorProvider;
+ // TODO(b/251476085): these should be migrated out of the view
private final Provider<CredentialViewModel> mCredentialViewModelProvider;
+ private final PromptViewModel mPromptViewModel;
@VisibleForTesting final BiometricCallback mBiometricCallback;
- @Nullable private AuthBiometricView mBiometricView;
+ @Nullable private AuthBiometricViewAdapter mBiometricView;
@Nullable private View mCredentialView;
private final AuthPanelController mPanelController;
private final FrameLayout mFrameLayout;
@@ -154,7 +165,8 @@ public class AuthContainerView extends LinearLayout
// HAT received from LockSettingsService when credential is verified.
@Nullable private byte[] mCredentialAttestation;
- @VisibleForTesting
+ // TODO(b/251476085): remove when legacy prompt is replaced
+ @Deprecated
static class Config {
Context mContext;
AuthDialogCallback mCallback;
@@ -167,96 +179,9 @@ public class AuthContainerView extends LinearLayout
long mOperationId;
long mRequestId = -1;
boolean mSkipAnimation = false;
- @BiometricMultiSensorMode int mMultiSensorConfig = BIOMETRIC_MULTI_SENSOR_DEFAULT;
ScaleFactorProvider mScaleProvider;
}
- public static class Builder {
- Config mConfig;
-
- public Builder(Context context) {
- mConfig = new Config();
- mConfig.mContext = context;
- }
-
- public Builder setCallback(AuthDialogCallback callback) {
- mConfig.mCallback = callback;
- return this;
- }
-
- public Builder setPromptInfo(PromptInfo promptInfo) {
- mConfig.mPromptInfo = promptInfo;
- return this;
- }
-
- public Builder setRequireConfirmation(boolean requireConfirmation) {
- mConfig.mRequireConfirmation = requireConfirmation;
- return this;
- }
-
- public Builder setUserId(int userId) {
- mConfig.mUserId = userId;
- return this;
- }
-
- public Builder setOpPackageName(String opPackageName) {
- mConfig.mOpPackageName = opPackageName;
- return this;
- }
-
- public Builder setSkipIntro(boolean skip) {
- mConfig.mSkipIntro = skip;
- return this;
- }
-
- public Builder setOperationId(@DurationMillisLong long operationId) {
- mConfig.mOperationId = operationId;
- return this;
- }
-
- /** Unique id for this request. */
- public Builder setRequestId(long requestId) {
- mConfig.mRequestId = requestId;
- return this;
- }
-
- @VisibleForTesting
- public Builder setSkipAnimationDuration(boolean skip) {
- mConfig.mSkipAnimation = skip;
- return this;
- }
-
- /** The multi-sensor mode. */
- public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) {
- mConfig.mMultiSensorConfig = multiSensorConfig;
- return this;
- }
-
- public Builder setScaleFactorProvider(ScaleFactorProvider scaleProvider) {
- mConfig.mScaleProvider = scaleProvider;
- return this;
- }
-
- public AuthContainerView build(@Background DelayableExecutor bgExecutor, int[] sensorIds,
- @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
- @Nullable List<FaceSensorPropertiesInternal> faceProps,
- @NonNull WakefulnessLifecycle wakefulnessLifecycle,
- @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
- @NonNull UserManager userManager,
- @NonNull LockPatternUtils lockPatternUtils,
- @NonNull InteractionJankMonitor jankMonitor,
- @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
- @NonNull Provider<AuthBiometricFingerprintViewModel>
- authBiometricFingerprintViewModelProvider,
- @NonNull Provider<CredentialViewModel> credentialViewModelProvider) {
- mConfig.mSensorIds = sensorIds;
- return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
- panelInteractionDetector, userManager, lockPatternUtils, jankMonitor,
- biometricPromptInteractor, authBiometricFingerprintViewModelProvider,
- credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor);
- }
- }
-
@VisibleForTesting
final class BiometricCallback implements AuthBiometricView.Callback {
@Override
@@ -285,6 +210,9 @@ public class AuthContainerView extends LinearLayout
addCredentialView(false /* animatePanel */, true /* animateContents */);
}, mConfig.mSkipAnimation ? 0 : AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS);
break;
+ case AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR:
+ mConfig.mCallback.onStartFingerprintNow(getRequestId());
+ break;
default:
Log.e(TAG, "Unhandled action: " + action);
}
@@ -336,8 +264,35 @@ public class AuthContainerView extends LinearLayout
alertDialog.show();
}
+ // TODO(b/251476085): remove Config and further decompose these properties out of view classes
+ AuthContainerView(@NonNull Config config,
+ @NonNull FeatureFlags featureFlags,
+ @NonNull CoroutineScope applicationCoroutineScope,
+ @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
+ @Nullable List<FaceSensorPropertiesInternal> faceProps,
+ @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
+ @NonNull UserManager userManager,
+ @NonNull LockPatternUtils lockPatternUtils,
+ @NonNull InteractionJankMonitor jankMonitor,
+ @NonNull Provider<AuthBiometricFingerprintViewModel>
+ authBiometricFingerprintViewModelProvider,
+ @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractor,
+ @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor,
+ @NonNull PromptViewModel promptViewModel,
+ @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
+ @NonNull @Background DelayableExecutor bgExecutor) {
+ this(config, featureFlags, applicationCoroutineScope, fpProps, faceProps,
+ wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
+ jankMonitor, authBiometricFingerprintViewModelProvider, promptSelectorInteractor,
+ promptCredentialInteractor, promptViewModel, credentialViewModelProvider,
+ new Handler(Looper.getMainLooper()), bgExecutor);
+ }
+
@VisibleForTesting
- AuthContainerView(Config config,
+ AuthContainerView(@NonNull Config config,
+ @NonNull FeatureFlags featureFlags,
+ @NonNull CoroutineScope applicationCoroutineScope,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@Nullable List<FaceSensorPropertiesInternal> faceProps,
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
@@ -345,9 +300,11 @@ public class AuthContainerView extends LinearLayout
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils,
@NonNull InteractionJankMonitor jankMonitor,
- @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
@NonNull Provider<AuthBiometricFingerprintViewModel>
authBiometricFingerprintViewModelProvider,
+ @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider,
+ @NonNull Provider<PromptCredentialInteractor> credentialInteractor,
+ @NonNull PromptViewModel promptViewModel,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull Handler mainHandler,
@NonNull @Background DelayableExecutor bgExecutor) {
@@ -360,6 +317,7 @@ public class AuthContainerView extends LinearLayout
mWindowManager = mContext.getSystemService(WindowManager.class);
mWakefulnessLifecycle = wakefulnessLifecycle;
mPanelInteractionDetector = panelInteractionDetector;
+ mApplicationCoroutineScope = applicationCoroutineScope;
mTranslationY = getResources()
.getDimension(R.dimen.biometric_dialog_animation_translation_offset);
@@ -376,10 +334,70 @@ public class AuthContainerView extends LinearLayout
mPanelController = new AuthPanelController(mContext, mPanelView);
mBackgroundExecutor = bgExecutor;
mInteractionJankMonitor = jankMonitor;
- mBiometricPromptInteractor = biometricPromptInteractor;
+ mPromptCredentialInteractor = credentialInteractor;
mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
+ mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
mCredentialViewModelProvider = credentialViewModelProvider;
+ mPromptViewModel = promptViewModel;
+
+ if (featureFlags.isEnabled(Flags.BIOMETRIC_BP_STRONG)) {
+ showPrompt(config, layoutInflater, promptViewModel,
+ Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
+ Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
+ } else {
+ showLegacyPrompt(config, layoutInflater, fpProps, faceProps);
+ }
+
+ // TODO: De-dupe the logic with AuthCredentialPasswordView
+ setOnKeyListener((v, keyCode, event) -> {
+ if (keyCode != KeyEvent.KEYCODE_BACK) {
+ return false;
+ }
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ onBackInvoked();
+ }
+ return true;
+ });
+
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ setFocusableInTouchMode(true);
+ requestFocus();
+ }
+
+ private void showPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater,
+ @NonNull PromptViewModel viewModel,
+ @Nullable FingerprintSensorPropertiesInternal fpProps,
+ @Nullable FaceSensorPropertiesInternal faceProps) {
+ if (Utils.isBiometricAllowed(config.mPromptInfo)) {
+ mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
+ config.mPromptInfo,
+ config.mRequireConfirmation,
+ config.mUserId,
+ config.mOperationId,
+ new BiometricModalities(fpProps, faceProps));
+
+ final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
+ R.layout.biometric_prompt_layout, null, false);
+ mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
+ // TODO(b/201510778): This uses the wrong timeout in some cases
+ getJankListener(view, TRANSIT, AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
+ mBackgroundView, mBiometricCallback, mApplicationCoroutineScope);
+
+ // TODO(b/251476085): migrate these dependencies
+ if (fpProps != null && fpProps.isAnyUdfpsType()) {
+ view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps),
+ config.mScaleProvider);
+ }
+ } else {
+ mPromptSelectorInteractorProvider.get().resetPrompt();
+ }
+ }
+ // TODO(b/251476085): remove entirely
+ private void showLegacyPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater,
+ @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
+ @Nullable List<FaceSensorPropertiesInternal> faceProps
+ ) {
// Inflate biometric view only if necessary.
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
final FingerprintSensorPropertiesInternal fpProperties =
@@ -421,31 +439,18 @@ public class AuthContainerView extends LinearLayout
// init view before showing
if (mBiometricView != null) {
- mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
- mBiometricView.setPanelController(mPanelController);
- mBiometricView.setPromptInfo(mConfig.mPromptInfo);
- mBiometricView.setCallback(mBiometricCallback);
- mBiometricView.setBackgroundView(mBackgroundView);
- mBiometricView.setUserId(mConfig.mUserId);
- mBiometricView.setEffectiveUserId(mEffectiveUserId);
- mBiometricView.setJankListener(getJankListener(mBiometricView, TRANSIT,
+ final AuthBiometricView view = (AuthBiometricView) mBiometricView;
+ view.setRequireConfirmation(mConfig.mRequireConfirmation);
+ view.setPanelController(mPanelController);
+ view.setPromptInfo(mConfig.mPromptInfo);
+ view.setCallback(mBiometricCallback);
+ view.setBackgroundView(mBackgroundView);
+ view.setUserId(mConfig.mUserId);
+ view.setEffectiveUserId(mEffectiveUserId);
+ // TODO(b/201510778): This uses the wrong timeout in some cases (remove w/ above)
+ view.setJankListener(getJankListener(view, TRANSIT,
AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS));
}
-
- // TODO: De-dupe the logic with AuthCredentialPasswordView
- setOnKeyListener((v, keyCode, event) -> {
- if (keyCode != KeyEvent.KEYCODE_BACK) {
- return false;
- }
- if (event.getAction() == KeyEvent.ACTION_UP) {
- onBackInvoked();
- }
- return true;
- });
-
- setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- setFocusableInTouchMode(true);
- requestFocus();
}
private void onBackInvoked() {
@@ -495,7 +500,7 @@ public class AuthContainerView extends LinearLayout
mBackgroundView.setOnClickListener(null);
mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mBiometricPromptInteractor.get().useCredentialsForAuthentication(
+ mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication(
mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId);
final CredentialViewModel vm = mCredentialViewModelProvider.get();
vm.setAnimateContents(animateContents);
@@ -527,7 +532,7 @@ public class AuthContainerView extends LinearLayout
() -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
- mBiometricScrollView.addView(mBiometricView);
+ mBiometricScrollView.addView(mBiometricView.asView());
} else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
addCredentialView(true /* animatePanel */, false /* animateContents */);
} else {
@@ -601,9 +606,13 @@ public class AuthContainerView extends LinearLayout
}
private static boolean shouldUpdatePositionForUdfps(@NonNull View view) {
+ // TODO(b/251476085): legacy view (delete when removed)
if (view instanceof AuthBiometricFingerprintView) {
return ((AuthBiometricFingerprintView) view).isUdfps();
}
+ if (view instanceof BiometricPromptLayout) {
+ return ((BiometricPromptLayout) view).isUdfps();
+ }
return false;
}
@@ -613,7 +622,7 @@ public class AuthContainerView extends LinearLayout
if (display == null) {
return false;
}
- if (!shouldUpdatePositionForUdfps(mBiometricView)) {
+ if (mBiometricView == null || !shouldUpdatePositionForUdfps(mBiometricView.asView())) {
return false;
}
@@ -626,12 +635,12 @@ public class AuthContainerView extends LinearLayout
case Surface.ROTATION_90:
mPanelController.setPosition(AuthPanelController.POSITION_RIGHT);
- setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ setScrollViewGravity(Gravity.BOTTOM | Gravity.RIGHT);
break;
case Surface.ROTATION_270:
mPanelController.setPosition(AuthPanelController.POSITION_LEFT);
- setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ setScrollViewGravity(Gravity.BOTTOM | Gravity.LEFT);
break;
case Surface.ROTATION_180:
@@ -689,7 +698,7 @@ public class AuthContainerView extends LinearLayout
mCredentialView.animate().cancel();
}
mPanelView.animate().cancel();
- mBiometricView.animate().cancel();
+ mBiometricView.cancelAnimation();
animate().cancel();
onDialogAnimatedIn();
}
@@ -750,8 +759,9 @@ public class AuthContainerView extends LinearLayout
@Override
public void onPointerDown() {
if (mBiometricView != null) {
- if (mBiometricView.onPointerDown(mFailedModalities)) {
+ if (mFailedModalities.contains(TYPE_FACE)) {
Log.d(TAG, "retrying failed modalities (pointer down)");
+ mFailedModalities.remove(TYPE_FACE);
mBiometricCallback.onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
}
} else {
@@ -885,11 +895,17 @@ public class AuthContainerView extends LinearLayout
}
mContainerState = STATE_SHOWING;
if (mBiometricView != null) {
- mConfig.mCallback.onDialogAnimatedIn(getRequestId());
- mBiometricView.onDialogAnimatedIn();
+ final boolean delayFingerprint = mBiometricView.isCoex() && !mConfig.mRequireConfirmation;
+ mConfig.mCallback.onDialogAnimatedIn(getRequestId(), !delayFingerprint);
+ mBiometricView.onDialogAnimatedIn(!delayFingerprint);
}
}
+ @Override
+ public PromptViewModel getViewModel() {
+ return mPromptViewModel;
+ }
+
@VisibleForTesting
static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) {
final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
@@ -922,26 +938,5 @@ public class AuthContainerView extends LinearLayout
if (mConfig != null) {
pw.println(" config.sensorIds exist=" + (mConfig.mSensorIds != null));
}
- final AuthBiometricView biometricView = mBiometricView;
- pw.println(" scrollView=" + findViewById(R.id.biometric_scrollview));
- pw.println(" biometricView=" + biometricView);
- if (biometricView != null) {
- int[] ids = {
- R.id.title,
- R.id.subtitle,
- R.id.description,
- R.id.biometric_icon_frame,
- R.id.biometric_icon,
- R.id.indicator,
- R.id.button_bar,
- R.id.button_negative,
- R.id.button_use_credential,
- R.id.button_confirm,
- R.id.button_try_again
- };
- for (final int id: ids) {
- pw.println(" " + biometricView.findViewById(id));
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index fd9cee0d6144..57f1928fe545 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -37,7 +37,6 @@ import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
-import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.IBiometricContextListener;
@@ -71,14 +70,18 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.udfps.UdfpsOverlayParams;
import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.CoreStartable;
-import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
+import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.data.repository.BiometricType;
import com.android.systemui.statusbar.CommandQueue;
@@ -86,8 +89,6 @@ import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
-import kotlin.Unit;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -101,6 +102,9 @@ import java.util.Set;
import javax.inject.Inject;
import javax.inject.Provider;
+import kotlin.Unit;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
* appropriate biometric UI (e.g. BiometricDialogView).
@@ -109,7 +113,7 @@ import javax.inject.Provider;
* {@link com.android.keyguard.KeyguardUpdateMonitor}
*/
@SysUISingleton
-public class AuthController implements CoreStartable, CommandQueue.Callbacks,
+public class AuthController implements CoreStartable, CommandQueue.Callbacks,
AuthDialogCallback, DozeReceiver {
private static final String TAG = "AuthController";
@@ -118,6 +122,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
private final Handler mHandler;
private final Context mContext;
+ private final FeatureFlags mFeatureFlags;
private final Execution mExecution;
private final CommandQueue mCommandQueue;
private final ActivityTaskManager mActivityTaskManager;
@@ -125,13 +130,15 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@Nullable private final FaceManager mFaceManager;
private final Provider<UdfpsController> mUdfpsControllerFactory;
private final Provider<SideFpsController> mSidefpsControllerFactory;
+ private final CoroutineScope mApplicationCoroutineScope;
// TODO: these should be migrated out once ready
- @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
-
@NonNull private final Provider<AuthBiometricFingerprintViewModel>
mAuthBiometricFingerprintViewModelProvider;
+ @NonNull private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor;
+ @NonNull private final Provider<PromptSelectorInteractor> mPromptSelectorInteractor;
@NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider;
+ @NonNull private final Provider<PromptViewModel> mPromptViewModelProvider;
@NonNull private final LogContextInteractor mLogContextInteractor;
private final Display mDisplay;
@@ -461,7 +468,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
@Override
- public void onDialogAnimatedIn(long requestId) {
+ public void onDialogAnimatedIn(long requestId, boolean startFingerprintNow) {
final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
if (receiver == null) {
Log.w(TAG, "Skip onDialogAnimatedIn");
@@ -469,7 +476,22 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
try {
- receiver.onDialogAnimatedIn();
+ receiver.onDialogAnimatedIn(startFingerprintNow);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e);
+ }
+ }
+
+ @Override
+ public void onStartFingerprintNow(long requestId) {
+ final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+ if (receiver == null) {
+ Log.e(TAG, "onStartUdfpsNow: Receiver is null");
+ return;
+ }
+
+ try {
+ receiver.onStartFingerprintNow();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e);
}
@@ -728,6 +750,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
@Inject
public AuthController(Context context,
+ @NonNull FeatureFlags featureFlags,
+ @Application CoroutineScope applicationCoroutineScope,
Execution execution,
CommandQueue commandQueue,
ActivityTaskManager activityTaskManager,
@@ -743,16 +767,19 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@NonNull LockPatternUtils lockPatternUtils,
@NonNull UdfpsLogger udfpsLogger,
@NonNull LogContextInteractor logContextInteractor,
- @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
@NonNull Provider<AuthBiometricFingerprintViewModel>
authBiometricFingerprintViewModelProvider,
+ @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractorProvider,
+ @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
+ @NonNull Provider<PromptViewModel> promptViewModelProvider,
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@Background DelayableExecutor bgExecutor,
@NonNull VibratorHelper vibrator,
@NonNull UdfpsUtils udfpsUtils) {
mContext = context;
+ mFeatureFlags = featureFlags;
mExecution = execution;
mUserManager = userManager;
mLockPatternUtils = lockPatternUtils;
@@ -773,10 +800,13 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mFaceEnrolledForUser = new SparseBooleanArray();
mVibratorHelper = vibrator;
mUdfpsUtils = udfpsUtils;
+ mApplicationCoroutineScope = applicationCoroutineScope;
mLogContextInteractor = logContextInteractor;
- mBiometricPromptInteractor = biometricPromptInteractor;
mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
+ mPromptSelectorInteractor = promptSelectorInteractorProvider;
+ mPromptCredentialInteractor = promptCredentialInteractorProvider;
+ mPromptViewModelProvider = promptViewModelProvider;
mCredentialViewModelProvider = credentialViewModelProvider;
mOrientationListener = new BiometricDisplayListener(
@@ -913,8 +943,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, long operationId, String opPackageName, long requestId,
- @BiometricMultiSensorMode int multiSensorConfig) {
+ int userId, long operationId, String opPackageName, long requestId) {
@Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
if (DEBUG) {
@@ -927,8 +956,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
+ ", credentialAllowed: " + credentialAllowed
+ ", requireConfirmation: " + requireConfirmation
+ ", operationId: " + operationId
- + ", requestId: " + requestId
- + ", multiSensorConfig: " + multiSensorConfig);
+ + ", requestId: " + requestId);
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = promptInfo;
@@ -940,7 +968,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
args.arg6 = opPackageName;
args.argl1 = operationId;
args.argl2 = requestId;
- args.argi2 = multiSensorConfig;
boolean skipAnimation = false;
if (mCurrentDialog != null) {
@@ -948,7 +975,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
skipAnimation = true;
}
- showDialog(args, skipAnimation, null /* savedState */);
+ showDialog(args, skipAnimation, null /* savedState */, mPromptViewModelProvider.get());
}
/**
@@ -1171,7 +1198,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
return mFpEnrolledForUser.getOrDefault(userId, false);
}
- private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
+ private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState,
+ @Nullable PromptViewModel viewModel) {
mCurrentDialogArgs = args;
final PromptInfo promptInfo = (PromptInfo) args.arg1;
@@ -1182,7 +1210,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
final String opPackageName = (String) args.arg6;
final long operationId = args.argl1;
final long requestId = args.argl2;
- @BiometricMultiSensorMode final int multiSensorConfig = args.argi2;
// Create a new dialog but do not replace the current one yet.
final AuthDialog newDialog = buildDialog(
@@ -1195,11 +1222,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
skipAnimation,
operationId,
requestId,
- multiSensorConfig,
mWakefulnessLifecycle,
mPanelInteractionDetector,
mUserManager,
- mLockPatternUtils);
+ mLockPatternUtils,
+ viewModel);
if (newDialog == null) {
Log.e(TAG, "Unsupported type configuration");
@@ -1253,6 +1280,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
// Save the state of the current dialog (buttons showing, etc)
if (mCurrentDialog != null) {
+ final PromptViewModel viewModel = mCurrentDialog.getViewModel();
final Bundle savedState = new Bundle();
mCurrentDialog.onSaveState(savedState);
mCurrentDialog.dismissWithoutCallback(false /* animate */);
@@ -1271,7 +1299,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
}
- showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
+ showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState, viewModel);
}
}
}
@@ -1286,26 +1314,28 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
protected AuthDialog buildDialog(@Background DelayableExecutor bgExecutor,
PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds,
String opPackageName, boolean skipIntro, long operationId, long requestId,
- @BiometricMultiSensorMode int multiSensorConfig,
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
@NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
@NonNull UserManager userManager,
- @NonNull LockPatternUtils lockPatternUtils) {
- return new AuthContainerView.Builder(mContext)
- .setCallback(this)
- .setPromptInfo(promptInfo)
- .setRequireConfirmation(requireConfirmation)
- .setUserId(userId)
- .setOpPackageName(opPackageName)
- .setSkipIntro(skipIntro)
- .setOperationId(operationId)
- .setRequestId(requestId)
- .setMultiSensorConfig(multiSensorConfig)
- .setScaleFactorProvider(() -> getScaleFactor())
- .build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle,
- panelInteractionDetector, userManager, lockPatternUtils,
- mInteractionJankMonitor, mBiometricPromptInteractor,
- mAuthBiometricFingerprintViewModelProvider, mCredentialViewModelProvider);
+ @NonNull LockPatternUtils lockPatternUtils,
+ @NonNull PromptViewModel viewModel) {
+ final AuthContainerView.Config config = new AuthContainerView.Config();
+ config.mContext = mContext;
+ config.mCallback = this;
+ config.mPromptInfo = promptInfo;
+ config.mRequireConfirmation = requireConfirmation;
+ config.mUserId = userId;
+ config.mOpPackageName = opPackageName;
+ config.mSkipIntro = skipIntro;
+ config.mOperationId = operationId;
+ config.mRequestId = requestId;
+ config.mSensorIds = sensorIds;
+ config.mScaleProvider = this::getScaleFactor;
+ return new AuthContainerView(config, mFeatureFlags, mApplicationCoroutineScope, mFpProps, mFaceProps,
+ wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
+ mInteractionJankMonitor, mAuthBiometricFingerprintViewModelProvider,
+ mPromptCredentialInteractor, mPromptSelectorInteractor, viewModel,
+ mCredentialViewModelProvider, bgExecutor);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index 51f39b358659..b6eabfa76e36 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -24,13 +24,17 @@ import android.os.Bundle;
import android.view.WindowManager;
import com.android.systemui.Dumpable;
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Interface for the biometric dialog UI.
+ *
+ * TODO(b/251476085): remove along with legacy controller once flag is removed
*/
+@Deprecated
public interface AuthDialog extends Dumpable {
String KEY_CONTAINER_GOING_AWAY = "container_going_away";
@@ -70,10 +74,10 @@ public interface AuthDialog extends Dumpable {
* {@link AuthPanelController}.
*/
class LayoutParams {
- final int mMediumHeight;
- final int mMediumWidth;
+ public final int mMediumHeight;
+ public final int mMediumWidth;
- LayoutParams(int mediumWidth, int mediumHeight) {
+ public LayoutParams(int mediumWidth, int mediumHeight) {
mMediumWidth = mediumWidth;
mMediumHeight = mediumHeight;
}
@@ -172,4 +176,6 @@ public interface AuthDialog extends Dumpable {
* must remain fixed on the physical sensor location.
*/
void onOrientationChanged();
+
+ PromptViewModel getViewModel();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
index bbe461aaf6d9..9a2194025a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
@@ -70,5 +70,10 @@ public interface AuthDialogCallback {
/**
* Notifies when the dialog has finished animating.
*/
- void onDialogAnimatedIn(long requestId);
+ void onDialogAnimatedIn(long requestId, boolean startFingerprintNow);
+
+ /**
+ * Notifies that the fingerprint sensor should be started now.
+ */
+ void onStartFingerprintNow(long requestId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
index f5f46405660f..f56bb881d8b7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
@@ -84,9 +84,6 @@ abstract class AuthIconController(
}
}
- /** If the icon should act as a "retry" button in the [currentState]. */
- fun iconTapSendsRetryWhen(@BiometricState currentState: Int): Boolean = false
-
/** Call during [updateState] if the controller is not [deactivated]. */
abstract fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
index ad100716eceb..acdde3404ab5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -114,16 +114,7 @@ public class AuthPanelController extends ViewOutlineProvider {
}
private int getTopBound(@Position int position) {
- switch (position) {
- case POSITION_BOTTOM:
- return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin);
- case POSITION_LEFT:
- case POSITION_RIGHT:
- return Math.max((mContainerHeight - mContentHeight) / 2, mMargin);
- default:
- Log.e(TAG, "Unrecognized position: " + position);
- return getTopBound(POSITION_BOTTOM);
- }
+ return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin);
}
public void setContainerDimensions(int containerWidth, int containerHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index 43745bf74aae..16dc42acde46 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -63,7 +63,7 @@ public class UdfpsDialogMeasureAdapter {
}
@NonNull
- AuthDialog.LayoutParams onMeasureInternal(
+ public AuthDialog.LayoutParams onMeasureInternal(
int width, int height, @NonNull AuthDialog.LayoutParams layoutParams,
float scaleFactor) {
@@ -86,7 +86,7 @@ public class UdfpsDialogMeasureAdapter {
* too cleanly support this case. So, let's have the onLayout code translate the sensor location
* instead.
*/
- int getBottomSpacerHeight() {
+ public int getBottomSpacerHeight() {
return mBottomSpacerHeight;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 096d94144480..ddf1457e385c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -31,6 +31,8 @@ import com.android.systemui.biometrics.domain.interactor.LogContextInteractor
import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.concurrency.ThreadFactory
import dagger.Binds
@@ -57,6 +59,11 @@ interface BiometricsModule {
@Binds
@SysUISingleton
+ fun providesPromptSelectorInteractor(impl: PromptSelectorInteractorImpl):
+ PromptSelectorInteractor
+
+ @Binds
+ @SysUISingleton
fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index 92a13cfe538b..b4dc272b71da 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -2,7 +2,7 @@ package com.android.systemui.biometrics.data.repository
import android.hardware.biometrics.PromptInfo
import com.android.systemui.biometrics.AuthController
-import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -35,12 +35,20 @@ interface PromptRepository {
/** The kind of credential to use (biometric, pin, pattern, etc.). */
val kind: StateFlow<PromptKind>
+ /**
+ * If explicit confirmation is required.
+ *
+ * Note: overlaps/conflicts with [PromptInfo.isConfirmationRequested], which needs clean up.
+ */
+ val isConfirmationRequired: StateFlow<Boolean>
+
/** Update the prompt configuration, which should be set before [isShowing]. */
fun setPrompt(
promptInfo: PromptInfo,
userId: Int,
gatekeeperChallenge: Long?,
- kind: PromptKind = PromptKind.ANY_BIOMETRIC,
+ kind: PromptKind,
+ requireConfirmation: Boolean = false,
)
/** Unset the prompt info. */
@@ -74,29 +82,35 @@ class PromptRepositoryImpl @Inject constructor(private val authController: AuthC
private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null)
override val userId = _userId.asStateFlow()
- private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.ANY_BIOMETRIC)
+ private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
override val kind = _kind.asStateFlow()
+ private val _isConfirmationRequired: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
+
override fun setPrompt(
promptInfo: PromptInfo,
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
+ requireConfirmation: Boolean,
) {
_kind.value = kind
_userId.value = userId
_challenge.value = gatekeeperChallenge
_promptInfo.value = promptInfo
+ _isConfirmationRequired.value = requireConfirmation
}
override fun unsetPrompt() {
_promptInfo.value = null
_userId.value = null
_challenge.value = null
- _kind.value = PromptKind.ANY_BIOMETRIC
+ _kind.value = PromptKind.Biometric()
+ _isConfirmationRequired.value = false
}
companion object {
- private const val TAG = "BiometricPromptRepository"
+ private const val TAG = "PromptRepositoryImpl"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index 6362c2f627d3..d92c2178046e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -1,14 +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.biometrics.domain.interactor
import android.hardware.biometrics.PromptInfo
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
import com.android.systemui.biometrics.Utils
-import com.android.systemui.biometrics.data.model.PromptKind
import com.android.systemui.biometrics.data.repository.PromptRepository
import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -24,8 +40,16 @@ import kotlinx.coroutines.withContext
/**
* Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users
* PIN, pattern, or password credential instead of a biometric.
+ *
+ * This is used to cache the calling app's options that were given to the underlying authenticate
+ * APIs and should be set before any UI is shown to the user.
+ *
+ * There can be at most one request active at a given time. Use [resetPrompt] when no request is
+ * active to clear the cache.
+ *
+ * Views that use any biometric should use [PromptSelectorInteractor] instead.
*/
-class BiometricPromptCredentialInteractor
+class PromptCredentialInteractor
@Inject
constructor(
@Background private val bgDispatcher: CoroutineDispatcher,
@@ -36,7 +60,7 @@ constructor(
val isShowing: Flow<Boolean> = biometricPromptRepository.isShowing
/** Metadata about the current credential prompt, including app-supplied preferences. */
- val prompt: Flow<BiometricPromptRequest?> =
+ val prompt: Flow<BiometricPromptRequest.Credential?> =
combine(
biometricPromptRepository.promptInfo,
biometricPromptRepository.challenge,
@@ -48,20 +72,20 @@ constructor(
}
when (kind) {
- PromptKind.PIN ->
+ PromptKind.Pin ->
BiometricPromptRequest.Credential.Pin(
info = promptInfo,
userInfo = userInfo(userId),
operationInfo = operationInfo(challenge)
)
- PromptKind.PATTERN ->
+ PromptKind.Pattern ->
BiometricPromptRequest.Credential.Pattern(
info = promptInfo,
userInfo = userInfo(userId),
operationInfo = operationInfo(challenge),
stealthMode = credentialInteractor.isStealthModeActive(userId)
)
- PromptKind.PASSWORD ->
+ PromptKind.Password ->
BiometricPromptRequest.Credential.Password(
info = promptInfo,
userInfo = userInfo(userId),
@@ -182,8 +206,8 @@ constructor(
/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
when (this) {
- Utils.CREDENTIAL_PIN -> PromptKind.PIN
- Utils.CREDENTIAL_PASSWORD -> PromptKind.PASSWORD
- Utils.CREDENTIAL_PATTERN -> PromptKind.PATTERN
- else -> PromptKind.ANY_BIOMETRIC
+ Utils.CREDENTIAL_PIN -> PromptKind.Pin
+ Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
+ Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
+ else -> PromptKind.Biometric()
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
new file mode 100644
index 000000000000..e6e07f9d7794
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -0,0 +1,186 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.hardware.biometrics.PromptInfo
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.Utils.getCredentialType
+import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
+import com.android.systemui.biometrics.data.repository.PromptRepository
+import com.android.systemui.biometrics.domain.model.BiometricModalities
+import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/**
+ * Business logic for BiometricPrompt's biometric view variants (face, fingerprint, coex, etc.).
+ *
+ * This is used to cache the calling app's options that were given to the underlying authenticate
+ * APIs and should be set before any UI is shown to the user.
+ *
+ * There can be at most one request active at a given time. Use [resetPrompt] when no request is
+ * active to clear the cache.
+ *
+ * Views that use credential fallback should use [PromptCredentialInteractor] instead.
+ */
+interface PromptSelectorInteractor {
+
+ /** Static metadata about the current prompt. */
+ val prompt: Flow<BiometricPromptRequest.Biometric?>
+
+ /** If using a credential is allowed. */
+ val isCredentialAllowed: Flow<Boolean>
+
+ /**
+ * The kind of credential the user may use as a fallback or [PromptKind.Biometric] if unknown or
+ * not [isCredentialAllowed].
+ */
+ val credentialKind: Flow<PromptKind>
+
+ /** If the API caller requested explicit confirmation after successful authentication. */
+ val isConfirmationRequested: Flow<Boolean>
+
+ /** Use biometrics for authentication. */
+ fun useBiometricsForAuthentication(
+ promptInfo: PromptInfo,
+ requireConfirmation: Boolean,
+ userId: Int,
+ challenge: Long,
+ modalities: BiometricModalities,
+ )
+
+ /** Use credential-based authentication instead of biometrics. */
+ fun useCredentialsForAuthentication(
+ promptInfo: PromptInfo,
+ @Utils.CredentialType kind: Int,
+ userId: Int,
+ challenge: Long,
+ )
+
+ /** Unset the current authentication request. */
+ fun resetPrompt()
+}
+
+@SysUISingleton
+class PromptSelectorInteractorImpl
+@Inject
+constructor(
+ private val promptRepository: PromptRepository,
+ lockPatternUtils: LockPatternUtils,
+) : PromptSelectorInteractor {
+
+ override val prompt: Flow<BiometricPromptRequest.Biometric?> =
+ combine(
+ promptRepository.promptInfo,
+ promptRepository.challenge,
+ promptRepository.userId,
+ promptRepository.kind
+ ) { promptInfo, challenge, userId, kind ->
+ if (promptInfo == null || userId == null || challenge == null) {
+ return@combine null
+ }
+
+ when (kind) {
+ is PromptKind.Biometric ->
+ BiometricPromptRequest.Biometric(
+ info = promptInfo,
+ userInfo = BiometricUserInfo(userId = userId),
+ operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge),
+ modalities = kind.activeModalities,
+ )
+ else -> null
+ }
+ }
+
+ override val isConfirmationRequested: Flow<Boolean> =
+ promptRepository.promptInfo
+ .map { info -> info?.isConfirmationRequested ?: false }
+ .distinctUntilChanged()
+
+ override val isCredentialAllowed: Flow<Boolean> =
+ promptRepository.promptInfo
+ .map { info -> if (info != null) isDeviceCredentialAllowed(info) else false }
+ .distinctUntilChanged()
+
+ override val credentialKind: Flow<PromptKind> =
+ combine(prompt, isCredentialAllowed) { prompt, isAllowed ->
+ if (prompt != null && isAllowed) {
+ when (
+ getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
+ ) {
+ Utils.CREDENTIAL_PIN -> PromptKind.Pin
+ Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
+ Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
+ else -> PromptKind.Biometric()
+ }
+ } else {
+ PromptKind.Biometric()
+ }
+ }
+
+ override fun useBiometricsForAuthentication(
+ promptInfo: PromptInfo,
+ requireConfirmation: Boolean,
+ userId: Int,
+ challenge: Long,
+ modalities: BiometricModalities
+ ) {
+ promptRepository.setPrompt(
+ promptInfo = promptInfo,
+ userId = userId,
+ gatekeeperChallenge = challenge,
+ kind = PromptKind.Biometric(modalities),
+ requireConfirmation = requireConfirmation,
+ )
+ }
+
+ override fun useCredentialsForAuthentication(
+ promptInfo: PromptInfo,
+ @Utils.CredentialType kind: Int,
+ userId: Int,
+ challenge: Long,
+ ) {
+ promptRepository.setPrompt(
+ promptInfo = promptInfo,
+ userId = userId,
+ gatekeeperChallenge = challenge,
+ kind = kind.asBiometricPromptCredential(),
+ )
+ }
+
+ override fun resetPrompt() {
+ promptRepository.unsetPrompt()
+ }
+}
+
+// TODO(b/251476085): remove along with Utils.CredentialType
+/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
+private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
+ when (this) {
+ Utils.CREDENTIAL_PIN -> PromptKind.Pin
+ Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
+ Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
+ else -> PromptKind.Biometric()
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModalities.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModalities.kt
new file mode 100644
index 000000000000..274f58a5266b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModalities.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.biometrics.domain.model
+
+import android.hardware.biometrics.SensorProperties
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+
+/** The available modalities for an operation. */
+data class BiometricModalities(
+ val fingerprintProperties: FingerprintSensorPropertiesInternal? = null,
+ val faceProperties: FaceSensorPropertiesInternal? = null,
+) {
+ /** If there are no available modalities. */
+ val isEmpty: Boolean
+ get() = !hasFingerprint && !hasFace
+
+ /** If fingerprint authentication is available (and [fingerprintProperties] is non-null). */
+ val hasFingerprint: Boolean
+ get() = fingerprintProperties != null
+
+ /** If fingerprint authentication is available (and [faceProperties] is non-null). */
+ val hasFace: Boolean
+ get() = faceProperties != null
+
+ /** If only face authentication is enabled. */
+ val hasFaceOnly: Boolean
+ get() = hasFace && !hasFingerprint
+
+ /** If only fingerprint authentication is enabled. */
+ val hasFingerprintOnly: Boolean
+ get() = hasFingerprint && !hasFace
+
+ /** If face & fingerprint authentication is enabled (coex). */
+ val hasFaceAndFingerprint: Boolean
+ get() = hasFingerprint && hasFace
+
+ /** If [hasFace] and it is configured as a STRONG class 3 biometric. */
+ val isFaceStrong: Boolean
+ get() = faceProperties?.sensorStrength == SensorProperties.STRENGTH_STRONG
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt
new file mode 100644
index 000000000000..3197c0935d0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.model
+
+import android.hardware.biometrics.BiometricAuthenticator
+
+/** Shadows [BiometricAuthenticator.Modality] for Kotlin use within SysUI. */
+enum class BiometricModality {
+ None,
+ Fingerprint,
+ Face,
+}
+
+/** Convert a framework [BiometricAuthenticator.Modality] to a SysUI [BiometricModality]. */
+@BiometricAuthenticator.Modality
+fun Int.asBiometricModality(): BiometricModality =
+ when (this) {
+ BiometricAuthenticator.TYPE_FINGERPRINT -> BiometricModality.Fingerprint
+ BiometricAuthenticator.TYPE_FACE -> BiometricModality.Face
+ else -> BiometricModality.None
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 5ee0381db630..75de47d12427 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -21,6 +21,7 @@ sealed class BiometricPromptRequest(
info: PromptInfo,
userInfo: BiometricUserInfo,
operationInfo: BiometricOperationInfo,
+ val modalities: BiometricModalities,
) :
BiometricPromptRequest(
title = info.title?.toString() ?: "",
@@ -28,7 +29,9 @@ sealed class BiometricPromptRequest(
description = info.description?.toString() ?: "",
userInfo = userInfo,
operationInfo = operationInfo
- )
+ ) {
+ val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
+ }
/** Prompt using a credential (pin, pattern, password). */
sealed class Credential(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
index e82646f0d861..416fc64e69a3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,19 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics.data.model
+package com.android.systemui.biometrics.shared.model
import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.domain.model.BiometricModalities
// TODO(b/251476085): this should eventually replace Utils.CredentialType
/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */
-enum class PromptKind {
- ANY_BIOMETRIC,
- PIN,
- PATTERN,
- PASSWORD,
+sealed interface PromptKind {
+ data class Biometric(
+ val activeModalities: BiometricModalities = BiometricModalities(),
+ ) : PromptKind
+
+ object Pin : PromptKind
+ object Pattern : PromptKind
+ object Password : PromptKind
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
new file mode 100644
index 000000000000..fb246cd51d8a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -0,0 +1,182 @@
+/*
+ * 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.biometrics.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Insets;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.biometrics.AuthBiometricFingerprintIconController;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthDialog;
+import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter;
+
+import kotlin.Pair;
+
+/**
+ * Contains the Biometric views (title, subtitle, icon, buttons, etc.).
+ *
+ * TODO(b/251476085): get the udfps junk out of here, at a minimum. Likely can be replaced with a
+ * normal LinearLayout.
+ */
+public class BiometricPromptLayout extends LinearLayout {
+
+ private static final String TAG = "BiometricPromptLayout";
+
+ @NonNull
+ private final WindowManager mWindowManager;
+ @Nullable
+ private AuthController.ScaleFactorProvider mScaleFactorProvider;
+ @Nullable
+ private UdfpsDialogMeasureAdapter mUdfpsAdapter;
+
+ private final boolean mUseCustomBpSize;
+ private final int mCustomBpWidth;
+ private final int mCustomBpHeight;
+
+ public BiometricPromptLayout(Context context) {
+ this(context, null);
+ }
+
+ public BiometricPromptLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mWindowManager = context.getSystemService(WindowManager.class);
+
+ mUseCustomBpSize = getResources().getBoolean(R.bool.use_custom_bp_size);
+ mCustomBpWidth = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_width);
+ mCustomBpHeight = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_height);
+ }
+
+ @Deprecated
+ public void setUdfpsAdapter(@NonNull UdfpsDialogMeasureAdapter adapter,
+ @NonNull AuthController.ScaleFactorProvider scaleProvider) {
+ mUdfpsAdapter = adapter;
+ mScaleFactorProvider = scaleProvider != null ? scaleProvider : () -> 1.0f;
+ }
+
+ @Deprecated
+ public boolean isUdfps() {
+ return mUdfpsAdapter != null;
+ }
+
+ @Deprecated
+ public void updateFingerprintAffordanceSize(
+ @NonNull AuthBiometricFingerprintIconController iconController) {
+ if (mUdfpsAdapter != null) {
+ final int sensorDiameter = mUdfpsAdapter.getSensorDiameter(
+ mScaleFactorProvider.provide());
+ iconController.setIconLayoutParamSize(new Pair(sensorDiameter, sensorDiameter));
+ }
+ }
+
+ @NonNull
+ private AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
+ int totalHeight = 0;
+ final int numChildren = getChildCount();
+ for (int i = 0; i < numChildren; i++) {
+ final View child = getChildAt(i);
+
+ if (child.getId() == R.id.space_above_icon
+ || child.getId() == R.id.space_below_icon
+ || child.getId() == R.id.button_bar) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+ MeasureSpec.EXACTLY));
+ } else if (child.getId() == R.id.biometric_icon_frame) {
+ final View iconView = findViewById(R.id.biometric_icon);
+ child.measure(
+ MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().width,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height,
+ MeasureSpec.EXACTLY));
+ } else if (child.getId() == R.id.biometric_icon) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+ } else {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+ }
+
+ if (child.getVisibility() != View.GONE) {
+ totalHeight += child.getMeasuredHeight();
+ }
+ }
+
+ final AuthDialog.LayoutParams params = new AuthDialog.LayoutParams(width, totalHeight);
+ if (mUdfpsAdapter != null) {
+ return mUdfpsAdapter.onMeasureInternal(width, height, params,
+ (mScaleFactorProvider != null) ? mScaleFactorProvider.provide() : 1.0f);
+ } else {
+ return params;
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (mUseCustomBpSize) {
+ width = mCustomBpWidth;
+ height = mCustomBpHeight;
+ } else {
+ width = Math.min(width, height);
+ }
+
+ // add nav bar insets since the parent AuthContainerView
+ // uses LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ final Insets insets = mWindowManager.getMaximumWindowMetrics().getWindowInsets()
+ .getInsets(WindowInsets.Type.navigationBars());
+ final AuthDialog.LayoutParams params = onMeasureInternal(width, height);
+ setMeasuredDimension(params.mMediumWidth + insets.left + insets.right,
+ params.mMediumHeight + insets.bottom);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mUdfpsAdapter != null) {
+ // Move the UDFPS icon and indicator text if necessary. This probably only needs to
+ // happen for devices where the UDFPS sensor is too low.
+ // TODO(b/201510778): Update this logic to support cases where the sensor or text
+ // overlap the button bar area.
+ final float bottomSpacerHeight = mUdfpsAdapter.getBottomSpacerHeight();
+ Log.w(TAG, "bottomSpacerHeight: " + bottomSpacerHeight);
+ if (bottomSpacerHeight < 0) {
+ final FrameLayout iconFrame = findViewById(R.id.biometric_icon_frame);
+ iconFrame.setTranslationY(-bottomSpacerHeight);
+ final TextView indicator = findViewById(R.id.indicator);
+ indicator.setTranslationY(-bottomSpacerHeight);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
index ede62acb3255..a3f34ce7471d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -68,15 +68,15 @@ class CredentialPasswordView(context: Context, attrs: AttributeSet?) :
var inputTopBound: Int
var headerRightBound = right
var headerTopBounds = top
+ var headerBottomBounds = bottom
val subTitleBottom: Int = if (subtitleView.isGone) titleView.bottom else subtitleView.bottom
val descBottom = if (descriptionView.isGone) subTitleBottom else descriptionView.bottom
if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
inputTopBound = (bottom - credentialInput.height) / 2
inputLeftBound = (right - left) / 2
headerRightBound = inputLeftBound
- headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset)
-
- if (descriptionView.bottom > bottomInset) {
+ if (descriptionView.bottom > headerBottomBounds) {
+ headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset)
credentialHeader.layout(left, headerTopBounds, headerRightBound, bottom)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
new file mode 100644
index 000000000000..8486c3f96b21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -0,0 +1,622 @@
+/*
+ * 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.biometrics.ui.binder
+
+import android.animation.Animator
+import android.content.Context
+import android.hardware.biometrics.BiometricAuthenticator
+import android.hardware.biometrics.BiometricConstants
+import android.hardware.biometrics.BiometricPrompt
+import android.hardware.face.FaceManager
+import android.os.Bundle
+import android.text.method.ScrollingMovementMethod
+import android.util.Log
+import android.view.View
+import android.view.accessibility.AccessibilityManager
+import android.widget.Button
+import android.widget.TextView
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricFaceIconController
+import com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceIconController
+import com.android.systemui.biometrics.AuthBiometricFingerprintIconController
+import com.android.systemui.biometrics.AuthBiometricView
+import com.android.systemui.biometrics.AuthBiometricView.Callback
+import com.android.systemui.biometrics.AuthBiometricViewAdapter
+import com.android.systemui.biometrics.AuthIconController
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.domain.model.BiometricModalities
+import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.domain.model.asBiometricModality
+import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.biometrics.ui.BiometricPromptLayout
+import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode
+import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
+import com.android.systemui.biometrics.ui.viewmodel.PromptSize
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+private const val TAG = "BiometricViewBinder"
+
+/** Top-most view binder for BiometricPrompt views. */
+object BiometricViewBinder {
+
+ /** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */
+ @JvmStatic
+ fun bind(
+ view: BiometricPromptLayout,
+ viewModel: PromptViewModel,
+ panelViewController: AuthPanelController,
+ jankListener: BiometricJankListener,
+ backgroundView: View,
+ legacyCallback: Callback,
+ applicationScope: CoroutineScope,
+ ): AuthBiometricViewAdapter {
+ val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
+ fun notifyAccessibilityChanged() {
+ Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
+ }
+
+ val textColorError =
+ view.resources.getColor(R.color.biometric_dialog_error, view.context.theme)
+ val textColorHint =
+ view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
+
+ val titleView = view.findViewById<TextView>(R.id.title)
+ val subtitleView = view.findViewById<TextView>(R.id.subtitle)
+ val descriptionView = view.findViewById<TextView>(R.id.description)
+
+ // set selected for marquee
+ titleView.isSelected = true
+ subtitleView.isSelected = true
+ descriptionView.movementMethod = ScrollingMovementMethod()
+
+ val iconViewOverlay = view.findViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+ val iconView = view.findViewById<LottieAnimationView>(R.id.biometric_icon)
+ val indicatorMessageView = view.findViewById<TextView>(R.id.indicator)
+
+ // Negative-side (left) buttons
+ val negativeButton = view.findViewById<Button>(R.id.button_negative)
+ val cancelButton = view.findViewById<Button>(R.id.button_cancel)
+ val credentialFallbackButton = view.findViewById<Button>(R.id.button_use_credential)
+
+ // Positive-side (right) buttons
+ val confirmationButton = view.findViewById<Button>(R.id.button_confirm)
+ val retryButton = view.findViewById<Button>(R.id.button_try_again)
+
+ // TODO(b/251476085): temporary workaround for the unsafe callbacks & legacy controllers
+ val adapter =
+ Spaghetti(
+ view = view,
+ viewModel = viewModel,
+ applicationContext = view.context.applicationContext,
+ applicationScope = applicationScope,
+ )
+
+ // bind to prompt
+ var boundSize = false
+ view.repeatWhenAttached {
+ // these do not change and need to be set before any size transitions
+ val modalities = viewModel.modalities.first()
+ titleView.text = viewModel.title.first()
+ descriptionView.text = viewModel.description.first()
+ subtitleView.text = viewModel.subtitle.first()
+
+ // set button listeners
+ negativeButton.setOnClickListener {
+ legacyCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE)
+ }
+ cancelButton.setOnClickListener {
+ legacyCallback.onAction(Callback.ACTION_USER_CANCELED)
+ }
+ credentialFallbackButton.setOnClickListener {
+ viewModel.onSwitchToCredential()
+ legacyCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL)
+ }
+ confirmationButton.setOnClickListener { viewModel.confirmAuthenticated() }
+ retryButton.setOnClickListener {
+ viewModel.showAuthenticating(isRetry = true)
+ legacyCallback.onAction(Callback.ACTION_BUTTON_TRY_AGAIN)
+ }
+
+ // TODO(b/251476085): migrate legacy icon controllers and remove
+ var legacyState: Int = viewModel.legacyState.value
+ val iconController =
+ modalities.asIconController(
+ view.context,
+ iconView,
+ iconViewOverlay,
+ )
+ adapter.attach(this, iconController, modalities, legacyCallback)
+ if (iconController is AuthBiometricFingerprintIconController) {
+ view.updateFingerprintAffordanceSize(iconController)
+ }
+ if (iconController is HackyCoexIconController) {
+ iconController.faceMode = !viewModel.isConfirmationRequested.first()
+ }
+
+ // the icon controller must be created before this happens for the legacy
+ // sizing code in BiometricPromptLayout to work correctly. Simplify this
+ // when those are also migrated. (otherwise the icon size may not be set to
+ // a pixel value before the view is measured and WRAP_CONTENT will be incorrectly
+ // used as part of the measure spec)
+ if (!boundSize) {
+ boundSize = true
+ BiometricViewSizeBinder.bind(
+ view = view,
+ viewModel = viewModel,
+ viewsToHideWhenSmall =
+ listOf(
+ titleView,
+ subtitleView,
+ descriptionView,
+ ),
+ viewsToFadeInOnSizeChange =
+ listOf(
+ titleView,
+ subtitleView,
+ descriptionView,
+ indicatorMessageView,
+ negativeButton,
+ cancelButton,
+ retryButton,
+ confirmationButton,
+ credentialFallbackButton,
+ ),
+ panelViewController = panelViewController,
+ jankListener = jankListener,
+ )
+ }
+
+ // TODO(b/251476085): migrate legacy icon controllers and remove
+ // The fingerprint sensor is started by the legacy
+ // AuthContainerView#onDialogAnimatedIn in all cases but the implicit coex flow
+ // (delayed mode). In that case, start it on the first transition to delayed
+ // which will be triggered by any auth failure.
+ lifecycleScope.launch {
+ val oldMode = viewModel.fingerprintStartMode.first()
+ viewModel.fingerprintStartMode.collect { newMode ->
+ // trigger sensor to start
+ if (
+ oldMode == FingerprintStartMode.Pending &&
+ newMode == FingerprintStartMode.Delayed
+ ) {
+ legacyCallback.onAction(Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR)
+ }
+
+ if (newMode.isStarted) {
+ // do wonky switch from implicit to explicit flow
+ (iconController as? HackyCoexIconController)?.faceMode = false
+ viewModel.showAuthenticating(
+ modalities.asDefaultHelpMessage(view.context),
+ )
+ }
+ }
+ }
+
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // handle background clicks
+ launch {
+ combine(viewModel.isAuthenticated, viewModel.size) { (authenticated, _), size ->
+ when {
+ authenticated -> false
+ size == PromptSize.SMALL -> false
+ size == PromptSize.LARGE -> false
+ else -> true
+ }
+ }
+ .collect { dismissOnClick ->
+ backgroundView.setOnClickListener {
+ if (dismissOnClick) {
+ legacyCallback.onAction(Callback.ACTION_USER_CANCELED)
+ } else {
+ Log.w(TAG, "Ignoring background click")
+ }
+ }
+ }
+ }
+
+ // set messages
+ launch {
+ viewModel.isIndicatorMessageVisible.collect { show ->
+ indicatorMessageView.visibility = show.asVisibleOrHidden()
+ }
+ }
+
+ // configure & hide/disable buttons
+ launch {
+ viewModel.credentialKind
+ .map { kind ->
+ when (kind) {
+ PromptKind.Pin ->
+ view.resources.getString(R.string.biometric_dialog_use_pin)
+ PromptKind.Password ->
+ view.resources.getString(R.string.biometric_dialog_use_password)
+ PromptKind.Pattern ->
+ view.resources.getString(R.string.biometric_dialog_use_pattern)
+ else -> ""
+ }
+ }
+ .collect { credentialFallbackButton.text = it }
+ }
+ launch { viewModel.negativeButtonText.collect { negativeButton.text = it } }
+ launch {
+ viewModel.isConfirmButtonVisible.collect { show ->
+ confirmationButton.visibility = show.asVisibleOrGone()
+ }
+ }
+ launch {
+ viewModel.isCancelButtonVisible.collect { show ->
+ cancelButton.visibility = show.asVisibleOrGone()
+ }
+ }
+ launch {
+ viewModel.isNegativeButtonVisible.collect { show ->
+ negativeButton.visibility = show.asVisibleOrGone()
+ }
+ }
+ launch {
+ viewModel.isTryAgainButtonVisible.collect { show ->
+ retryButton.visibility = show.asVisibleOrGone()
+ }
+ }
+ launch {
+ viewModel.isCredentialButtonVisible.collect { show ->
+ credentialFallbackButton.visibility = show.asVisibleOrGone()
+ }
+ }
+
+ // reuse the icon as a confirm button
+ launch {
+ viewModel.isConfirmButtonVisible
+ .map { isPending ->
+ when {
+ isPending && iconController.actsAsConfirmButton ->
+ View.OnClickListener { viewModel.confirmAuthenticated() }
+ else -> null
+ }
+ }
+ .collect { onClick ->
+ iconViewOverlay.setOnClickListener(onClick)
+ iconView.setOnClickListener(onClick)
+ }
+ }
+
+ // TODO(b/251476085): remove w/ legacy icon controllers
+ // set icon affordance using legacy states
+ // like the old code, this causes animations to repeat on config changes :(
+ // but keep behavior for now as no one has complained...
+ launch {
+ viewModel.legacyState.collect { newState ->
+ iconController.updateState(legacyState, newState)
+ legacyState = newState
+ }
+ }
+
+ // not sure why this is here, but the legacy code did it probably needed?
+ launch {
+ viewModel.isAuthenticating.collect { isAuthenticating ->
+ if (isAuthenticating) {
+ notifyAccessibilityChanged()
+ }
+ }
+ }
+
+ // dismiss prompt when authenticated and confirmed
+ launch {
+ viewModel.isAuthenticated.collect { authState ->
+ if (authState.isAuthenticatedAndConfirmed) {
+ view.announceForAccessibility(
+ view.resources.getString(R.string.biometric_dialog_authenticated)
+ )
+ notifyAccessibilityChanged()
+
+ launch {
+ delay(authState.delay)
+ legacyCallback.onAction(Callback.ACTION_AUTHENTICATED)
+ }
+ }
+ }
+ }
+
+ // show error & help messages
+ launch {
+ viewModel.message.collect { promptMessage ->
+ val isError = promptMessage is PromptMessage.Error
+
+ indicatorMessageView.text = promptMessage.message
+ indicatorMessageView.setTextColor(
+ if (isError) textColorError else textColorHint
+ )
+
+ // select to enable marquee unless a screen reader is enabled
+ // TODO(wenhuiy): this may have recently changed per UX - verify and remove
+ indicatorMessageView.isSelected =
+ !accessibilityManager.isEnabled ||
+ !accessibilityManager.isTouchExplorationEnabled
+
+ notifyAccessibilityChanged()
+ }
+ }
+ }
+ }
+
+ return adapter
+ }
+}
+
+/**
+ * Adapter for legacy events. Remove once legacy controller can be replaced by flagged code.
+ *
+ * These events can be dispatched when the view is being recreated so they need to be delivered to
+ * the view model (which will be retained) via the application scope.
+ *
+ * Do not reference the [view] for anything other than [asView].
+ *
+ * TODO(b/251476085): remove after replacing AuthContainerView
+ */
+private class Spaghetti(
+ private val view: View,
+ private val viewModel: PromptViewModel,
+ private val applicationContext: Context,
+ private val applicationScope: CoroutineScope,
+) : AuthBiometricViewAdapter {
+
+ private var lifecycleScope: CoroutineScope? = null
+ private var modalities: BiometricModalities = BiometricModalities()
+ private var faceFailedAtLeastOnce = false
+ private var legacyCallback: Callback? = null
+
+ override var legacyIconController: AuthIconController? = null
+ private set
+
+ // hacky way to suppress lockout errors
+ private val lockoutErrorStrings =
+ listOf(
+ BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
+ BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT,
+ )
+ .map { FaceManager.getErrorString(applicationContext, it, 0 /* vendorCode */) }
+
+ fun attach(
+ lifecycleOwner: LifecycleOwner,
+ iconController: AuthIconController,
+ activeModalities: BiometricModalities,
+ callback: Callback,
+ ) {
+ modalities = activeModalities
+ legacyIconController = iconController
+ legacyCallback = callback
+
+ lifecycleOwner.lifecycle.addObserver(
+ object : DefaultLifecycleObserver {
+ override fun onCreate(owner: LifecycleOwner) {
+ lifecycleScope = owner.lifecycleScope
+ iconController.deactivated = false
+ }
+
+ override fun onDestroy(owner: LifecycleOwner) {
+ lifecycleScope = null
+ iconController.deactivated = true
+ }
+ }
+ )
+ }
+
+ override fun onDialogAnimatedIn(fingerprintWasStarted: Boolean) {
+ if (fingerprintWasStarted) {
+ viewModel.ensureFingerprintHasStarted(isDelayed = false)
+ viewModel.showAuthenticating(modalities.asDefaultHelpMessage(applicationContext))
+ } else {
+ viewModel.showAuthenticating()
+ }
+ }
+
+ override fun onAuthenticationSucceeded(@BiometricAuthenticator.Modality modality: Int) {
+ applicationScope.launch {
+ val authenticatedModality = modality.asBiometricModality()
+ val msgId = getHelpForSuccessfulAuthentication(authenticatedModality)
+ viewModel.showAuthenticated(
+ modality = authenticatedModality,
+ dismissAfterDelay = 500,
+ helpMessage = if (msgId != null) applicationContext.getString(msgId) else ""
+ )
+ }
+ }
+
+ private suspend fun getHelpForSuccessfulAuthentication(
+ authenticatedModality: BiometricModality,
+ ): Int? =
+ when {
+ // for coex, show a message when face succeeds after fingerprint has also started
+ modalities.hasFaceAndFingerprint &&
+ (viewModel.fingerprintStartMode.first() != FingerprintStartMode.Pending) &&
+ (authenticatedModality == BiometricModality.Face) ->
+ R.string.biometric_dialog_tap_confirm_with_face
+ else -> null
+ }
+
+ override fun onAuthenticationFailed(
+ @BiometricAuthenticator.Modality modality: Int,
+ failureReason: String,
+ ) {
+ val failedModality = modality.asBiometricModality()
+ viewModel.ensureFingerprintHasStarted(isDelayed = true)
+
+ applicationScope.launch {
+ val suppress =
+ modalities.hasFaceAndFingerprint &&
+ (failedModality == BiometricModality.Face) &&
+ faceFailedAtLeastOnce
+ if (failedModality == BiometricModality.Face) {
+ faceFailedAtLeastOnce = true
+ }
+
+ viewModel.showTemporaryError(
+ failureReason,
+ messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
+ authenticateAfterError = modalities.hasFingerprint,
+ suppressIfErrorShowing = suppress,
+ failedModality = failedModality,
+ )
+ }
+ }
+
+ override fun onError(modality: Int, error: String) {
+ val errorModality = modality.asBiometricModality()
+ if (ignoreUnsuccessfulEventsFrom(errorModality, error)) {
+ return
+ }
+
+ applicationScope.launch {
+ val suppress =
+ modalities.hasFaceAndFingerprint && (errorModality == BiometricModality.Face)
+ viewModel.showTemporaryError(
+ error,
+ suppressIfErrorShowing = suppress,
+ )
+ delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
+ legacyCallback?.onAction(Callback.ACTION_ERROR)
+ }
+ }
+
+ override fun onHelp(modality: Int, help: String) {
+ if (ignoreUnsuccessfulEventsFrom(modality.asBiometricModality(), "")) {
+ return
+ }
+
+ applicationScope.launch {
+ viewModel.showTemporaryHelp(
+ help,
+ messageAfterHelp = modalities.asDefaultHelpMessage(applicationContext),
+ )
+ }
+ }
+
+ private fun ignoreUnsuccessfulEventsFrom(modality: BiometricModality, message: String) =
+ when {
+ modalities.hasFaceAndFingerprint ->
+ (modality == BiometricModality.Face) &&
+ !(modalities.isFaceStrong && lockoutErrorStrings.contains(message))
+ else -> false
+ }
+
+ override fun startTransitionToCredentialUI() {
+ applicationScope.launch {
+ viewModel.onSwitchToCredential()
+ legacyCallback?.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL)
+ }
+ }
+
+ override fun requestLayout() {
+ // nothing, for legacy view...
+ }
+
+ override fun restoreState(bundle: Bundle?) {
+ // nothing, for legacy view...
+ }
+
+ override fun onSaveState(bundle: Bundle?) {
+ // nothing, for legacy view...
+ }
+
+ override fun onOrientationChanged() {
+ // nothing, for legacy view...
+ }
+
+ override fun cancelAnimation() {
+ view.animate()?.cancel()
+ }
+
+ override fun isCoex() = modalities.hasFaceAndFingerprint
+
+ override fun asView() = view
+}
+
+private fun BiometricModalities.asDefaultHelpMessage(context: Context): String =
+ when {
+ hasFingerprint -> context.getString(R.string.fingerprint_dialog_touch_sensor)
+ else -> ""
+ }
+
+private fun BiometricModalities.asIconController(
+ context: Context,
+ iconView: LottieAnimationView,
+ iconViewOverlay: LottieAnimationView,
+): AuthIconController =
+ when {
+ hasFaceAndFingerprint -> HackyCoexIconController(context, iconView, iconViewOverlay)
+ hasFingerprint -> AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
+ hasFace -> AuthBiometricFaceIconController(context, iconView)
+ else -> throw IllegalStateException("unexpected view type :$this")
+ }
+
+private fun Boolean.asVisibleOrGone(): Int = if (this) View.VISIBLE else View.GONE
+
+private fun Boolean.asVisibleOrHidden(): Int = if (this) View.VISIBLE else View.INVISIBLE
+
+// TODO(b/251476085): proper type?
+typealias BiometricJankListener = Animator.AnimatorListener
+
+// TODO(b/251476085): delete - temporary until the legacy icon controllers are replaced
+private class HackyCoexIconController(
+ context: Context,
+ iconView: LottieAnimationView,
+ iconViewOverlay: LottieAnimationView,
+) : AuthBiometricFingerprintAndFaceIconController(context, iconView, iconViewOverlay) {
+
+ private var state: Int? = null
+ private val faceController = AuthBiometricFaceIconController(context, iconView)
+
+ var faceMode: Boolean = true
+ set(value) {
+ if (field != value) {
+ field = value
+
+ faceController.deactivated = !value
+ iconView.setImageIcon(null)
+ iconViewOverlay.setImageIcon(null)
+ state?.let { updateIcon(AuthBiometricView.STATE_IDLE, it) }
+ }
+ }
+
+ override fun updateIcon(lastState: Int, newState: Int) {
+ if (deactivated) {
+ return
+ }
+
+ if (faceMode) {
+ faceController.updateIcon(lastState, newState)
+ } else {
+ super.updateIcon(lastState, newState)
+ }
+
+ state = newState
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
new file mode 100644
index 000000000000..e4c4e9aedb56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.biometrics.ui.binder
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityManager
+import android.widget.TextView
+import androidx.core.animation.addListener
+import androidx.core.view.doOnLayout
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthDialog
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.ui.BiometricPromptLayout
+import com.android.systemui.biometrics.ui.viewmodel.PromptSize
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.biometrics.ui.viewmodel.isLarge
+import com.android.systemui.biometrics.ui.viewmodel.isMedium
+import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
+import com.android.systemui.biometrics.ui.viewmodel.isSmall
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+/** Helper for [BiometricViewBinder] to handle resize transitions. */
+object BiometricViewSizeBinder {
+
+ /** Resizes [BiometricPromptLayout] and the [panelViewController] via the [PromptViewModel]. */
+ fun bind(
+ view: BiometricPromptLayout,
+ viewModel: PromptViewModel,
+ viewsToHideWhenSmall: List<TextView>,
+ viewsToFadeInOnSizeChange: List<View>,
+ panelViewController: AuthPanelController,
+ jankListener: BiometricJankListener,
+ ) {
+ val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
+ fun notifyAccessibilityChanged() {
+ Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
+ }
+
+ fun startMonitoredAnimation(animators: List<Animator>) {
+ with(AnimatorSet()) {
+ addListener(jankListener)
+ addListener(onEnd = { notifyAccessibilityChanged() })
+ play(animators.first()).apply { animators.drop(1).forEach { next -> with(next) } }
+ start()
+ }
+ }
+
+ val iconHolderView = view.findViewById<View>(R.id.biometric_icon_frame)
+ val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
+ val fullSizeYOffset =
+ view.resources.getDimension(R.dimen.biometric_dialog_medium_to_large_translation_offset)
+
+ // cache the original position of the icon view (as done in legacy view)
+ // this must happen before any size changes can be made
+ var iconHolderOriginalY = 0f
+ view.doOnLayout {
+ iconHolderOriginalY = iconHolderView.y
+
+ // bind to prompt
+ // TODO(b/251476085): migrate the legacy panel controller and simplify this
+ view.repeatWhenAttached {
+ var currentSize: PromptSize? = null
+ lifecycleScope.launch {
+ viewModel.size.collect { size ->
+ // prepare for animated size transitions
+ for (v in viewsToHideWhenSmall) {
+ v.showTextOrHide(forceHide = size.isSmall)
+ }
+ if (currentSize == null && size.isSmall) {
+ iconHolderView.alpha = 0f
+ }
+ if ((currentSize.isSmall && size.isMedium) || size.isSmall) {
+ viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
+ }
+
+ // propagate size changes to legacy panel controller and animate transitions
+ view.doOnLayout {
+ val width = view.measuredWidth
+ val height = view.measuredHeight
+
+ when {
+ size.isSmall -> {
+ iconHolderView.alpha = 1f
+ iconHolderView.y =
+ view.height - iconHolderView.height - iconPadding
+ val newHeight =
+ iconHolderView.height + 2 * iconPadding.toInt() -
+ iconHolderView.paddingTop -
+ iconHolderView.paddingBottom
+ panelViewController.updateForContentDimensions(
+ width,
+ newHeight,
+ 0, /* animateDurationMs */
+ )
+ }
+ size.isMedium && currentSize.isSmall -> {
+ val duration = AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
+ panelViewController.updateForContentDimensions(
+ width,
+ height,
+ duration,
+ )
+ startMonitoredAnimation(
+ listOf(
+ iconHolderView.asVerticalAnimator(
+ duration = duration.toLong(),
+ toY = iconHolderOriginalY,
+ ),
+ viewsToFadeInOnSizeChange.asFadeInAnimator(
+ duration = duration.toLong(),
+ delay = duration.toLong(),
+ ),
+ )
+ )
+ }
+ size.isMedium && currentSize.isNullOrNotSmall -> {
+ panelViewController.updateForContentDimensions(
+ width,
+ height,
+ 0, /* animateDurationMs */
+ )
+ }
+ size.isLarge -> {
+ val duration = AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
+ panelViewController.setUseFullScreen(true)
+ panelViewController.updateForContentDimensions(
+ panelViewController.containerWidth,
+ panelViewController.containerHeight,
+ duration,
+ )
+
+ startMonitoredAnimation(
+ listOf(
+ view.asVerticalAnimator(
+ duration.toLong() * 2 / 3,
+ toY = view.y - fullSizeYOffset
+ ),
+ listOf(view)
+ .asFadeInAnimator(
+ duration = duration.toLong() / 2,
+ delay = duration.toLong(),
+ ),
+ )
+ )
+ // TODO(b/251476085): clean up (copied from legacy)
+ if (view.isAttachedToWindow) {
+ val parent = view.parent as? ViewGroup
+ parent?.removeView(view)
+ }
+ }
+ }
+
+ currentSize = size
+ notifyAccessibilityChanged()
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+private fun TextView.showTextOrHide(forceHide: Boolean = false) {
+ visibility = if (forceHide || text.isBlank()) View.GONE else View.VISIBLE
+}
+
+private fun View.asVerticalAnimator(
+ duration: Long,
+ toY: Float,
+ fromY: Float = this.y
+): ValueAnimator {
+ val animator = ValueAnimator.ofFloat(fromY, toY)
+ animator.duration = duration
+ animator.addUpdateListener { y = it.animatedValue as Float }
+ return animator
+}
+
+private fun List<View>.asFadeInAnimator(duration: Long, delay: Long): ValueAnimator {
+ forEach { it.alpha = 0f }
+ val animator = ValueAnimator.ofFloat(0f, 1f)
+ animator.duration = duration
+ animator.startDelay = delay
+ animator.addUpdateListener {
+ val alpha = it.animatedValue as Float
+ forEach { view -> view.alpha = alpha }
+ }
+ return animator
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt
index ba23f1cfa22d..a64798cba745 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt
@@ -4,7 +4,7 @@ import android.graphics.drawable.Drawable
import android.os.UserHandle
/** View model for the top-level header / info area of BiometricPrompt. */
-interface HeaderViewModel {
+interface CredentialHeaderViewModel {
val user: UserHandle
val title: String
val subtitle: String
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
index 84bbceb38fa7..9d7b94081145 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -7,8 +7,8 @@ import android.text.InputType
import com.android.internal.widget.LockPatternView
import com.android.systemui.R
import com.android.systemui.biometrics.Utils
-import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.CredentialStatus
+import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
@@ -27,11 +27,11 @@ class CredentialViewModel
@Inject
constructor(
@Application private val applicationContext: Context,
- private val credentialInteractor: BiometricPromptCredentialInteractor,
+ private val credentialInteractor: PromptCredentialInteractor,
) {
/** Top level information about the prompt. */
- val header: Flow<HeaderViewModel> =
+ val header: Flow<CredentialHeaderViewModel> =
credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>().map {
request ->
BiometricPromptHeaderViewModelImpl(
@@ -109,12 +109,14 @@ constructor(
}
/** Check a PIN or password and update [validatedAttestation] or [remainingAttempts]. */
- suspend fun checkCredential(text: CharSequence, header: HeaderViewModel) =
+ suspend fun checkCredential(text: CharSequence, header: CredentialHeaderViewModel) =
checkCredential(credentialInteractor.checkCredential(header.asRequest(), text = text))
/** Check a pattern and update [validatedAttestation] or [remainingAttempts]. */
- suspend fun checkCredential(pattern: List<LockPatternView.Cell>, header: HeaderViewModel) =
- checkCredential(credentialInteractor.checkCredential(header.asRequest(), pattern = pattern))
+ suspend fun checkCredential(
+ pattern: List<LockPatternView.Cell>,
+ header: CredentialHeaderViewModel
+ ) = checkCredential(credentialInteractor.checkCredential(header.asRequest(), pattern = pattern))
private suspend fun checkCredential(result: CredentialStatus) {
when (result) {
@@ -172,7 +174,7 @@ private class BiometricPromptHeaderViewModelImpl(
override val subtitle: String,
override val description: String,
override val icon: Drawable,
-) : HeaderViewModel
+) : CredentialHeaderViewModel
-private fun HeaderViewModel.asRequest(): BiometricPromptRequest.Credential =
+private fun CredentialHeaderViewModel.asRequest(): BiometricPromptRequest.Credential =
(this as BiometricPromptHeaderViewModelImpl).request
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
new file mode 100644
index 000000000000..9cb91b3d51a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import com.android.systemui.biometrics.domain.model.BiometricModality
+
+/**
+ * The authenticated state with the [authenticatedModality] (when [isAuthenticated]) with an
+ * optional [delay] to keep the UI showing before dismissing when [needsUserConfirmation] is not
+ * required.
+ */
+data class PromptAuthState(
+ val isAuthenticated: Boolean,
+ val authenticatedModality: BiometricModality = BiometricModality.None,
+ val needsUserConfirmation: Boolean = false,
+ val delay: Long = 0,
+) {
+ /** If authentication was successful and the user has confirmed (or does not need to). */
+ val isAuthenticatedAndConfirmed: Boolean
+ get() = isAuthenticated && !needsUserConfirmation
+
+ /** If a successful authentication has not occurred. */
+ val isNotAuthenticated: Boolean
+ get() = !isAuthenticated
+
+ /** If a authentication has succeeded and it was done by face (may need confirmation). */
+ val isAuthenticatedByFace: Boolean
+ get() = isAuthenticated && authenticatedModality == BiometricModality.Face
+
+ /** If a authentication has succeeded and it was done by fingerprint (may need confirmation). */
+ val isAuthenticatedByFingerprint: Boolean
+ get() = isAuthenticated && authenticatedModality == BiometricModality.Fingerprint
+
+ /** Copies this state, but toggles [needsUserConfirmation] to false. */
+ fun asConfirmed(): PromptAuthState =
+ PromptAuthState(
+ isAuthenticated = isAuthenticated,
+ authenticatedModality = authenticatedModality,
+ needsUserConfirmation = false,
+ delay = delay,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
new file mode 100644
index 000000000000..219da716f7d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+/**
+ * A help, hint, or error message to show.
+ *
+ * These typically correspond to the same category of help/error callbacks from the underlying HAL
+ * that runs the biometric operation, but may be customized by the framework.
+ */
+sealed interface PromptMessage {
+
+ /** The message to show the user or the empty string. */
+ val message: String
+ get() =
+ when (this) {
+ is Error -> errorMessage
+ is Help -> helpMessage
+ else -> ""
+ }
+
+ /** If this is an [Error] or [Help] message. */
+ val isErrorOrHelp: Boolean
+ get() = this is Error || this is Help
+
+ /** An error message. */
+ data class Error(val errorMessage: String) : PromptMessage
+
+ /** A help message. */
+ data class Help(val helpMessage: String) : PromptMessage
+
+ /** No message. */
+ object Empty : PromptMessage
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptSize.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptSize.kt
new file mode 100644
index 000000000000..d779062be640
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptSize.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+/** The size of a biometric prompt. */
+enum class PromptSize {
+ /** Minimal UI, showing only biometric icon. */
+ SMALL,
+ /** Normal-sized biometric UI, showing title, icon, buttons, etc. */
+ MEDIUM,
+ /** Full-screen credential UI. */
+ LARGE,
+}
+
+val PromptSize?.isSmall: Boolean
+ get() = this != null && this == PromptSize.SMALL
+
+val PromptSize?.isNotSmall: Boolean
+ get() = this != null && this != PromptSize.SMALL
+
+val PromptSize?.isNullOrNotSmall: Boolean
+ get() = this == null || this != PromptSize.SMALL
+
+val PromptSize?.isMedium: Boolean
+ get() = this != null && this == PromptSize.MEDIUM
+
+val PromptSize?.isLarge: Boolean
+ get() = this != null && this == PromptSize.LARGE
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
new file mode 100644
index 000000000000..2f8ed096f4ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -0,0 +1,453 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import android.hardware.biometrics.BiometricPrompt
+import android.util.Log
+import com.android.systemui.biometrics.AuthBiometricView
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.domain.model.BiometricModalities
+import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.PromptKind
+import javax.inject.Inject
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/** ViewModel for BiometricPrompt. */
+class PromptViewModel
+@Inject
+constructor(
+ private val interactor: PromptSelectorInteractor,
+) {
+ /** The set of modalities available for this prompt */
+ val modalities: Flow<BiometricModalities> =
+ interactor.prompt.map { it?.modalities ?: BiometricModalities() }.distinctUntilChanged()
+
+ // TODO(b/251476085): remove after icon controllers are migrated - do not keep this state
+ private var _legacyState = MutableStateFlow(AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN)
+ val legacyState: StateFlow<Int> = _legacyState.asStateFlow()
+
+ private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** If the user is currently authenticating (i.e. at least one biometric is scanning). */
+ val isAuthenticating: Flow<Boolean> = _isAuthenticating.asStateFlow()
+
+ private val _isAuthenticated: MutableStateFlow<PromptAuthState> =
+ MutableStateFlow(PromptAuthState(false))
+
+ /** If the user has successfully authenticated and confirmed (when explicitly required). */
+ val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
+
+ /** If the API caller requested explicit confirmation after successful authentication. */
+ val isConfirmationRequested: Flow<Boolean> = interactor.isConfirmationRequested
+
+ /** The kind of credential the user has. */
+ val credentialKind: Flow<PromptKind> = interactor.credentialKind
+
+ /** The label to use for the cancel button. */
+ val negativeButtonText: Flow<String> = interactor.prompt.map { it?.negativeButtonText ?: "" }
+
+ private val _message: MutableStateFlow<PromptMessage> = MutableStateFlow(PromptMessage.Empty)
+
+ /** A message to show the user, if there is an error, hint, or help to show. */
+ val message: Flow<PromptMessage> = _message.asStateFlow()
+
+ private val isRetrySupported: Flow<Boolean> = modalities.map { it.hasFace }
+
+ private val _fingerprintStartMode = MutableStateFlow(FingerprintStartMode.Pending)
+
+ /** Fingerprint sensor state. */
+ val fingerprintStartMode: Flow<FingerprintStartMode> = _fingerprintStartMode.asStateFlow()
+
+ private val _forceLargeSize = MutableStateFlow(false)
+ private val _forceMediumSize = MutableStateFlow(false)
+
+ /** The size of the prompt. */
+ val size: Flow<PromptSize> =
+ combine(
+ _forceLargeSize,
+ _forceMediumSize,
+ modalities,
+ interactor.isConfirmationRequested,
+ fingerprintStartMode,
+ ) { forceLarge, forceMedium, modalities, confirmationRequired, fpStartMode ->
+ when {
+ forceLarge -> PromptSize.LARGE
+ forceMedium -> PromptSize.MEDIUM
+ modalities.hasFaceOnly && !confirmationRequired -> PromptSize.SMALL
+ modalities.hasFaceAndFingerprint &&
+ !confirmationRequired &&
+ fpStartMode == FingerprintStartMode.Pending -> PromptSize.SMALL
+ else -> PromptSize.MEDIUM
+ }
+ }
+ .distinctUntilChanged()
+
+ /** Title for the prompt. */
+ val title: Flow<String> = interactor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
+
+ /** Subtitle for the prompt. */
+ val subtitle: Flow<String> = interactor.prompt.map { it?.subtitle ?: "" }.distinctUntilChanged()
+
+ /** Description for the prompt. */
+ val description: Flow<String> =
+ interactor.prompt.map { it?.description ?: "" }.distinctUntilChanged()
+
+ /** If the indicator (help, error) message should be shown. */
+ val isIndicatorMessageVisible: Flow<Boolean> =
+ combine(
+ size,
+ message,
+ ) { size, message ->
+ size.isNotSmall && message.message.isNotBlank()
+ }
+ .distinctUntilChanged()
+
+ /** If the auth is pending confirmation and the confirm button should be shown. */
+ val isConfirmButtonVisible: Flow<Boolean> =
+ combine(
+ size,
+ isAuthenticated,
+ ) { size, authState ->
+ size.isNotSmall && authState.isAuthenticated && authState.needsUserConfirmation
+ }
+ .distinctUntilChanged()
+
+ /** If the negative button should be shown. */
+ val isNegativeButtonVisible: Flow<Boolean> =
+ combine(
+ size,
+ isAuthenticated,
+ interactor.isCredentialAllowed,
+ ) { size, authState, credentialAllowed ->
+ size.isNotSmall && authState.isNotAuthenticated && !credentialAllowed
+ }
+ .distinctUntilChanged()
+
+ /** If the cancel button should be shown (. */
+ val isCancelButtonVisible: Flow<Boolean> =
+ combine(
+ size,
+ isAuthenticated,
+ isNegativeButtonVisible,
+ isConfirmButtonVisible,
+ ) { size, authState, showNegativeButton, showConfirmButton ->
+ size.isNotSmall &&
+ authState.isAuthenticated &&
+ !showNegativeButton &&
+ showConfirmButton
+ }
+ .distinctUntilChanged()
+
+ private val _canTryAgainNow = MutableStateFlow(false)
+ /**
+ * If authentication can be manually restarted via the try again button or touching a
+ * fingerprint sensor.
+ */
+ val canTryAgainNow: Flow<Boolean> =
+ combine(
+ _canTryAgainNow,
+ size,
+ isAuthenticated,
+ isRetrySupported,
+ ) { readyToTryAgain, size, authState, supportsRetry ->
+ readyToTryAgain && size.isNotSmall && supportsRetry && authState.isNotAuthenticated
+ }
+ .distinctUntilChanged()
+
+ /** If the try again button show be shown (only the button, see [canTryAgainNow]). */
+ val isTryAgainButtonVisible: Flow<Boolean> =
+ combine(
+ canTryAgainNow,
+ modalities,
+ ) { tryAgainIsPossible, modalities ->
+ tryAgainIsPossible && modalities.hasFaceOnly
+ }
+ .distinctUntilChanged()
+
+ /** If the credential fallback button show be shown. */
+ val isCredentialButtonVisible: Flow<Boolean> =
+ combine(
+ size,
+ isAuthenticated,
+ interactor.isCredentialAllowed,
+ ) { size, authState, credentialAllowed ->
+ size.isNotSmall && authState.isNotAuthenticated && credentialAllowed
+ }
+ .distinctUntilChanged()
+
+ private var messageJob: Job? = null
+
+ /**
+ * Show a temporary error [message] associated with an optional [failedModality].
+ *
+ * An optional [messageAfterError] will be shown via [showAuthenticating] when
+ * [authenticateAfterError] is set (or via [showHelp] when not set) after the error is
+ * dismissed.
+ *
+ * The error is ignored if the user has already authenticated and it is treated as
+ * [onSilentError] if [suppressIfErrorShowing] is set and an error message is already showing.
+ */
+ suspend fun showTemporaryError(
+ message: String,
+ messageAfterError: String = "",
+ authenticateAfterError: Boolean = false,
+ suppressIfErrorShowing: Boolean = false,
+ failedModality: BiometricModality = BiometricModality.None,
+ ) = coroutineScope {
+ if (_isAuthenticated.value.isAuthenticated) {
+ return@coroutineScope
+ }
+ if (_message.value.isErrorOrHelp && suppressIfErrorShowing) {
+ onSilentError(failedModality)
+ return@coroutineScope
+ }
+
+ _isAuthenticating.value = false
+ _isAuthenticated.value = PromptAuthState(false)
+ _forceMediumSize.value = true
+ _canTryAgainNow.value = supportsRetry(failedModality)
+ _message.value = PromptMessage.Error(message)
+ _legacyState.value = AuthBiometricView.STATE_ERROR
+
+ messageJob?.cancel()
+ messageJob = launch {
+ delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
+ if (authenticateAfterError) {
+ showAuthenticating(messageAfterError)
+ } else {
+ showHelp(messageAfterError)
+ }
+ }
+ }
+
+ /**
+ * Call instead of [showTemporaryError] if an error from the HAL should be silently ignored to
+ * enable retry (if the [failedModality] supports retrying).
+ *
+ * Ignored if the user has already authenticated.
+ */
+ private fun onSilentError(failedModality: BiometricModality = BiometricModality.None) {
+ if (_isAuthenticated.value.isNotAuthenticated) {
+ _canTryAgainNow.value = supportsRetry(failedModality)
+ }
+ }
+
+ /**
+ * Call to ensure the fingerprint sensor has started. Either when the dialog is first shown
+ * (most cases) or when it should be enabled after a first error (coex implicit flow).
+ */
+ fun ensureFingerprintHasStarted(isDelayed: Boolean) {
+ if (_fingerprintStartMode.value == FingerprintStartMode.Pending) {
+ _fingerprintStartMode.value =
+ if (isDelayed) FingerprintStartMode.Delayed else FingerprintStartMode.Normal
+ }
+ }
+
+ // enable retry only when face fails (fingerprint runs constantly)
+ private fun supportsRetry(failedModality: BiometricModality) =
+ failedModality == BiometricModality.Face
+
+ /**
+ * Show a persistent help message.
+ *
+ * Will be show even if the user has already authenticated.
+ */
+ suspend fun showHelp(message: String) {
+ val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated
+ if (!alreadyAuthenticated) {
+ _isAuthenticating.value = false
+ _isAuthenticated.value = PromptAuthState(false)
+ }
+
+ _message.value =
+ if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
+ _forceMediumSize.value = true
+ _legacyState.value =
+ if (alreadyAuthenticated) {
+ AuthBiometricView.STATE_PENDING_CONFIRMATION
+ } else {
+ AuthBiometricView.STATE_HELP
+ }
+
+ messageJob?.cancel()
+ messageJob = null
+ }
+
+ /**
+ * Show a temporary help message and transition back to a fixed message.
+ *
+ * Ignored if the user has already authenticated.
+ */
+ suspend fun showTemporaryHelp(
+ message: String,
+ messageAfterHelp: String = "",
+ ) = coroutineScope {
+ if (_isAuthenticated.value.isAuthenticated) {
+ return@coroutineScope
+ }
+
+ _isAuthenticating.value = false
+ _isAuthenticated.value = PromptAuthState(false)
+ _message.value =
+ if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
+ _forceMediumSize.value = true
+ _legacyState.value = AuthBiometricView.STATE_HELP
+
+ messageJob?.cancel()
+ messageJob = launch {
+ delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
+ showAuthenticating(messageAfterHelp)
+ }
+ }
+
+ /** Show the user that biometrics are actively running and set [isAuthenticating]. */
+ fun showAuthenticating(message: String = "", isRetry: Boolean = false) {
+ if (_isAuthenticated.value.isAuthenticated) {
+ // TODO(jbolinger): convert to go/tex-apc?
+ Log.w(TAG, "Cannot show authenticating after authenticated")
+ return
+ }
+
+ _isAuthenticating.value = true
+ _isAuthenticated.value = PromptAuthState(false)
+ _message.value = if (message.isBlank()) PromptMessage.Empty else PromptMessage.Help(message)
+ _legacyState.value = AuthBiometricView.STATE_AUTHENTICATING
+
+ // reset the try again button(s) after the user attempts a retry
+ if (isRetry) {
+ _canTryAgainNow.value = false
+ }
+
+ messageJob?.cancel()
+ messageJob = null
+ }
+
+ /**
+ * Show successfully authentication, set [isAuthenticated], and dismiss the prompt after a
+ * [dismissAfterDelay] or prompt for explicit confirmation (if required).
+ */
+ suspend fun showAuthenticated(
+ modality: BiometricModality,
+ dismissAfterDelay: Long,
+ helpMessage: String = "",
+ ) {
+ if (_isAuthenticated.value.isAuthenticated) {
+ // TODO(jbolinger): convert to go/tex-apc?
+ Log.w(TAG, "Cannot show authenticated after authenticated")
+ return
+ }
+
+ _isAuthenticating.value = false
+ val needsUserConfirmation = needsExplicitConfirmation(modality)
+ _isAuthenticated.value =
+ PromptAuthState(true, modality, needsUserConfirmation, dismissAfterDelay)
+ _message.value = PromptMessage.Empty
+ _legacyState.value =
+ if (needsUserConfirmation) {
+ AuthBiometricView.STATE_PENDING_CONFIRMATION
+ } else {
+ AuthBiometricView.STATE_AUTHENTICATED
+ }
+
+ messageJob?.cancel()
+ messageJob = null
+
+ if (helpMessage.isNotBlank()) {
+ showHelp(helpMessage)
+ }
+ }
+
+ private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
+ val availableModalities = modalities.first()
+ val confirmationRequested = interactor.isConfirmationRequested.first()
+
+ if (availableModalities.hasFaceAndFingerprint) {
+ // coex only needs confirmation when face is successful, unless it happens on the
+ // first attempt (i.e. without failure) before fingerprint scanning starts
+ if (modality == BiometricModality.Face) {
+ return (fingerprintStartMode.first() != FingerprintStartMode.Pending) ||
+ confirmationRequested
+ }
+ }
+ if (availableModalities.hasFaceOnly) {
+ return confirmationRequested
+ }
+ // fingerprint only never requires confirmation
+ return false
+ }
+
+ /**
+ * Set the prompt's auth state to authenticated and confirmed.
+ *
+ * This should only be used after [showAuthenticated] when the operation requires explicit user
+ * confirmation.
+ */
+ fun confirmAuthenticated() {
+ val authState = _isAuthenticated.value
+ if (authState.isNotAuthenticated) {
+ "Cannot show authenticated after authenticated"
+ Log.w(TAG, "Cannot confirm authenticated when not authenticated")
+ return
+ }
+
+ _isAuthenticated.value = authState.asConfirmed()
+ _message.value = PromptMessage.Empty
+ _legacyState.value = AuthBiometricView.STATE_AUTHENTICATED
+
+ messageJob?.cancel()
+ messageJob = null
+ }
+
+ /**
+ * Switch to the credential view.
+ *
+ * TODO(b/251476085): this should be decoupled from the shared panel controller
+ */
+ fun onSwitchToCredential() {
+ _forceLargeSize.value = true
+ }
+
+ companion object {
+ private const val TAG = "PromptViewModel"
+ }
+}
+
+/** How the fingerprint sensor was started for the prompt. */
+enum class FingerprintStartMode {
+ /** Fingerprint sensor has not started. */
+ Pending,
+
+ /** Fingerprint sensor started immediately when prompt was displayed. */
+ Normal,
+
+ /** Fingerprint sensor started after the first failure of another passive modality. */
+ Delayed;
+
+ /** If this is [Normal] or [Delayed]. */
+ val isStarted: Boolean
+ get() = this == Normal || this == Delayed
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
index 4c817b2e46a8..49a0a3c1e965 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.data.repo
+import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -29,7 +30,15 @@ class BouncerRepository @Inject constructor() {
/** The user-facing message to show in the bouncer. */
val message: StateFlow<String?> = _message.asStateFlow()
+ private val _throttling = MutableStateFlow<AuthenticationThrottledModel?>(null)
+ /** The current authentication throttling state. If `null`, there's no throttling. */
+ val throttling: StateFlow<AuthenticationThrottledModel?> = _throttling.asStateFlow()
+
fun setMessage(message: String?) {
_message.value = message
}
+
+ fun setThrottling(throttling: AuthenticationThrottledModel?) {
+ _throttling.value = throttling
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 8264fed4846a..e462e2f5b7d8 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -17,10 +17,12 @@
package com.android.systemui.bouncer.domain.interactor
import android.content.Context
+import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
@@ -29,8 +31,11 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Encapsulates business logic and application state accessing use-cases. */
@@ -46,7 +51,22 @@ constructor(
) {
/** The user-facing message to show in the bouncer. */
- val message: StateFlow<String?> = repository.message
+ val message: StateFlow<String?> =
+ combine(
+ repository.message,
+ repository.throttling,
+ ) { message, throttling ->
+ messageOrThrottlingMessage(message, throttling)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ messageOrThrottlingMessage(
+ repository.message.value,
+ repository.throttling.value,
+ )
+ )
/**
* The currently-configured authentication method. This determines how the authentication
@@ -55,6 +75,9 @@ constructor(
val authenticationMethod: StateFlow<AuthenticationMethodModel> =
authenticationInteractor.authenticationMethod
+ /** The current authentication throttling state. If `null`, there's no throttling. */
+ val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling
+
init {
applicationScope.launch {
combine(
@@ -129,14 +152,39 @@ constructor(
fun authenticate(
input: List<Any>,
) {
+ if (repository.throttling.value != null) {
+ return
+ }
+
val isAuthenticated = authenticationInteractor.authenticate(input)
- if (isAuthenticated) {
- sceneInteractor.setCurrentScene(
- containerName = containerName,
- scene = SceneModel(SceneKey.Gone),
- )
- } else {
- repository.setMessage(errorMessage(authenticationMethod.value))
+ val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value
+ when {
+ isAuthenticated -> {
+ repository.setThrottling(null)
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.Gone),
+ )
+ }
+ failedAttempts >= THROTTLE_AGGRESSIVELY_AFTER || failedAttempts % THROTTLE_EVERY == 0 ->
+ applicationScope.launch {
+ var remainingDurationSec = THROTTLE_DURATION_SEC
+ while (remainingDurationSec > 0) {
+ repository.setThrottling(
+ AuthenticationThrottledModel(
+ failedAttemptCount = failedAttempts,
+ totalDurationSec = THROTTLE_DURATION_SEC,
+ remainingDurationSec = remainingDurationSec,
+ )
+ )
+ remainingDurationSec--
+ delay(1000)
+ }
+
+ repository.setThrottling(null)
+ clearMessage()
+ }
+ else -> repository.setMessage(errorMessage(authenticationMethod.value))
}
}
@@ -163,10 +211,31 @@ constructor(
}
}
+ private fun messageOrThrottlingMessage(
+ message: String?,
+ throttling: AuthenticationThrottledModel?,
+ ): String {
+ return when {
+ throttling != null ->
+ applicationContext.getString(
+ com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
+ throttling.remainingDurationSec,
+ )
+ message != null -> message
+ else -> ""
+ }
+ }
+
@AssistedFactory
interface Factory {
fun create(
containerName: String,
): BouncerInteractor
}
+
+ companion object {
+ @VisibleForTesting const val THROTTLE_DURATION_SEC = 30
+ @VisibleForTesting const val THROTTLE_AGGRESSIVELY_AFTER = 15
+ @VisibleForTesting const val THROTTLE_EVERY = 5
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt
new file mode 100644
index 000000000000..cbea635f6b13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.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.bouncer.shared.model
+
+/**
+ * Models application state for when further authentication attempts are being throttled due to too
+ * many consecutive failed authentication attempts.
+ */
+data class AuthenticationThrottledModel(
+ /** Total number of failed attempts so far. */
+ val failedAttemptCount: Int,
+ /** Total amount of time the user has to wait before attempting again. */
+ val totalDurationSec: Int,
+ /** Remaining amount of time the user has to wait before attempting again. */
+ val remainingDurationSec: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index ebefb78f0477..774a5593530c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -16,4 +16,14 @@
package com.android.systemui.bouncer.ui.viewmodel
-sealed interface AuthMethodBouncerViewModel
+import kotlinx.coroutines.flow.StateFlow
+
+sealed interface AuthMethodBouncerViewModel {
+ /**
+ * Whether user input is enabled.
+ *
+ * If `false`, user input should be completely ignored in the UI as the user is "locked out" of
+ * being able to attempt to unlock the device.
+ */
+ val isInputEnabled: StateFlow<Boolean>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index c6528d0736cd..02991bd47c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.bouncer.ui.viewmodel
import android.content.Context
+import com.android.systemui.R
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dagger.qualifiers.Application
@@ -24,10 +25,14 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/** Holds UI state and handles user input on bouncer UIs. */
class BouncerViewModel
@@ -40,16 +45,42 @@ constructor(
) {
private val interactor: BouncerInteractor = interactorFactory.create(containerName)
+ /**
+ * Whether updates to the message should be cross-animated from one message to another.
+ *
+ * If `false`, no animation should be applied, the message text should just be replaced
+ * instantly.
+ */
+ val isMessageUpdateAnimationsEnabled: StateFlow<Boolean> =
+ interactor.throttling
+ .map { it == null }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = interactor.throttling.value == null,
+ )
+
+ private val isInputEnabled: StateFlow<Boolean> =
+ interactor.throttling
+ .map { it == null }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = interactor.throttling.value == null,
+ )
+
private val pin: PinBouncerViewModel by lazy {
PinBouncerViewModel(
applicationScope = applicationScope,
interactor = interactor,
+ isInputEnabled = isInputEnabled,
)
}
private val password: PasswordBouncerViewModel by lazy {
PasswordBouncerViewModel(
interactor = interactor,
+ isInputEnabled = isInputEnabled,
)
}
@@ -58,6 +89,7 @@ constructor(
applicationContext = applicationContext,
applicationScope = applicationScope,
interactor = interactor,
+ isInputEnabled = isInputEnabled,
)
}
@@ -81,11 +113,59 @@ constructor(
initialValue = interactor.message.value ?: "",
)
+ private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
+ /**
+ * A message for a throttling dialog to show when the user has attempted the wrong credential
+ * too many times and now must wait a while before attempting again.
+ *
+ * If `null`, no dialog should be shown.
+ *
+ * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user
+ * dismisses this dialog.
+ */
+ val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
+
+ init {
+ applicationScope.launch {
+ interactor.throttling
+ .map { model ->
+ model?.let {
+ when (interactor.authenticationMethod.value) {
+ is AuthenticationMethodModel.PIN ->
+ R.string.kg_too_many_failed_pin_attempts_dialog_message
+ is AuthenticationMethodModel.Password ->
+ R.string.kg_too_many_failed_password_attempts_dialog_message
+ is AuthenticationMethodModel.Pattern ->
+ R.string.kg_too_many_failed_pattern_attempts_dialog_message
+ else -> null
+ }?.let { stringResourceId ->
+ applicationContext.getString(
+ stringResourceId,
+ model.failedAttemptCount,
+ model.totalDurationSec,
+ )
+ }
+ }
+ }
+ .distinctUntilChanged()
+ .collect { dialogMessageOrNull ->
+ if (dialogMessageOrNull != null) {
+ _throttlingDialogMessage.value = dialogMessageOrNull
+ }
+ }
+ }
+ }
+
/** Notifies that the emergency services button was clicked. */
fun onEmergencyServicesButtonClicked() {
// TODO(b/280877228): implement this
}
+ /** Notifies that a throttling dialog has been dismissed by the user. */
+ fun onThrottlingDialogDismissed() {
+ _throttlingDialogMessage.value = null
+ }
+
private fun toViewModel(
authMethod: AuthenticationMethodModel,
): AuthMethodBouncerViewModel? {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 730d4e8ba050..c38fcaa3b657 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.asStateFlow
/** Holds UI state and handles user input for the password bouncer UI. */
class PasswordBouncerViewModel(
private val interactor: BouncerInteractor,
+ override val isInputEnabled: StateFlow<Boolean>,
) : AuthMethodBouncerViewModel {
private val _password = MutableStateFlow("")
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index eb1b45771ad4..1b0b38ea6e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -37,6 +37,7 @@ class PatternBouncerViewModel(
private val applicationContext: Context,
applicationScope: CoroutineScope,
private val interactor: BouncerInteractor,
+ override val isInputEnabled: StateFlow<Boolean>,
) : AuthMethodBouncerViewModel {
/** The number of columns in the dot grid. */
@@ -63,6 +64,16 @@ class PatternBouncerViewModel(
/** All dots on the grid. */
val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow()
+ /** Whether the pattern itself should be rendered visibly. */
+ val isPatternVisible: StateFlow<Boolean> =
+ interactor.authenticationMethod
+ .map { authMethod -> isPatternVisible(authMethod) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = isPatternVisible(interactor.authenticationMethod.value),
+ )
+
/** Notifies that the UI has been shown to the user. */
fun onShown() {
interactor.resetMessage()
@@ -146,6 +157,10 @@ class PatternBouncerViewModel(
_selectedDots.value = linkedSetOf()
}
+ private fun isPatternVisible(authMethodModel: AuthenticationMethodModel): Boolean {
+ return (authMethodModel as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false
+ }
+
private fun defaultDots(): List<PatternDotViewModel> {
return buildList {
(0 until columnCount).forEach { x ->
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index f9223cb0872e..2a733d93b857 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -33,6 +33,7 @@ import kotlinx.coroutines.launch
class PinBouncerViewModel(
private val applicationScope: CoroutineScope,
private val interactor: BouncerInteractor,
+ override val isInputEnabled: StateFlow<Boolean>,
) : AuthMethodBouncerViewModel {
private val entered = MutableStateFlow<List<Int>>(emptyList())
diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
index a334c1ed2338..0bdc7f1dfb96 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
@@ -77,6 +77,10 @@ public class ComplicationTypesUpdater extends ConditionalCoreStartable {
Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
settingsObserver,
UserHandle.myUserId());
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ settingsObserver,
+ UserHandle.myUserId());
settingsObserver.onChange(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 61d6fede4bab..d8121926adba 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -100,6 +100,16 @@ object Flags {
val SENSITIVE_REVEAL_ANIM =
unreleasedFlag(268005230, "sensitive_reveal_anim", teamfood = true)
+ // TODO(b/280783617): Tracking Bug
+ @Keep
+ @JvmField
+ val BUILDER_EXTRAS_OVERRIDE =
+ sysPropBooleanFlag(
+ 128,
+ "persist.sysui.notification.builder_extras_override",
+ default = false
+ )
+
// 200 - keyguard/lockscreen
// ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -132,7 +142,7 @@ object Flags {
* the digits when the clock moves.
*/
@JvmField
- val STEP_CLOCK_ANIMATION = unreleasedFlag(212, "step_clock_animation", teamfood = true)
+ val STEP_CLOCK_ANIMATION = releasedFlag(212, "step_clock_animation")
/**
* Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
@@ -213,6 +223,7 @@ object Flags {
)
/** Whether to use a new data source for intents to run on keyguard dismissal. */
+ // TODO(b/275069969): Tracking bug.
@JvmField
val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent")
@@ -236,13 +247,18 @@ object Flags {
/** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
// TODO(b/279794160): Tracking bug.
@JvmField
- val DELAY_BOUNCER = releasedFlag(235, "delay_bouncer")
+ val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer")
/** Migrate the indication area to the new keyguard root view. */
// TODO(b/280067944): Tracking bug.
@JvmField
val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area")
+ /** Whether to listen for fingerprint authentication over keyguard occluding activities. */
+ // TODO(b/283260512): Tracking bug.
+ @JvmField
+ val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag(237, "fp_listen_occluding_apps")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -515,13 +531,6 @@ object Flags {
val ENABLE_PIP_APP_ICON_OVERLAY =
sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true)
- // TODO(b/272110828): Tracking bug
- @Keep
- @JvmField
- val ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
- sysPropBooleanFlag(
- 1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = true)
-
// TODO(b/273443374): Tracking Bug
@Keep
@JvmField val LOCKSCREEN_LIVE_WALLPAPER =
@@ -605,8 +614,6 @@ object Flags {
unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false)
// 1500 - chooser aka sharesheet
- // TODO(b/254512507): Tracking Bug
- val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled")
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
@@ -717,4 +724,12 @@ object Flags {
@JvmField
val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION =
unreleasedFlag(2805, "split_shade_subpixel_optimization", teamfood = true)
+
+ // TODO(b/278761837): Tracking Bug
+ @JvmField
+ val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
+
+ // TODO(b/283084712): Tracking Bug
+ @JvmField
+ val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 54da680d8a68..51a29b00f9db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -19,7 +19,6 @@ package com.android.systemui.keyguard;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
@@ -30,13 +29,11 @@ import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.TransitionFlags;
import static android.view.WindowManager.TransitionOldType;
import static android.view.WindowManager.TransitionType;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.Service;
@@ -116,6 +113,14 @@ public class KeyguardService extends Service {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
final int taskId = taskInfo != null ? change.getTaskInfo().taskId : -1;
+ if (taskId != -1 && change.getParent() != null) {
+ final TransitionInfo.Change parentChange = info.getChange(change.getParent());
+ if (parentChange != null && parentChange.getTaskInfo() != null) {
+ // Only adding the root task as the animation target.
+ continue;
+ }
+ }
+
final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
// wallpapers go into the "below" layer space
info.getChanges().size() - i,
@@ -123,13 +128,6 @@ public class KeyguardService extends Service {
(change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0,
info, t, leashMap);
- // Use hasAnimatingParent to mark the anything below root task
- if (taskId != -1 && change.getParent() != null) {
- final TransitionInfo.Change parentChange = info.getChange(change.getParent());
- if (parentChange != null && parentChange.getTaskInfo() != null) {
- target.hasAnimatingParent = true;
- }
- }
out.add(target);
}
return out.toArray(new RemoteAnimationTarget[out.size()]);
@@ -173,18 +171,15 @@ public class KeyguardService extends Service {
wrap(info, true /* wallpapers */, t, mLeashMap);
final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0];
- // Sets the alpha to 0 for the opening root task for fade in animation. And since
- // the fade in animation can only apply on the first opening app, so set alpha to 1
- // for anything else.
- for (RemoteAnimationTarget target : apps) {
- if (target.taskId != -1
- && target.mode == RemoteAnimationTarget.MODE_OPENING
- && !target.hasAnimatingParent) {
- t.setAlpha(target.leash, 0.0f);
- } else {
- t.setAlpha(target.leash, 1.0f);
+ // Set alpha back to 1 for the independent changes because we will be animating
+ // children instead.
+ for (TransitionInfo.Change chg : info.getChanges()) {
+ if (TransitionInfo.isIndependent(chg, info)) {
+ t.setAlpha(chg.getLeash(), 1.f);
}
}
+ initAlphaForAnimationTargets(t, apps);
+ initAlphaForAnimationTargets(t, wallpapers);
t.apply();
synchronized (mFinishCallbacks) {
mFinishCallbacks.put(transition, finishCallback);
@@ -223,6 +218,14 @@ public class KeyguardService extends Service {
// nothing, we'll just let it finish on its own I guess.
}
}
+
+ private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t,
+ @NonNull RemoteAnimationTarget[] targets) {
+ for (RemoteAnimationTarget target : targets) {
+ if (target.mode != MODE_OPENING) continue;
+ t.setAlpha(target.leash, 0.f);
+ }
+ }
};
}
@@ -333,15 +336,29 @@ public class KeyguardService extends Service {
};
private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() {
+ private static final String TRACK_NAME = "IKeyguardService";
+
+ /**
+ * Helper for tracing the most-recent call on the IKeyguardService interface.
+ * IKeyguardService is oneway, so we are most interested in the order of the calls as they
+ * are received. We use an async track to make it easier to visualize in the trace.
+ * @param name name of the trace section
+ */
+ private static void trace(String name) {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, 0);
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, name, 0);
+ }
@Override // Binder interface
public void addStateMonitorCallback(IKeyguardStateCallback callback) {
+ trace("addStateMonitorCallback");
checkPermission();
mKeyguardViewMediator.addStateMonitorCallback(callback);
}
@Override // Binder interface
public void verifyUnlock(IKeyguardExitCallback callback) {
+ trace("verifyUnlock");
Trace.beginSection("KeyguardService.mBinder#verifyUnlock");
checkPermission();
mKeyguardViewMediator.verifyUnlock(callback);
@@ -350,6 +367,7 @@ public class KeyguardService extends Service {
@Override // Binder interface
public void setOccluded(boolean isOccluded, boolean animate) {
+ trace("setOccluded isOccluded=" + isOccluded + " animate=" + animate);
Log.d(TAG, "setOccluded(" + isOccluded + ")");
Trace.beginSection("KeyguardService.mBinder#setOccluded");
@@ -360,24 +378,28 @@ public class KeyguardService extends Service {
@Override // Binder interface
public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
+ trace("dismiss message=" + message);
checkPermission();
mKeyguardViewMediator.dismiss(callback, message);
}
@Override // Binder interface
public void onDreamingStarted() {
+ trace("onDreamingStarted");
checkPermission();
mKeyguardViewMediator.onDreamingStarted();
}
@Override // Binder interface
public void onDreamingStopped() {
+ trace("onDreamingStopped");
checkPermission();
mKeyguardViewMediator.onDreamingStopped();
}
@Override // Binder interface
public void onStartedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) {
+ trace("onStartedGoingToSleep pmSleepReason=" + pmSleepReason);
checkPermission();
mKeyguardViewMediator.onStartedGoingToSleep(
WindowManagerPolicyConstants.translateSleepReasonToOffReason(pmSleepReason));
@@ -388,6 +410,8 @@ public class KeyguardService extends Service {
@Override // Binder interface
public void onFinishedGoingToSleep(
@PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) {
+ trace("onFinishedGoingToSleep pmSleepReason=" + pmSleepReason
+ + " cameraGestureTriggered=" + cameraGestureTriggered);
checkPermission();
mKeyguardViewMediator.onFinishedGoingToSleep(
WindowManagerPolicyConstants.translateSleepReasonToOffReason(pmSleepReason),
@@ -399,6 +423,8 @@ public class KeyguardService extends Service {
@Override // Binder interface
public void onStartedWakingUp(
@PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
+ trace("onStartedWakingUp pmWakeReason=" + pmWakeReason
+ + " cameraGestureTriggered=" + cameraGestureTriggered);
Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp");
checkPermission();
mKeyguardViewMediator.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
@@ -409,6 +435,7 @@ public class KeyguardService extends Service {
@Override // Binder interface
public void onFinishedWakingUp() {
+ trace("onFinishedWakingUp");
Trace.beginSection("KeyguardService.mBinder#onFinishedWakingUp");
checkPermission();
mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.FINISHED_WAKING_UP);
@@ -417,6 +444,7 @@ public class KeyguardService extends Service {
@Override // Binder interface
public void onScreenTurningOn(IKeyguardDrawnCallback callback) {
+ trace("onScreenTurningOn");
Trace.beginSection("KeyguardService.mBinder#onScreenTurningOn");
checkPermission();
mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNING_ON,
@@ -451,6 +479,7 @@ public class KeyguardService extends Service {
@Override // Binder interface
public void onScreenTurnedOn() {
+ trace("onScreenTurnedOn");
Trace.beginSection("KeyguardService.mBinder#onScreenTurnedOn");
checkPermission();
mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNED_ON);
@@ -460,12 +489,14 @@ public class KeyguardService extends Service {
@Override // Binder interface
public void onScreenTurningOff() {
+ trace("onScreenTurningOff");
checkPermission();
mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNING_OFF);
}
@Override // Binder interface
public void onScreenTurnedOff() {
+ trace("onScreenTurnedOff");
checkPermission();
mKeyguardViewMediator.onScreenTurnedOff();
mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNED_OFF);
@@ -474,12 +505,14 @@ public class KeyguardService extends Service {
@Override // Binder interface
public void setKeyguardEnabled(boolean enabled) {
+ trace("setKeyguardEnabled enabled" + enabled);
checkPermission();
mKeyguardViewMediator.setKeyguardEnabled(enabled);
}
@Override // Binder interface
public void onSystemReady() {
+ trace("onSystemReady");
Trace.beginSection("KeyguardService.mBinder#onSystemReady");
checkPermission();
mKeyguardViewMediator.onSystemReady();
@@ -488,24 +521,28 @@ public class KeyguardService extends Service {
@Override // Binder interface
public void doKeyguardTimeout(Bundle options) {
+ trace("doKeyguardTimeout");
checkPermission();
mKeyguardViewMediator.doKeyguardTimeout(options);
}
@Override // Binder interface
public void setSwitchingUser(boolean switching) {
+ trace("setSwitchingUser switching=" + switching);
checkPermission();
mKeyguardViewMediator.setSwitchingUser(switching);
}
@Override // Binder interface
public void setCurrentUser(int userId) {
+ trace("setCurrentUser userId=" + userId);
checkPermission();
mKeyguardViewMediator.setCurrentUser(userId);
}
- @Override
+ @Override // Binder interface
public void onBootCompleted() {
+ trace("onBootCompleted");
checkPermission();
mKeyguardViewMediator.onBootCompleted();
}
@@ -515,28 +552,33 @@ public class KeyguardService extends Service {
* {@code IRemoteAnimationRunner#onAnimationStart} instead.
*/
@Deprecated
- @Override
+ @Override // Binder interface
public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+ trace("startKeyguardExitAnimation startTime=" + startTime
+ + " fadeoutDuration=" + fadeoutDuration);
Trace.beginSection("KeyguardService.mBinder#startKeyguardExitAnimation");
checkPermission();
mKeyguardViewMediator.startKeyguardExitAnimation(startTime, fadeoutDuration);
Trace.endSection();
}
- @Override
+ @Override // Binder interface
public void onShortPowerPressedGoHome() {
+ trace("onShortPowerPressedGoHome");
checkPermission();
mKeyguardViewMediator.onShortPowerPressedGoHome();
}
- @Override
+ @Override // Binder interface
public void dismissKeyguardToLaunch(Intent intentToLaunch) {
+ trace("dismissKeyguardToLaunch");
checkPermission();
mKeyguardViewMediator.dismissKeyguardToLaunch(intentToLaunch);
}
- @Override
+ @Override // Binder interface
public void onSystemKeyPressed(int keycode) {
+ trace("onSystemKeyPressed keycode=" + keycode);
checkPermission();
mKeyguardViewMediator.onSystemKeyPressed(keycode);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index f96f337d5cb2..122e25975837 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -812,8 +812,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
// Translate up from the bottom.
surfaceBehindMatrix.setTranslate(
- surfaceBehindRemoteAnimationTarget.localBounds.left.toFloat(),
- surfaceBehindRemoteAnimationTarget.localBounds.top.toFloat() +
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index d8affa4d6c21..1978b3d048b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -139,7 +139,8 @@ constructor(
if (dozeDisabledAndScreenOff || dozeEnabledAndDozeAnimationCompleted) {
Trace.beginSection("ResourceTrimmer#trimMemory")
Log.d(LOG_TAG, "SysUI asleep, trimming memory.")
- globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+ globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
Trace.endSection()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
new file mode 100644
index 000000000000..641e20b4a3fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class KeyguardClockRepository
+@Inject
+constructor(
+ private val secureSettings: SecureSettings,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+
+ val selectedClockSize: Flow<SettingsClockSize> =
+ secureSettings
+ .observerFlow(
+ names = arrayOf(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
+ userId = UserHandle.USER_SYSTEM,
+ )
+ .onStart { emit(Unit) } // Forces an initial update.
+ .map { getClockSize() }
+
+ private suspend fun getClockSize(): SettingsClockSize {
+ return withContext(backgroundDispatcher) {
+ if (
+ secureSettings.getIntForUser(
+ Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
+ 1,
+ UserHandle.USER_CURRENT
+ ) == 1
+ ) {
+ SettingsClockSize.DYNAMIC
+ } else {
+ SettingsClockSize.SMALL
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
new file mode 100644
index 000000000000..98f445c4419a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business-logic related to the keyguard clock. */
+@SysUISingleton
+class KeyguardClockInteractor
+@Inject
+constructor(
+ repository: KeyguardClockRepository,
+) {
+ val selectedClockSize: Flow<SettingsClockSize> = repository.selectedClockSize
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt
new file mode 100644
index 000000000000..c6b0f58a0cd3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.shared.model
+
+enum class SettingsClockSize {
+ DYNAMIC,
+ SMALL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt
new file mode 100644
index 000000000000..57c32b3a56d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.ui.binder
+
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.flow.collect
+
+/** Binder for the small clock view, large clock view and smartspace. */
+object KeyguardPreviewClockSmartspaceViewBinder {
+
+ @JvmStatic
+ fun bind(
+ largeClockHostView: View,
+ smallClockHostView: View,
+ smartspace: View?,
+ viewModel: KeyguardPreviewClockSmartspaceViewModel,
+ ) {
+ largeClockHostView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.isLargeClockVisible.collect { largeClockHostView.isVisible = it }
+ }
+ }
+
+ smallClockHostView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.isSmallClockVisible.collect { smallClockHostView.isVisible = it }
+ }
+ }
+
+ smartspace?.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.smartSpaceTopPadding.collect { smartspace.setTopPadding(it) }
+ }
+ }
+ }
+
+ private fun View.setTopPadding(padding: Int) {
+ setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 555a09baa5b7..4308d843c27a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -22,6 +22,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.res.Resources
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.os.Bundle
@@ -33,6 +34,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
+import androidx.core.view.isInvisible
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.R
@@ -40,7 +42,10 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockSmartspaceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel
+import com.android.systemui.plugins.ClockController
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.shared.clocks.DefaultClockController
import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
@@ -60,6 +65,7 @@ constructor(
@Application private val context: Context,
@Main private val mainDispatcher: CoroutineDispatcher,
@Main private val mainHandler: Handler,
+ private val clockSmartspaceViewModel: KeyguardPreviewClockSmartspaceViewModel,
private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
displayManager: DisplayManager,
private val windowManager: WindowManager,
@@ -79,6 +85,7 @@ constructor(
KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
false,
)
+ /** [shouldHideClock] here means that we never create and bind the clock views */
private val shouldHideClock: Boolean =
bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false)
@@ -87,7 +94,8 @@ constructor(
val surfacePackage: SurfaceControlViewHost.SurfacePackage
get() = host.surfacePackage
- private var clockView: View? = null
+ private lateinit var largeClockHostView: FrameLayout
+ private lateinit var smallClockHostView: FrameLayout
private var smartSpaceView: View? = null
private var colorOverride: Int? = null
@@ -126,6 +134,12 @@ constructor(
if (!shouldHideClock) {
setUpClock(rootView)
+ KeyguardPreviewClockSmartspaceViewBinder.bind(
+ largeClockHostView,
+ smallClockHostView,
+ smartSpaceView,
+ clockSmartspaceViewModel,
+ )
}
rootView.measure(
@@ -205,11 +219,9 @@ constructor(
smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
val topPadding: Int =
- with(context.resources) {
- getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
- getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
- getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
- }
+ KeyguardPreviewClockSmartspaceViewModel.getLargeClockSmartspaceTopPadding(
+ context.resources
+ )
val startPadding: Int =
with(context.resources) {
@@ -284,10 +296,19 @@ constructor(
}
private fun setUpClock(parentView: ViewGroup) {
+ largeClockHostView = createLargeClockHostView()
+ largeClockHostView.isInvisible = true
+ parentView.addView(largeClockHostView)
+
+ smallClockHostView = createSmallClockHostView(parentView.resources)
+ smallClockHostView.isInvisible = true
+ parentView.addView(smallClockHostView)
+
+ // TODO (b/283465254): Move the listeners to KeyguardClockRepository
val clockChangeListener =
object : ClockRegistry.ClockChangeListener {
override fun onCurrentClockChanged() {
- onClockChanged(parentView)
+ onClockChanged()
}
}
clockRegistry.registerClockChangeListener(clockChangeListener)
@@ -317,62 +338,89 @@ constructor(
disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
val layoutChangeListener =
- object : View.OnLayoutChangeListener {
- override fun onLayoutChange(
- v: View,
- left: Int,
- top: Int,
- right: Int,
- bottom: Int,
- oldLeft: Int,
- oldTop: Int,
- oldRight: Int,
- oldBottom: Int
- ) {
- if (clockController.clock !is DefaultClockController) {
- clockController.clock
- ?.largeClock
- ?.events
- ?.onTargetRegionChanged(
- KeyguardClockSwitch.getLargeClockRegion(parentView)
- )
- }
+ View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+ if (clockController.clock !is DefaultClockController) {
+ clockController.clock
+ ?.largeClock
+ ?.events
+ ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
}
}
-
parentView.addOnLayoutChangeListener(layoutChangeListener)
-
disposables.add(
DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
)
- onClockChanged(parentView)
+ onClockChanged()
+ }
+
+ private fun createLargeClockHostView(): FrameLayout {
+ val hostView = FrameLayout(context)
+ hostView.layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ )
+ return hostView
+ }
+
+ private fun createSmallClockHostView(resources: Resources): FrameLayout {
+ val hostView = FrameLayout(context)
+ val layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ resources.getDimensionPixelSize(R.dimen.small_clock_height)
+ )
+ layoutParams.topMargin =
+ KeyguardPreviewClockSmartspaceViewModel.getStatusBarHeight(resources) +
+ resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
+ hostView.layoutParams = layoutParams
+
+ hostView.setPaddingRelative(
+ resources.getDimensionPixelSize(R.dimen.clock_padding_start),
+ 0,
+ 0,
+ 0
+ )
+ hostView.clipChildren = false
+ return hostView
}
- private fun onClockChanged(parentView: ViewGroup) {
+ private fun onClockChanged() {
val clock = clockRegistry.createCurrentClock()
clockController.clock = clock
colorOverride?.let { clock.events.onSeedColorChanged(it) }
- clock.largeClock.events.onTargetRegionChanged(
- KeyguardClockSwitch.getLargeClockRegion(parentView)
- )
-
- clockView?.let { parentView.removeView(it) }
- clockView =
- clock.largeClock.view.apply {
- if (shouldHighlightSelectedAffordance) {
- alpha = DIM_ALPHA
- }
- parentView.addView(this)
- visibility = View.VISIBLE
- }
+ updateLargeClock(clock)
+ updateSmallClock(clock)
// Hide smart space if the clock has weather display; otherwise show it
hideSmartspace(clock.largeClock.config.hasCustomWeatherDataDisplay)
}
+ private fun updateLargeClock(clock: ClockController) {
+ clock.largeClock.events.onTargetRegionChanged(
+ KeyguardClockSwitch.getLargeClockRegion(largeClockHostView)
+ )
+ if (shouldHighlightSelectedAffordance) {
+ clock.largeClock.view.alpha = DIM_ALPHA
+ }
+ largeClockHostView.removeAllViews()
+ largeClockHostView.addView(clock.largeClock.view)
+ }
+
+ private fun updateSmallClock(clock: ClockController) {
+ clock.smallClock.events.onTargetRegionChanged(
+ KeyguardClockSwitch.getSmallClockRegion(smallClockHostView)
+ )
+ if (shouldHighlightSelectedAffordance) {
+ clock.smallClock.view.alpha = DIM_ALPHA
+ }
+ smallClockHostView.removeAllViews()
+ smallClockHostView.addView(clock.smallClock.view)
+ }
+
companion object {
private const val KEY_HOST_TOKEN = "host_token"
private const val KEY_VIEW_WIDTH = "width"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt
new file mode 100644
index 000000000000..00c603b04ccf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.ui.viewmodel
+
+import android.content.Context
+import android.content.res.Resources
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** View model for the small clock view, large clock view and smartspace. */
+class KeyguardPreviewClockSmartspaceViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ interactor: KeyguardClockInteractor,
+) {
+
+ val isLargeClockVisible: Flow<Boolean> =
+ interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC }
+
+ val isSmallClockVisible: Flow<Boolean> =
+ interactor.selectedClockSize.map { it == SettingsClockSize.SMALL }
+
+ val smartSpaceTopPadding: Flow<Int> =
+ interactor.selectedClockSize.map {
+ when (it) {
+ SettingsClockSize.DYNAMIC -> getLargeClockSmartspaceTopPadding(context.resources)
+ SettingsClockSize.SMALL -> getSmallClockSmartspaceTopPadding(context.resources)
+ }
+ }
+
+ companion object {
+ fun getLargeClockSmartspaceTopPadding(resources: Resources): Int {
+ return with(resources) {
+ getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
+ getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
+ getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
+ }
+ }
+
+ fun getSmallClockSmartspaceTopPadding(resources: Resources): Int {
+ return with(resources) {
+ getStatusBarHeight(this) +
+ getDimensionPixelSize(R.dimen.small_clock_padding_top) +
+ getDimensionPixelSize(R.dimen.small_clock_height)
+ }
+ }
+
+ fun getStatusBarHeight(resource: Resources): Int {
+ var result = 0
+ val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android")
+ if (resourceId > 0) {
+ result = resource.getDimensionPixelSize(resourceId)
+ }
+ return result
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 1469d96dd3e8..bce334610f28 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -16,10 +16,12 @@
package com.android.systemui.media.controls.pipeline
+import android.annotation.SuppressLint
import android.app.BroadcastOptions
import android.app.Notification
import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
import android.app.PendingIntent
+import android.app.StatusBarManager
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
@@ -43,7 +45,6 @@ import android.media.session.PlaybackState
import android.net.Uri
import android.os.Parcelable
import android.os.Process
-import android.os.RemoteException
import android.os.UserHandle
import android.provider.Settings
import android.service.notification.StatusBarNotification
@@ -53,7 +54,6 @@ import android.util.Log
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
import com.android.internal.logging.InstanceId
-import com.android.internal.statusbar.IStatusBarService
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Dumpable
import com.android.systemui.R
@@ -185,7 +185,6 @@ class MediaDataManager(
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val statusBarService: IStatusBarService,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -230,6 +229,10 @@ class MediaDataManager(
private val artworkHeight =
context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
+ @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE
+ private val statusBarManager =
+ context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager
+
/** Check whether this notification is an RCN */
private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean {
return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
@@ -257,7 +260,6 @@ class MediaDataManager(
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager,
- statusBarService: IStatusBarService,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : this(
context,
@@ -283,7 +285,6 @@ class MediaDataManager(
logger,
smartspaceManager,
keyguardUpdateMonitor,
- statusBarService,
)
private val appChangeReceiver =
@@ -793,27 +794,12 @@ class MediaDataManager(
song = HybridGroupManager.resolveTitle(notif)
}
if (song.isNullOrBlank()) {
- if (mediaFlags.isMediaTitleRequired(sbn.packageName, sbn.user)) {
- // App is required to provide a title: cancel the underlying notification
- try {
- statusBarService.onNotificationError(
- sbn.packageName,
- sbn.tag,
- sbn.id,
- sbn.uid,
- sbn.initialPid,
- MEDIA_TITLE_ERROR_MESSAGE,
- sbn.user.identifier
- )
- } catch (e: RemoteException) {
- Log.e(TAG, "cancelNotification failed: $e")
- }
- // Only add log for media removed if active media is updated with invalid title.
- foregroundExecutor.execute { removeEntry(key, !isNewlyActiveEntry) }
- return
- } else {
- // For apps that don't have the title requirement yet, add a placeholder
- song = context.getString(R.string.controls_media_empty_title, appName)
+ // For apps that don't include a title, log and add a placeholder
+ song = context.getString(R.string.controls_media_empty_title, appName)
+ try {
+ statusBarManager.logBlankMediaTitle(sbn.packageName, sbn.user.identifier)
+ } catch (e: RuntimeException) {
+ Log.e(TAG, "Error reporting blank media title for package ${sbn.packageName}")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 3751c60b06d5..9bc66f6c98d0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -64,9 +64,4 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
/** Check whether we allow remote media to generate resume controls */
fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
-
- /** Check whether app is required to provide a non-empty media title */
- fun isMediaTitleRequired(packageName: String, user: UserHandle): Boolean {
- return StatusBarManager.isMediaTitleRequiredForApp(packageName, user)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index bbc86c839ad2..cfb581b67757 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -39,6 +39,7 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
+import android.icu.text.SimpleDateFormat;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -101,7 +102,9 @@ import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Date;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -287,6 +290,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private LogArray mPredictionLog = new LogArray(MAX_NUM_LOGGED_PREDICTIONS);
private LogArray mGestureLogInsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES);
private LogArray mGestureLogOutsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES);
+ private SimpleDateFormat mLogDateFormat = new SimpleDateFormat("HH:mm:ss.SSS", Locale.US);
+ private Date mTmpLogDate = new Date();
private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
@@ -1036,11 +1041,17 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
// For debugging purposes, only log edge points
+ long curTime = System.currentTimeMillis();
+ mTmpLogDate.setTime(curTime);
+ String curTimeStr = mLogDateFormat.format(mTmpLogDate);
(isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format(
- "Gesture [%d,alw=%B,%B,%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]",
- System.currentTimeMillis(), isTrackpadMultiFingerSwipe, mAllowGesture,
+ "Gesture [%d [%s],alw=%B, mltf=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B,"
+ + " qsDisbld=%b, blkdAct=%B, pip=%B,"
+ + " disp=%s, wl=%d, il=%d, wr=%d, ir=%d, excl=%s]",
+ curTime, curTimeStr, mAllowGesture, isTrackpadMultiFingerSwipe,
mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
- QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisplaySize,
+ QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisabledForQuickstep,
+ mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
} else if (mAllowGesture || mLogGesture) {
if (!mThresholdCrossed) {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 7627c0c22d1e..efbec29bcff1 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -288,15 +288,55 @@ constructor(
}
/**
+ * Like [updateNoteTaskAsUser] but automatically apply to the current user and all its work
+ * profiles.
+ *
+ * @see updateNoteTaskAsUser
+ * @see UserTracker.userHandle
+ * @see UserTracker.userProfiles
+ */
+ fun updateNoteTaskForCurrentUserAndManagedProfiles() {
+ updateNoteTaskAsUser(userTracker.userHandle)
+ for (profile in userTracker.userProfiles) {
+ if (userManager.isManagedProfile(profile.id)) {
+ updateNoteTaskAsUser(profile.userHandle)
+ }
+ }
+ }
+
+ /**
* Updates all [NoteTaskController] related information, including but not exclusively the
* widget shortcut created by the [user] - by default it will use the current user.
*
+ * If the user is not current user, the update will be dispatched to run in that user's process.
+ *
* Keep in mind the shortcut API has a
* [rate limiting](https://developer.android.com/develop/ui/views/launch/shortcuts/managing-shortcuts#rate-limiting)
* and may not be updated in real-time. To reduce the chance of stale shortcuts, we run the
* function during System UI initialization.
*/
fun updateNoteTaskAsUser(user: UserHandle) {
+ if (!userManager.isUserUnlocked(user)) {
+ debugLog { "updateNoteTaskAsUser call but user locked: user=$user" }
+ return
+ }
+
+ if (user == userTracker.userHandle) {
+ updateNoteTaskAsUserInternal(user)
+ } else {
+ // TODO(b/278729185): Replace fire and forget service with a bounded service.
+ val intent = NoteTaskControllerUpdateService.createIntent(context)
+ context.startServiceAsUser(intent, user)
+ }
+ }
+
+ @InternalNoteTaskApi
+ fun updateNoteTaskAsUserInternal(user: UserHandle) {
+ if (!userManager.isUserUnlocked(user)) {
+ debugLog { "updateNoteTaskAsUserInternal call but user locked: user=$user" }
+ return
+ }
+
val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user)
val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty()
@@ -314,18 +354,8 @@ constructor(
/** @see OnRoleHoldersChangedListener */
fun onRoleHoldersChanged(roleName: String, user: UserHandle) {
if (roleName != ROLE_NOTES) return
- if (!userManager.isUserUnlocked(user)) {
- debugLog { "onRoleHoldersChanged call but user locked: role=$roleName, user=$user" }
- return
- }
- if (user == userTracker.userHandle) {
- updateNoteTaskAsUser(user)
- } else {
- // TODO(b/278729185): Replace fire and forget service with a bounded service.
- val intent = NoteTaskControllerUpdateService.createIntent(context)
- context.startServiceAsUser(intent, user)
- }
+ updateNoteTaskAsUser(user)
}
private val SecureSettings.preferredUser: UserHandle
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
index 26b35cc8ffd1..3e352afe3832 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
@@ -44,7 +44,7 @@ constructor(
override fun onCreate() {
super.onCreate()
// TODO(b/278729185): Replace fire and forget service with a bounded service.
- controller.updateNoteTaskAsUser(user)
+ controller.updateNoteTaskAsUserInternal(user)
stopSelf()
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 221ff65e4dfe..fe1034a6aa32 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -15,7 +15,10 @@
*/
package com.android.systemui.notetask
+import android.app.role.OnRoleHoldersChangedListener
import android.app.role.RoleManager
+import android.content.Context
+import android.content.pm.UserInfo
import android.os.UserHandle
import android.view.KeyEvent
import android.view.KeyEvent.KEYCODE_N
@@ -54,6 +57,7 @@ constructor(
initializeHandleSystemKey()
initializeOnRoleHoldersChanged()
initializeOnUserUnlocked()
+ initializeUserTracker()
}
/**
@@ -79,7 +83,7 @@ constructor(
private fun initializeOnRoleHoldersChanged() {
roleManager.addOnRoleHoldersChangedListenerAsUser(
backgroundExecutor,
- controller::onRoleHoldersChanged,
+ callbacks,
UserHandle.ALL,
)
}
@@ -93,18 +97,41 @@ constructor(
*/
private fun initializeOnUserUnlocked() {
if (keyguardUpdateMonitor.isUserUnlocked(userTracker.userId)) {
- controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle)
- } else {
- keyguardUpdateMonitor.registerCallback(onUserUnlockedCallback)
+ controller.updateNoteTaskForCurrentUserAndManagedProfiles()
}
+ keyguardUpdateMonitor.registerCallback(callbacks)
}
- // KeyguardUpdateMonitor.registerCallback uses a weak reference, so we need a hard reference.
- private val onUserUnlockedCallback =
- object : KeyguardUpdateMonitorCallback() {
+ private fun initializeUserTracker() {
+ userTracker.addCallback(callbacks, backgroundExecutor)
+ }
+
+ // Some callbacks use a weak reference, so we play safe and keep a hard reference to them all.
+ private val callbacks =
+ object :
+ KeyguardUpdateMonitorCallback(),
+ CommandQueue.Callbacks,
+ UserTracker.Callback,
+ OnRoleHoldersChangedListener {
+
+ override fun handleSystemKey(key: KeyEvent) {
+ key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask)
+ }
+
+ override fun onRoleHoldersChanged(roleName: String, user: UserHandle) {
+ controller.onRoleHoldersChanged(roleName, user)
+ }
+
override fun onUserUnlocked() {
- controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle)
- keyguardUpdateMonitor.removeCallback(this)
+ controller.updateNoteTaskForCurrentUserAndManagedProfiles()
+ }
+
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ controller.updateNoteTaskForCurrentUserAndManagedProfiles()
+ }
+
+ override fun onProfilesChanged(profiles: List<UserInfo>) {
+ controller.updateNoteTaskForCurrentUserAndManagedProfiles()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 6d7455f2b259..752471d83735 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -16,12 +16,16 @@
package com.android.systemui.scene
+import com.android.systemui.scene.data.model.SceneContainerConfigModule
import com.android.systemui.scene.ui.composable.SceneModule
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModelModule
import dagger.Module
@Module(
includes =
[
+ SceneContainerConfigModule::class,
+ SceneContainerViewModelModule::class,
SceneModule::class,
],
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt
new file mode 100644
index 000000000000..0af80949f95e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.scene.data.model
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.shared.model.SceneContainerNames
+import com.android.systemui.scene.shared.model.SceneKey
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module
+object SceneContainerConfigModule {
+
+ @Provides
+ fun containerConfigs(): Map<String, SceneContainerConfig> {
+ return mapOf(
+ SceneContainerNames.SYSTEM_UI_DEFAULT to
+ SceneContainerConfig(
+ name = SceneContainerNames.SYSTEM_UI_DEFAULT,
+ // Note that this list is in z-order. The first one is the bottom-most and the
+ // last
+ // one is top-most.
+ sceneKeys =
+ listOf(
+ SceneKey.Gone,
+ SceneKey.Lockscreen,
+ SceneKey.Bouncer,
+ SceneKey.Shade,
+ SceneKey.QuickSettings,
+ ),
+ initialSceneKey = SceneKey.Lockscreen,
+ ),
+ )
+ }
+
+ @Provides
+ @SysUISingleton
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+ fun provideDefaultSceneContainerConfig(
+ configs: Map<String, SceneContainerConfig>,
+ ): SceneContainerConfig {
+ return checkNotNull(configs[SceneContainerNames.SYSTEM_UI_DEFAULT]) {
+ "No SceneContainerConfig named \"${SceneContainerNames.SYSTEM_UI_DEFAULT}\"."
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt
new file mode 100644
index 000000000000..64f5087d99bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+object SceneContainerNames {
+ const val SYSTEM_UI_DEFAULT = "system_ui"
+}
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 a4daafccd75b..8c1ad9b4571b 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
@@ -19,17 +19,12 @@ package com.android.systemui.scene.ui.viewmodel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.StateFlow
/** Models UI state for a single scene container. */
-class SceneContainerViewModel
-@AssistedInject
-constructor(
+class SceneContainerViewModel(
private val interactor: SceneInteractor,
- @Assisted val containerName: String,
+ val containerName: String,
) {
/**
* Keys of all scenes in the container.
@@ -54,11 +49,4 @@ constructor(
fun setSceneTransitionProgress(progress: Float) {
interactor.setSceneTransitionProgress(containerName, progress)
}
-
- @AssistedFactory
- interface Factory {
- fun create(
- containerName: String,
- ): SceneContainerViewModel
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt
new file mode 100644
index 000000000000..100f42764322
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.scene.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneContainerNames
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module
+object SceneContainerViewModelModule {
+
+ @Provides
+ @SysUISingleton
+ @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+ fun defaultSceneContainerViewModel(
+ interactor: SceneInteractor,
+ ): SceneContainerViewModel {
+ return SceneContainerViewModel(
+ interactor = interactor,
+ containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index fb4feb8c64b4..a532195c5b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -33,7 +33,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
-import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
@@ -317,7 +316,7 @@ public class CommandQueue extends IStatusBar.Stub implements
IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed,
boolean requireConfirmation, int userId, long operationId, String opPackageName,
- long requestId, @BiometricMultiSensorMode int multiSensorConfig) {
+ long requestId) {
}
/** @see IStatusBar#onBiometricAuthenticated(int) */
@@ -956,8 +955,7 @@ public class CommandQueue extends IStatusBar.Stub implements
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, long operationId, String opPackageName, long requestId,
- @BiometricMultiSensorMode int multiSensorConfig) {
+ int userId, long operationId, String opPackageName, long requestId) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = promptInfo;
@@ -969,7 +967,6 @@ public class CommandQueue extends IStatusBar.Stub implements
args.arg6 = opPackageName;
args.argl1 = operationId;
args.argl2 = requestId;
- args.argi2 = multiSensorConfig;
mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args)
.sendToTarget();
}
@@ -1573,8 +1570,7 @@ public class CommandQueue extends IStatusBar.Stub implements
someArgs.argi1 /* userId */,
someArgs.argl1 /* operationId */,
(String) someArgs.arg6 /* opPackageName */,
- someArgs.argl2 /* requestId */,
- someArgs.argi2 /* multiSensorConfig */);
+ someArgs.argl2 /* requestId */);
}
someArgs.recycle();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 993c3801cecd..b956207190b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -354,7 +354,11 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
@Override
protected void snapChild(final View animView, final float targetLeft, float velocity) {
- superSnapChild(animView, targetLeft, velocity);
+ if (animView instanceof SwipeableView) {
+ // only perform the snapback animation on views that are swipeable inside the shade.
+ superSnapChild(animView, targetLeft, velocity);
+ }
+
mCallback.onDragCancelled(animView);
if (targetLeft == 0) {
handleMenuCoveredOrDismissed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index a6b2bd89bfa5..f26a84ba20a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -30,6 +30,8 @@ import android.view.InsetsFlags;
import android.view.ViewDebug;
import android.view.WindowInsetsController.Appearance;
+import androidx.annotation.NonNull;
+
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.Dumpable;
@@ -46,7 +48,6 @@ import com.android.systemui.util.Compile;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Date;
import javax.inject.Inject;
@@ -57,7 +58,8 @@ import javax.inject.Inject;
public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable {
private static final String TAG = "LightBarController";
- private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG;
+ private static final boolean DEBUG_LOGS = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
@@ -113,6 +115,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
private String mLastSetScrimStateLog;
private String mLastNavigationBarAppearanceChangedLog;
+ private StringBuilder mLogStringBuilder = null;
@Inject
public LightBarController(
@@ -193,35 +196,43 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
final boolean darkForTop = darkForQs || mGlobalActionsVisible;
mNavigationLight =
((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
- mLastNavigationBarAppearanceChangedLog = "onNavigationBarAppearanceChanged()"
- + " appearance=" + appearance
- + " nbModeChanged=" + nbModeChanged
- + " navigationBarMode=" + navigationBarMode
- + " navbarColorManagedByIme=" + navbarColorManagedByIme
- + " mHasLightNavigationBar=" + mHasLightNavigationBar
- + " ignoreScrimForce=" + ignoreScrimForce
- + " darkForScrim=" + darkForScrim
- + " lightForScrim=" + lightForScrim
- + " darkForQs=" + darkForQs
- + " darkForTop=" + darkForTop
- + " mNavigationLight=" + mNavigationLight
- + " last=" + last
- + " timestamp=" + new Date();
- if (DEBUG) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
+ if (DEBUG_NAVBAR) {
+ mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
+ .append("onNavigationBarAppearanceChanged()")
+ .append(" appearance=").append(appearance)
+ .append(" nbModeChanged=").append(nbModeChanged)
+ .append(" navigationBarMode=").append(navigationBarMode)
+ .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
+ .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+ .append(" ignoreScrimForce=").append(ignoreScrimForce)
+ .append(" darkForScrim=").append(darkForScrim)
+ .append(" lightForScrim=").append(lightForScrim)
+ .append(" darkForQs=").append(darkForQs)
+ .append(" darkForTop=").append(darkForTop)
+ .append(" mNavigationLight=").append(mNavigationLight)
+ .append(" last=").append(last)
+ .append(" timestamp=").append(System.currentTimeMillis())
+ .toString();
+ if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
+ }
} else {
mNavigationLight = mHasLightNavigationBar
&& (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim)
&& !mQsCustomizing;
- mLastNavigationBarAppearanceChangedLog = "onNavigationBarAppearanceChanged()"
- + " appearance=" + appearance
- + " nbModeChanged=" + nbModeChanged
- + " navigationBarMode=" + navigationBarMode
- + " navbarColorManagedByIme=" + navbarColorManagedByIme
- + " mHasLightNavigationBar=" + mHasLightNavigationBar
- + " mNavigationLight=" + mNavigationLight
- + " last=" + last
- + " timestamp=" + new Date();
- if (DEBUG) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
+ if (DEBUG_NAVBAR) {
+ mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
+ .append("onNavigationBarAppearanceChanged()")
+ .append(" appearance=").append(appearance)
+ .append(" nbModeChanged=").append(nbModeChanged)
+ .append(" navigationBarMode=").append(navigationBarMode)
+ .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
+ .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+ .append(" mNavigationLight=").append(mNavigationLight)
+ .append(" last=").append(last)
+ .append(" timestamp=").append(System.currentTimeMillis())
+ .toString();
+ if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
+ }
}
if (mNavigationLight != last) {
updateNavigation();
@@ -319,18 +330,22 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
} else {
if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
}
- mLastSetScrimStateLog = "setScrimState()"
- + " scrimState=" + scrimState
- + " scrimBehindAlpha=" + scrimBehindAlpha
- + " scrimInFrontColor=" + scrimInFrontColor
- + " forceForScrim=" + forceForScrim
- + " scrimColorIsLight=" + scrimColorIsLight
- + " mHasLightNavigationBar=" + mHasLightNavigationBar
- + " mBouncerVisible=" + mBouncerVisible
- + " mForceDarkForScrim=" + mForceDarkForScrim
- + " mForceLightForScrim=" + mForceLightForScrim
- + " timestamp=" + new Date();
- if (DEBUG) Log.d(TAG, mLastSetScrimStateLog);
+ if (DEBUG_NAVBAR) {
+ mLastSetScrimStateLog = getLogStringBuilder()
+ .append("setScrimState()")
+ .append(" scrimState=").append(scrimState)
+ .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
+ .append(" scrimInFrontColor=").append(scrimInFrontColor)
+ .append(" forceForScrim=").append(forceForScrim)
+ .append(" scrimColorIsLight=").append(scrimColorIsLight)
+ .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+ .append(" mBouncerVisible=").append(mBouncerVisible)
+ .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
+ .append(" mForceLightForScrim=").append(mForceLightForScrim)
+ .append(" timestamp=").append(System.currentTimeMillis())
+ .toString();
+ if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
+ }
} else {
boolean forceDarkForScrimLast = mForceDarkForScrim;
// For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold.
@@ -344,15 +359,28 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) {
reevaluate();
}
- mLastSetScrimStateLog = "setScrimState()"
- + " scrimState=" + scrimState
- + " scrimBehindAlpha=" + scrimBehindAlpha
- + " scrimInFrontColor=" + scrimInFrontColor
- + " mHasLightNavigationBar=" + mHasLightNavigationBar
- + " mForceDarkForScrim=" + mForceDarkForScrim
- + " timestamp=" + new Date();
- if (DEBUG) Log.d(TAG, mLastSetScrimStateLog);
+ if (DEBUG_NAVBAR) {
+ mLastSetScrimStateLog = getLogStringBuilder()
+ .append("setScrimState()")
+ .append(" scrimState=").append(scrimState)
+ .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
+ .append(" scrimInFrontColor=").append(scrimInFrontColor)
+ .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+ .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
+ .append(" timestamp=").append(System.currentTimeMillis())
+ .toString();
+ if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
+ }
+ }
+ }
+
+ @NonNull
+ private StringBuilder getLogStringBuilder() {
+ if (mLogStringBuilder == null) {
+ mLogStringBuilder = new StringBuilder();
}
+ mLogStringBuilder.setLength(0);
+ return mLogStringBuilder;
}
private static boolean isLight(int appearance, int barMode, int flag) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 5ba02fa20167..b24a69292186 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -73,7 +73,6 @@ import android.os.Message;
import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.text.InputFilter;
@@ -113,7 +112,6 @@ import androidx.annotation.Nullable;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.view.RotationPolicy;
@@ -133,15 +131,11 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.AlphaTintDrawableWrapper;
-import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.RoundedCornerProgressDrawable;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -198,9 +192,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
private ViewGroup mDialogRowsView;
private ViewGroup mRinger;
- private DeviceConfigProxy mDeviceConfigProxy;
- private Executor mExecutor;
-
/**
* Container for the top part of the dialog, which contains the ringer, the ringer drawer, the
* volume rows, and the ellipsis button. This does not include the live caption button.
@@ -290,14 +281,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
private BackgroundBlurDrawable mDialogRowsViewBackground;
private final InteractionJankMonitor mInteractionJankMonitor;
- private boolean mSeparateNotification;
-
private int mWindowGravity;
@VisibleForTesting
- int mVolumeRingerIconDrawableId;
+ final int mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on;
@VisibleForTesting
- int mVolumeRingerMuteIconDrawableId;
+ final int mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute;
private int mOriginalGravity;
private final DevicePostureController.Callback mDevicePostureControllerCallback;
@@ -315,8 +304,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
VolumePanelFactory volumePanelFactory,
ActivityStarter activityStarter,
InteractionJankMonitor interactionJankMonitor,
- DeviceConfigProxy deviceConfigProxy,
- Executor executor,
CsdWarningDialog.Factory csdWarningDialogFactory,
DevicePostureController devicePostureController,
Looper looper,
@@ -374,12 +361,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
} else {
mDevicePostureControllerCallback = null;
}
-
- mDeviceConfigProxy = deviceConfigProxy;
- mExecutor = executor;
- mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
- updateRingerModeIconSet();
}
/**
@@ -401,44 +382,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
return mWindowGravity;
}
- /**
- * If ringer and notification are the same stream (T and earlier), use notification-like bell
- * icon set.
- * If ringer and notification are separated, then use generic speaker icons.
- */
- private void updateRingerModeIconSet() {
- if (mSeparateNotification) {
- mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on;
- mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute;
- } else {
- mVolumeRingerIconDrawableId = R.drawable.ic_volume_ringer;
- mVolumeRingerMuteIconDrawableId = R.drawable.ic_volume_ringer_mute;
- }
-
- if (mRingerDrawerMuteIcon != null) {
- mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
- }
- if (mRingerDrawerNormalIcon != null) {
- mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
- }
- }
-
- /**
- * Change icon for ring stream (not ringer mode icon)
- */
- private void updateRingRowIcon() {
- Optional<VolumeRow> volumeRow = mRows.stream().filter(row -> row.stream == STREAM_RING)
- .findFirst();
- if (volumeRow.isPresent()) {
- VolumeRow volRow = volumeRow.get();
- volRow.iconRes = mSeparateNotification ? R.drawable.ic_ring_volume
- : R.drawable.ic_volume_ringer;
- volRow.iconMuteRes = mSeparateNotification ? R.drawable.ic_ring_volume_off
- : R.drawable.ic_volume_ringer_mute;
- volRow.setIcon(volRow.iconRes, mContext.getTheme());
- }
- }
-
@Override
public void onUiModeChanged() {
mContext.getTheme().applyStyle(mContext.getThemeResId(), true);
@@ -454,9 +397,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
mConfigurationController.addCallback(this);
- mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- mExecutor, this::onDeviceConfigChange);
-
if (mDevicePostureController != null) {
mDevicePostureController.addCallback(mDevicePostureControllerCallback);
}
@@ -464,31 +404,15 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
@Override
public void destroy() {
+ Log.d(TAG, "destroy() called");
mController.removeCallback(mControllerCallbackH);
mHandler.removeCallbacksAndMessages(null);
mConfigurationController.removeCallback(this);
- mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
if (mDevicePostureController != null) {
mDevicePostureController.removeCallback(mDevicePostureControllerCallback);
}
}
- /**
- * Update ringer mode icon based on the config
- */
- private void onDeviceConfigChange(DeviceConfig.Properties properties) {
- Set<String> changeSet = properties.getKeyset();
- if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
- boolean newVal = properties.getBoolean(
- SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
- if (newVal != mSeparateNotification) {
- mSeparateNotification = newVal;
- updateRingerModeIconSet();
- updateRingRowIcon();
- }
- }
- }
-
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo internalInsetsInfo) {
// Set touchable region insets on the root dialog view. This tells WindowManager that
@@ -542,6 +466,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
}
private void initDialog(int lockTaskModeState) {
+ Log.d(TAG, "initDialog: called!");
mDialog = new CustomDialog(mContext);
initDimens();
@@ -699,7 +624,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon);
mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background);
- updateRingerModeIconSet();
+ if (mRingerDrawerMuteIcon != null) {
+ mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+ }
+ if (mRingerDrawerNormalIcon != null) {
+ mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
+ }
setupRingerDrawer();
@@ -724,13 +654,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
addRow(AudioManager.STREAM_MUSIC,
R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
if (!AudioSystem.isSingleVolume(mContext)) {
- if (mSeparateNotification) {
- addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
- R.drawable.ic_ring_volume_off, true, false);
- } else {
- addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer,
- R.drawable.ic_volume_ringer, true, false);
- }
+
+ addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
+ R.drawable.ic_ring_volume_off, true, false);
+
addRow(STREAM_ALARM,
R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false);
@@ -1344,7 +1271,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
}
protected void tryToRemoveCaptionsTooltip() {
- if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null) {
+ if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null && mDialog != null) {
ViewGroup container = mDialog.findViewById(R.id.volume_dialog_container);
container.removeView(mODICaptionsTooltipView);
mODICaptionsTooltipView = null;
@@ -1551,8 +1478,16 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
mHandler.removeMessages(H.DISMISS);
mHandler.removeMessages(H.SHOW);
- if (mIsAnimatingDismiss) {
- Log.d(TAG, "dismissH: isAnimatingDismiss");
+
+ boolean showingStateInconsistent = !mShowing && mDialog != null && mDialog.isShowing();
+ // If incorrectly assuming dialog is not showing, continue and make the state consistent.
+ if (showingStateInconsistent) {
+ Log.d(TAG, "dismissH: volume dialog possible in inconsistent state:"
+ + "mShowing=" + mShowing + ", mDialog==null?" + (mDialog == null));
+ }
+ if (mIsAnimatingDismiss && !showingStateInconsistent) {
+ Log.d(TAG, "dismissH: skipping dismiss because isAnimatingDismiss is true"
+ + " and showingStateInconsistent is false");
Trace.endSection();
return;
}
@@ -1570,8 +1505,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
.setDuration(mDialogHideAnimationDurationMs)
.setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
.withEndAction(() -> mHandler.postDelayed(() -> {
- mController.notifyVisible(false);
- mDialog.dismiss();
+ if (mController != null) {
+ mController.notifyVisible(false);
+ }
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
tryToRemoveCaptionsTooltip();
mIsAnimatingDismiss = false;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index bb04f82fcffa..aa4ee545a500 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -21,7 +21,6 @@ import android.media.AudioManager;
import android.os.Looper;
import com.android.internal.jank.InteractionJankMonitor;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
@@ -31,7 +30,6 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.volume.CsdWarningDialog;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
@@ -42,8 +40,6 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
-import java.util.concurrent.Executor;
-
/** Dagger Module for code in the volume package. */
@Module
public interface VolumeModule {
@@ -63,8 +59,6 @@ public interface VolumeModule {
VolumePanelFactory volumePanelFactory,
ActivityStarter activityStarter,
InteractionJankMonitor interactionJankMonitor,
- DeviceConfigProxy deviceConfigProxy,
- @Main Executor executor,
CsdWarningDialog.Factory csdFactory,
DevicePostureController devicePostureController,
DumpManager dumpManager) {
@@ -78,8 +72,6 @@ public interface VolumeModule {
volumePanelFactory,
activityStarter,
interactionJankMonitor,
- deviceConfigProxy,
- executor,
csdFactory,
devicePostureController,
Looper.getMainLooper(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 44c99053eb47..1990c8f644b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -145,50 +145,59 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun authenticate_withCorrectPin_returnsTrueAndUnlocksDevice() =
testScope.runTest {
+ val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
val isUnlocked by collectLastValue(underTest.isUnlocked)
underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
assertThat(isUnlocked).isTrue()
+ assertThat(failedAttemptCount).isEqualTo(0)
}
@Test
fun authenticate_withIncorrectPin_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
+ val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
val isUnlocked by collectLastValue(underTest.isUnlocked)
underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
assertThat(isUnlocked).isFalse()
+ assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
fun authenticate_withCorrectPassword_returnsTrueAndUnlocksDevice() =
testScope.runTest {
+ val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
val isUnlocked by collectLastValue(underTest.isUnlocked)
underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate("password".toList())).isTrue()
assertThat(isUnlocked).isTrue()
+ assertThat(failedAttemptCount).isEqualTo(0)
}
@Test
fun authenticate_withIncorrectPassword_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
+ val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
val isUnlocked by collectLastValue(underTest.isUnlocked)
underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
assertThat(isUnlocked).isFalse()
assertThat(underTest.authenticate("alohomora".toList())).isFalse()
assertThat(isUnlocked).isFalse()
+ assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
fun authenticate_withCorrectPattern_returnsTrueAndUnlocksDevice() =
testScope.runTest {
+ val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
val isUnlocked by collectLastValue(underTest.isUnlocked)
underTest.setAuthenticationMethod(
AuthenticationMethodModel.Pattern(
@@ -230,11 +239,13 @@ class AuthenticationInteractorTest : SysuiTestCase() {
)
.isTrue()
assertThat(isUnlocked).isTrue()
+ assertThat(failedAttemptCount).isEqualTo(0)
}
@Test
fun authenticate_withIncorrectPattern_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
+ val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
val isUnlocked by collectLastValue(underTest.isUnlocked)
underTest.setAuthenticationMethod(
AuthenticationMethodModel.Pattern(
@@ -276,6 +287,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
)
.isFalse()
assertThat(isUnlocked).isFalse()
+ assertThat(failedAttemptCount).isEqualTo(1)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
index 213dc8772cfa..2d1e8a830fc9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
@@ -73,7 +73,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
@Test
fun fingerprintSuccessDoesNotRequireExplicitConfirmation() {
- biometricView.onDialogAnimatedIn()
+ biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
biometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT)
TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
@@ -84,7 +84,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
@Test
fun faceSuccessRequiresExplicitConfirmation() {
- biometricView.onDialogAnimatedIn()
+ biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
biometricView.onAuthenticationSucceeded(TYPE_FACE)
waitForIdleSync()
@@ -104,7 +104,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
@Test
fun ignoresFaceErrors_faceIsNotClass3_notLockoutError() {
- biometricView.onDialogAnimatedIn()
+ biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
biometricView.onError(TYPE_FACE, "not a face")
waitForIdleSync()
@@ -121,7 +121,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
@Test
fun doNotIgnoresFaceErrors_faceIsClass3_notLockoutError() {
biometricView.isFaceClass3 = true
- biometricView.onDialogAnimatedIn()
+ biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
biometricView.onError(TYPE_FACE, "not a face")
waitForIdleSync()
@@ -138,7 +138,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
@Test
fun doNotIgnoresFaceErrors_faceIsClass3_lockoutError() {
biometricView.isFaceClass3 = true
- biometricView.onDialogAnimatedIn()
+ biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
biometricView.onError(
TYPE_FACE,
FaceManager.getErrorString(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
index 22ebc7ec3c58..8e5d96b0a2c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
@@ -120,7 +120,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
@Test
fun testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
- biometricView.onDialogAnimatedIn()
+ biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
biometricView.mNegativeButton.performClick()
TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
@@ -212,7 +212,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
@Test
fun testIgnoresUselessHelp() {
biometricView.mAnimationDurationHideDialog = 10_000
- biometricView.onDialogAnimatedIn()
+ biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
waitForIdleSync()
assertThat(biometricView.isAuthenticating).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 9d68cf3aee01..d31a86ae2809 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -41,11 +41,15 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
-import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
-import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -53,29 +57,34 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import org.junit.After
+import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
-class AuthContainerViewTest : SysuiTestCase() {
+open class AuthContainerViewTest : SysuiTestCase() {
@JvmField @Rule
var mockitoRule = MockitoJUnit.rule()
+ private val featureFlags = FakeFeatureFlags()
+
@Mock
lateinit var callback: AuthDialogCallback
@Mock
@@ -91,16 +100,25 @@ class AuthContainerViewTest : SysuiTestCase() {
@Mock
lateinit var interactionJankMonitor: InteractionJankMonitor
+ // TODO(b/278622168): remove with flag
+ open val useNewBiometricPrompt = false
+
private val testScope = TestScope(StandardTestDispatcher())
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val biometricPromptRepository = FakePromptRepository()
private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
private val credentialInteractor = FakeCredentialInteractor()
- private val bpCredentialInteractor = BiometricPromptCredentialInteractor(
+ private val bpCredentialInteractor = PromptCredentialInteractor(
Dispatchers.Main.immediate,
biometricPromptRepository,
- credentialInteractor
+ credentialInteractor,
)
+ private val promptSelectorInteractor by lazy {
+ PromptSelectorInteractorImpl(
+ biometricPromptRepository,
+ lockPatternUtils,
+ )
+ }
private val displayStateInteractor = DisplayStateInteractorImpl(
testScope.backgroundScope,
mContext,
@@ -115,6 +133,11 @@ class AuthContainerViewTest : SysuiTestCase() {
private var authContainer: TestAuthContainerView? = null
+ @Before
+ fun setup() {
+ featureFlags.set(Flags.BIOMETRIC_BP_STRONG, useNewBiometricPrompt)
+ }
+
@After
fun tearDown() {
if (authContainer?.isAttachedToWindow == true) {
@@ -125,7 +148,7 @@ class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testNotifiesAnimatedIn() {
initializeFingerprintContainer()
- verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L)
+ verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
}
@Test
@@ -164,13 +187,13 @@ class AuthContainerViewTest : SysuiTestCase() {
container.dismissFromSystemServer()
waitForIdleSync()
- verify(callback, never()).onDialogAnimatedIn(anyLong())
+ verify(callback, never()).onDialogAnimatedIn(anyLong(), anyBoolean())
container.addToView()
waitForIdleSync()
// attaching the view resets the state and allows this to happen again
- verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L)
+ verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
}
@Test
@@ -185,7 +208,7 @@ class AuthContainerViewTest : SysuiTestCase() {
// the first time is triggered by initializeFingerprintContainer()
// the second time was triggered by dismissWithoutCallback()
- verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L)
+ verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
}
@Test
@@ -479,6 +502,8 @@ class AuthContainerViewTest : SysuiTestCase() {
this.authenticators = authenticators
}
},
+ featureFlags,
+ testScope.backgroundScope,
fingerprintProps,
faceProps,
wakefulnessLifecycle,
@@ -486,8 +511,10 @@ class AuthContainerViewTest : SysuiTestCase() {
userManager,
lockPatternUtils,
interactionJankMonitor,
- { bpCredentialInteractor },
{ authBiometricFingerprintViewModel },
+ { promptSelectorInteractor },
+ { bpCredentialInteractor },
+ PromptViewModel(promptSelectorInteractor),
{ credentialViewModel },
Handler(TestableLooper.get(this).looper),
fakeExecutor
@@ -497,7 +524,10 @@ class AuthContainerViewTest : SysuiTestCase() {
}
}
- override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
+ override fun waitForIdleSync() {
+ testScope.runCurrent()
+ TestableLooper.get(this).processAllMessages()
+ }
private fun AuthContainerView.addToView() {
ViewUtils.attachView(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest2.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest2.kt
new file mode 100644
index 000000000000..b56d05537215
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest2.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.biometrics
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.runner.RunWith
+
+// TODO(b/278622168): remove with flag
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class AuthContainerViewTest2 : AuthContainerViewTest() {
+ override val useNewBiometricPrompt = true
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index a326cc7045dd..b9f92a064bc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -18,7 +18,6 @@ package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
import static com.google.common.truth.Truth.assertThat;
@@ -54,7 +53,6 @@ import android.content.res.Resources;
import android.graphics.Point;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.ComponentInfoInternal;
@@ -91,10 +89,14 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
+import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.VibratorHelper;
@@ -171,12 +173,16 @@ public class AuthControllerTest extends SysuiTestCase {
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
@Mock
- private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor;
+ private PromptCredentialInteractor mBiometricPromptCredentialInteractor;
+ @Mock
+ private PromptSelectorInteractor mPromptSelectionInteractor;
@Mock
private AuthBiometricFingerprintViewModel mAuthBiometricFingerprintViewModel;
@Mock
private CredentialViewModel mCredentialViewModel;
@Mock
+ private PromptViewModel mPromptViewModel;
+ @Mock
private UdfpsUtils mUdfpsUtils;
@Captor
@@ -194,12 +200,17 @@ public class AuthControllerTest extends SysuiTestCase {
private Handler mHandler;
private DelayableExecutor mBackgroundExecutor;
private TestableAuthController mAuthController;
+ private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
private VibratorHelper mVibratorHelper;
@Before
public void setup() throws RemoteException {
+ // TODO(b/278622168): remove with flag
+ // AuthController simply passes this through to AuthContainerView (does not impact test)
+ mFeatureFlags.set(Flags.BIOMETRIC_BP_STRONG, false);
+
mContextSpy = spy(mContext);
mExecution = new FakeExecution();
mTestableLooper = TestableLooper.get(this);
@@ -952,8 +963,7 @@ public class AuthControllerTest extends SysuiTestCase {
0 /* userId */,
0 /* operationId */,
"testPackage",
- REQUEST_ID,
- BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE);
+ REQUEST_ID);
}
private void switchTask(String packageName) {
@@ -993,25 +1003,26 @@ public class AuthControllerTest extends SysuiTestCase {
private PromptInfo mLastBiometricPromptInfo;
TestableAuthController(Context context) {
- super(context, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
+ super(context, mFeatureFlags, null /* applicationCoroutineScope */,
+ mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
mFingerprintManager, mFaceManager, () -> mUdfpsController,
() -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger,
- mLogContextInteractor, () -> mBiometricPromptCredentialInteractor,
- () -> mAuthBiometricFingerprintViewModel, () -> mCredentialViewModel,
- mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper,
- mUdfpsUtils);
+ mLogContextInteractor, () -> mAuthBiometricFingerprintViewModel,
+ () -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
+ () -> mCredentialViewModel, () -> mPromptViewModel,
+ mInteractionJankMonitor, mHandler,
+ mBackgroundExecutor, mVibratorHelper, mUdfpsUtils);
}
@Override
protected AuthDialog buildDialog(DelayableExecutor bgExecutor, PromptInfo promptInfo,
boolean requireConfirmation, int userId, int[] sensorIds,
String opPackageName, boolean skipIntro, long operationId, long requestId,
- @BiometricManager.BiometricMultiSensorMode int multiSensorConfig,
WakefulnessLifecycle wakefulnessLifecycle,
AuthDialogPanelInteractionDetector panelInteractionDetector,
UserManager userManager,
- LockPatternUtils lockPatternUtils) {
+ LockPatternUtils lockPatternUtils, PromptViewModel viewModel) {
mLastBiometricPromptInfo = promptInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 1379a0eeebdd..94244cdf271d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -18,10 +18,11 @@ package com.android.systemui.biometrics
import android.annotation.IdRes
import android.content.Context
-import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.SensorProperties
+import android.hardware.biometrics.SensorPropertiesInternal
import android.hardware.face.FaceSensorProperties
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorProperties
@@ -61,9 +62,9 @@ internal fun <T : AuthBiometricView> Int.asTestAuthBiometricView(
private fun buildPromptInfo(allowDeviceCredential: Boolean): PromptInfo {
val promptInfo = PromptInfo()
promptInfo.title = "Title"
- var authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
+ var authenticators = Authenticators.BIOMETRIC_WEAK
if (allowDeviceCredential) {
- authenticators = authenticators or BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ authenticators = authenticators or Authenticators.DEVICE_CREDENTIAL
} else {
promptInfo.negativeButtonText = "Negative"
}
@@ -80,7 +81,8 @@ internal fun AuthBiometricView?.destroyDialog() {
/** Create [FingerprintSensorPropertiesInternal] for a test. */
internal fun fingerprintSensorPropertiesInternal(
- ids: List<Int> = listOf(0)
+ ids: List<Int> = listOf(0),
+ strong: Boolean = true,
): List<FingerprintSensorPropertiesInternal> {
val componentInfo =
listOf(
@@ -102,7 +104,7 @@ internal fun fingerprintSensorPropertiesInternal(
return ids.map { id ->
FingerprintSensorPropertiesInternal(
id,
- SensorProperties.STRENGTH_STRONG,
+ if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_REAR,
@@ -113,7 +115,8 @@ internal fun fingerprintSensorPropertiesInternal(
/** Create [FaceSensorPropertiesInternal] for a test. */
internal fun faceSensorPropertiesInternal(
- ids: List<Int> = listOf(1)
+ ids: List<Int> = listOf(1),
+ strong: Boolean = true,
): List<FaceSensorPropertiesInternal> {
val componentInfo =
listOf(
@@ -135,7 +138,7 @@ internal fun faceSensorPropertiesInternal(
return ids.map { id ->
FaceSensorPropertiesInternal(
id,
- SensorProperties.STRENGTH_STRONG,
+ if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
2 /* maxEnrollmentsPerUser */,
componentInfo,
FaceSensorProperties.TYPE_RGB,
@@ -146,6 +149,24 @@ internal fun faceSensorPropertiesInternal(
}
}
+@Authenticators.Types
+internal fun Collection<SensorPropertiesInternal?>.extractAuthenticatorTypes(): Int {
+ var authenticators = Authenticators.EMPTY_SET
+ mapNotNull { it?.sensorStrength }
+ .forEach { strength ->
+ authenticators =
+ authenticators or
+ when (strength) {
+ SensorProperties.STRENGTH_CONVENIENCE ->
+ Authenticators.BIOMETRIC_CONVENIENCE
+ SensorProperties.STRENGTH_WEAK -> Authenticators.BIOMETRIC_WEAK
+ SensorProperties.STRENGTH_STRONG -> Authenticators.BIOMETRIC_STRONG
+ else -> Authenticators.EMPTY_SET
+ }
+ }
+ return authenticators
+}
+
internal fun promptInfo(
title: String = "title",
subtitle: String = "sub",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index 2d5614c15173..4836af635aed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -4,7 +4,7 @@ import android.hardware.biometrics.PromptInfo
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
-import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
@@ -60,7 +60,7 @@ class PromptRepositoryImplTest : SysuiTestCase() {
@Test
fun setsAndUnsetsPrompt() = runBlockingTest {
- val kind = PromptKind.PIN
+ val kind = PromptKind.Pin
val uid = 8
val challenge = 90L
val promptInfo = PromptInfo()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index dbcbf415221e..720a35c9024f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -9,15 +9,17 @@ import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
import com.android.systemui.biometrics.domain.model.BiometricUserInfo
import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
@@ -36,42 +38,39 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
- private val dispatcher = UnconfinedTestDispatcher()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
private val biometricPromptRepository = FakePromptRepository()
private val credentialInteractor = FakeCredentialInteractor()
- private lateinit var interactor: BiometricPromptCredentialInteractor
+ private lateinit var interactor: PromptCredentialInteractor
@Before
fun setup() {
interactor =
- BiometricPromptCredentialInteractor(
- dispatcher,
+ PromptCredentialInteractor(
+ testDispatcher,
biometricPromptRepository,
- credentialInteractor
+ credentialInteractor,
)
}
@Test
fun testIsShowing() =
- runTest(dispatcher) {
- var showing = false
- val job = launch { interactor.isShowing.collect { showing = it } }
+ testScope.runTest {
+ val showing by collectLastValue(interactor.isShowing)
biometricPromptRepository.setIsShowing(false)
assertThat(showing).isFalse()
biometricPromptRepository.setIsShowing(true)
assertThat(showing).isTrue()
-
- job.cancel()
}
@Test
fun testShowError() =
- runTest(dispatcher) {
- var error: CredentialStatus.Fail? = null
- val job = launch { interactor.verificationError.collect { error = it } }
+ testScope.runTest {
+ val error by collectLastValue(interactor.verificationError)
for (msg in listOf("once", "again")) {
interactor.setVerificationError(error(msg))
@@ -80,19 +79,14 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
interactor.resetVerificationError()
assertThat(error).isNull()
-
- job.cancel()
}
@Test
fun nullWhenNoPromptInfo() =
- runTest(dispatcher) {
- var prompt: BiometricPromptRequest? = null
- val job = launch { interactor.prompt.collect { prompt = it } }
+ testScope.runTest {
+ val prompt by collectLastValue(interactor.prompt)
assertThat(prompt).isNull()
-
- job.cancel()
}
@Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN)
@@ -102,12 +96,11 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
@Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN)
private fun useCredentialForPrompt(kind: Int) =
- runTest(dispatcher) {
+ testScope.runTest {
val isStealth = false
credentialInteractor.stealthMode = isStealth
- var prompt: BiometricPromptRequest? = null
- val job = launch { interactor.prompt.collect { prompt = it } }
+ val prompt by collectLastValue(interactor.prompt)
val title = "what a prompt"
val subtitle = "s"
@@ -124,14 +117,12 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
challenge = OPERATION_ID
)
- val p = prompt as? BiometricPromptRequest.Credential
- assertThat(p).isNotNull()
- assertThat(p!!.title).isEqualTo(title)
- assertThat(p.subtitle).isEqualTo(subtitle)
- assertThat(p.description).isEqualTo(description)
- assertThat(p.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
- assertThat(p.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
- assertThat(p)
+ assertThat(prompt?.title).isEqualTo(title)
+ assertThat(prompt?.subtitle).isEqualTo(subtitle)
+ assertThat(prompt?.description).isEqualTo(description)
+ assertThat(prompt?.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
+ assertThat(prompt?.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
+ assertThat(prompt)
.isInstanceOf(
when (kind) {
Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java
@@ -142,25 +133,25 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
else -> throw Exception("wrong kind")
}
)
- if (p is BiometricPromptRequest.Credential.Pattern) {
- assertThat(p.stealthMode).isEqualTo(isStealth)
+ val pattern = prompt as? BiometricPromptRequest.Credential.Pattern
+ if (pattern != null) {
+ assertThat(pattern.stealthMode).isEqualTo(isStealth)
}
interactor.resetPrompt()
assertThat(prompt).isNull()
-
- job.cancel()
}
@Test
fun checkCredential() =
- runTest(dispatcher) {
+ testScope.runTest {
val hat = ByteArray(4)
credentialInteractor.verifyCredentialResponse = { _ -> flowOf(verified(hat)) }
val errors = mutableListOf<CredentialStatus.Fail?>()
val job = launch { interactor.verificationError.toList(errors) }
+ runCurrent()
val checked =
interactor.checkCredential(pinRequest(), text = "1234")
@@ -168,6 +159,8 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
assertThat(checked).isNotNull()
assertThat(checked!!.hat).isSameInstanceAs(hat)
+
+ runCurrent()
assertThat(errors.map { it?.error }).containsExactly(null)
job.cancel()
@@ -175,7 +168,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
@Test
fun checkCredentialWhenBad() =
- runTest(dispatcher) {
+ testScope.runTest {
val errorMessage = "bad"
val remainingAttempts = 12
credentialInteractor.verifyCredentialResponse = { _ ->
@@ -184,6 +177,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
val errors = mutableListOf<CredentialStatus.Fail?>()
val job = launch { interactor.verificationError.toList(errors) }
+ runCurrent()
val checked =
interactor.checkCredential(pinRequest(), text = "1234")
@@ -192,6 +186,8 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
assertThat(checked).isNotNull()
assertThat(checked!!.remainingAttempts).isEqualTo(remainingAttempts)
assertThat(checked.urgentMessage).isNull()
+
+ runCurrent()
assertThat(errors.map { it?.error }).containsExactly(null, errorMessage).inOrder()
job.cancel()
@@ -199,7 +195,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
@Test
fun checkCredentialWhenBadAndUrgentMessage() =
- runTest(dispatcher) {
+ testScope.runTest {
val error = "not so bad"
val urgentMessage = "really bad"
credentialInteractor.verifyCredentialResponse = { _ ->
@@ -208,6 +204,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
val errors = mutableListOf<CredentialStatus.Fail?>()
val job = launch { interactor.verificationError.toList(errors) }
+ runCurrent()
val checked =
interactor.checkCredential(pinRequest(), text = "1234")
@@ -215,6 +212,8 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
assertThat(checked).isNotNull()
assertThat(checked!!.urgentMessage).isEqualTo(urgentMessage)
+
+ runCurrent()
assertThat(errors.map { it?.error }).containsExactly(null, error).inOrder()
assertThat(errors.last() as? CredentialStatus.Fail.Error)
.isEqualTo(error(error, 10, urgentMessage))
@@ -224,7 +223,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
@Test
fun checkCredentialWhenBadAndThrottled() =
- runTest(dispatcher) {
+ testScope.runTest {
val remainingAttempts = 3
val error = ":("
val urgentMessage = ":D"
@@ -239,6 +238,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
}
val errors = mutableListOf<CredentialStatus.Fail?>()
val job = launch { interactor.verificationError.toList(errors) }
+ runCurrent()
val checked =
interactor.checkCredential(pinRequest(), text = "1234")
@@ -246,6 +246,8 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
assertThat(checked).isNotNull()
assertThat(checked!!.remainingAttempts).isEqualTo(remainingAttempts)
+
+ runCurrent()
assertThat(checked.urgentMessage).isEqualTo(urgentMessage)
assertThat(errors.map { it?.error })
.containsExactly(null, "1", "2", "3", error)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
new file mode 100644
index 000000000000..a62ea3b77fc2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.hardware.biometrics.BiometricManager.Authenticators
+import android.hardware.biometrics.PromptInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.model.BiometricModalities
+import com.android.systemui.biometrics.faceSensorPropertiesInternal
+import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
+import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+private const val TITLE = "hey there"
+private const val SUBTITLE = "ok"
+private const val DESCRIPTION = "football"
+private const val NEGATIVE_TEXT = "escape"
+
+private const val USER_ID = 8
+private const val CHALLENGE = 999L
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class PromptSelectorInteractorImplTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+
+ private val testScope = TestScope()
+ private val promptRepository = FakePromptRepository()
+
+ private lateinit var interactor: PromptSelectorInteractor
+
+ @Before
+ fun setup() {
+ interactor = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
+ }
+
+ @Test
+ fun useBiometricsAndReset() =
+ testScope.runTest { useBiometricsAndReset(allowCredentialFallback = true) }
+
+ @Test
+ fun useBiometricsAndResetWithoutFallback() =
+ testScope.runTest { useBiometricsAndReset(allowCredentialFallback = false) }
+
+ private fun TestScope.useBiometricsAndReset(allowCredentialFallback: Boolean) {
+ setUserCredentialType(isPassword = true)
+
+ val confirmationRequired = true
+ val info =
+ PromptInfo().apply {
+ title = TITLE
+ subtitle = SUBTITLE
+ description = DESCRIPTION
+ negativeButtonText = NEGATIVE_TEXT
+ isConfirmationRequested = confirmationRequired
+ authenticators =
+ if (allowCredentialFallback) {
+ Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL
+ } else {
+ Authenticators.BIOMETRIC_STRONG
+ }
+ isDeviceCredentialAllowed = allowCredentialFallback
+ }
+ val modalities =
+ BiometricModalities(
+ fingerprintProperties = fingerprintSensorPropertiesInternal().first(),
+ faceProperties = faceSensorPropertiesInternal().first(),
+ )
+
+ val currentPrompt by collectLastValue(interactor.prompt)
+ val credentialKind by collectLastValue(interactor.credentialKind)
+ val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
+ val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequested)
+
+ assertThat(currentPrompt).isNull()
+
+ interactor.useBiometricsForAuthentication(
+ info,
+ confirmationRequired,
+ USER_ID,
+ CHALLENGE,
+ modalities
+ )
+
+ assertThat(currentPrompt).isNotNull()
+ assertThat(currentPrompt?.title).isEqualTo(TITLE)
+ assertThat(currentPrompt?.description).isEqualTo(DESCRIPTION)
+ assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE)
+ assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
+
+ if (allowCredentialFallback) {
+ assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
+ assertThat(isCredentialAllowed).isTrue()
+ } else {
+ assertThat(credentialKind).isEqualTo(PromptKind.Biometric())
+ assertThat(isCredentialAllowed).isFalse()
+ }
+ assertThat(isExplicitConfirmationRequired).isEqualTo(confirmationRequired)
+
+ interactor.resetPrompt()
+ verifyUnset()
+ }
+
+ @Test
+ fun usePinCredentialAndReset() =
+ testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PIN) }
+
+ @Test
+ fun usePattermCredentialAndReset() =
+ testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PATTERN) }
+
+ @Test
+ fun usePasswordCredentialAndReset() =
+ testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PASSWORD) }
+
+ private fun TestScope.useCredentialAndReset(@Utils.CredentialType kind: Int) {
+ setUserCredentialType(
+ isPin = kind == Utils.CREDENTIAL_PIN,
+ isPassword = kind == Utils.CREDENTIAL_PASSWORD,
+ )
+
+ val info =
+ PromptInfo().apply {
+ title = TITLE
+ subtitle = SUBTITLE
+ description = DESCRIPTION
+ negativeButtonText = NEGATIVE_TEXT
+ authenticators = Authenticators.DEVICE_CREDENTIAL
+ isDeviceCredentialAllowed = true
+ }
+
+ val currentPrompt by collectLastValue(interactor.prompt)
+ val credentialKind by collectLastValue(interactor.credentialKind)
+
+ assertThat(currentPrompt).isNull()
+
+ interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE)
+
+ // not using biometrics, should be null with no fallback option
+ assertThat(currentPrompt).isNull()
+ assertThat(credentialKind).isEqualTo(PromptKind.Biometric())
+
+ interactor.resetPrompt()
+ verifyUnset()
+ }
+
+ private fun TestScope.verifyUnset() {
+ val currentPrompt by collectLastValue(interactor.prompt)
+ val credentialKind by collectLastValue(interactor.credentialKind)
+
+ assertThat(currentPrompt).isNull()
+
+ val kind = credentialKind as? PromptKind.Biometric
+ assertThat(kind).isNotNull()
+ assertThat(kind?.activeModalities?.isEmpty).isTrue()
+ }
+
+ private fun setUserCredentialType(isPin: Boolean = false, isPassword: Boolean = false) {
+ whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(any()))
+ .thenReturn(
+ when {
+ isPin -> DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ isPassword -> DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
+ else -> DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ }
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricModalitiesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricModalitiesTest.kt
new file mode 100644
index 000000000000..526b83356ee9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricModalitiesTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.model
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.faceSensorPropertiesInternal
+import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BiometricModalitiesTest : SysuiTestCase() {
+
+ @Test
+ fun isEmpty() {
+ assertThat(BiometricModalities().isEmpty).isTrue()
+ }
+
+ @Test
+ fun fingerprintOnly() {
+ with(
+ BiometricModalities(
+ fingerprintProperties = fingerprintSensorPropertiesInternal().first(),
+ )
+ ) {
+ assertThat(isEmpty).isFalse()
+ assertThat(hasFace).isFalse()
+ assertThat(hasFaceOnly).isFalse()
+ assertThat(hasFingerprint).isTrue()
+ assertThat(hasFingerprintOnly).isTrue()
+ assertThat(hasFaceAndFingerprint).isFalse()
+ }
+ }
+
+ @Test
+ fun faceOnly() {
+ with(BiometricModalities(faceProperties = faceSensorPropertiesInternal().first())) {
+ assertThat(isEmpty).isFalse()
+ assertThat(hasFace).isTrue()
+ assertThat(hasFaceOnly).isTrue()
+ assertThat(hasFingerprint).isFalse()
+ assertThat(hasFingerprintOnly).isFalse()
+ assertThat(hasFaceAndFingerprint).isFalse()
+ }
+ }
+
+ @Test
+ fun faceStrength() {
+ with(
+ BiometricModalities(
+ fingerprintProperties = fingerprintSensorPropertiesInternal(strong = false).first(),
+ faceProperties = faceSensorPropertiesInternal(strong = true).first()
+ )
+ ) {
+ assertThat(isFaceStrong).isTrue()
+ }
+
+ with(
+ BiometricModalities(
+ fingerprintProperties = fingerprintSensorPropertiesInternal(strong = false).first(),
+ faceProperties = faceSensorPropertiesInternal(strong = false).first()
+ )
+ ) {
+ assertThat(isFaceStrong).isFalse()
+ }
+ }
+
+ @Test
+ fun faceAndFingerprint() {
+ with(
+ BiometricModalities(
+ fingerprintProperties = fingerprintSensorPropertiesInternal().first(),
+ faceProperties = faceSensorPropertiesInternal().first(),
+ )
+ ) {
+ assertThat(isEmpty).isFalse()
+ assertThat(hasFace).isTrue()
+ assertThat(hasFingerprint).isTrue()
+ assertThat(hasFaceOnly).isFalse()
+ assertThat(hasFingerprintOnly).isFalse()
+ assertThat(hasFaceAndFingerprint).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index 4c5e3c1bc6a6..e3529055c223 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -2,6 +2,7 @@ package com.android.systemui.biometrics.domain.model
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
import com.android.systemui.biometrics.promptInfo
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -21,11 +22,13 @@ class BiometricPromptRequestTest : SysuiTestCase() {
val subtitle = "a"
val description = "request"
+ val fpPros = fingerprintSensorPropertiesInternal().first()
val request =
BiometricPromptRequest.Biometric(
promptInfo(title = title, subtitle = subtitle, description = description),
BiometricUserInfo(USER_ID),
- BiometricOperationInfo(OPERATION_ID)
+ BiometricOperationInfo(OPERATION_ID),
+ BiometricModalities(fingerprintProperties = fpPros),
)
assertThat(request.title).isEqualTo(title)
@@ -33,6 +36,8 @@ class BiometricPromptRequestTest : SysuiTestCase() {
assertThat(request.description).isEqualTo(description)
assertThat(request.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
assertThat(request.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
+ assertThat(request.modalities)
+ .isEqualTo(BiometricModalities(fingerprintProperties = fpPros))
}
@Test
@@ -51,19 +56,19 @@ class BiometricPromptRequestTest : SysuiTestCase() {
description = description,
credentialTitle = null,
credentialSubtitle = null,
- credentialDescription = null
+ credentialDescription = null,
),
BiometricUserInfo(USER_ID),
- BiometricOperationInfo(OPERATION_ID)
+ BiometricOperationInfo(OPERATION_ID),
),
BiometricPromptRequest.Credential.Password(
promptInfo(
credentialTitle = title,
credentialSubtitle = subtitle,
- credentialDescription = description
+ credentialDescription = description,
),
BiometricUserInfo(USER_ID),
- BiometricOperationInfo(OPERATION_ID)
+ BiometricOperationInfo(OPERATION_ID),
),
BiometricPromptRequest.Credential.Pattern(
promptInfo(
@@ -71,11 +76,11 @@ class BiometricPromptRequestTest : SysuiTestCase() {
description = description,
credentialTitle = title,
credentialSubtitle = null,
- credentialDescription = null
+ credentialDescription = null,
),
BiometricUserInfo(USER_ID),
BiometricOperationInfo(OPERATION_ID),
- stealth
+ stealth,
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
index d73cdfc4249f..3245020ec584 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
@@ -2,12 +2,12 @@ package com.android.systemui.biometrics.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.model.PromptKind
import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.CredentialStatus
import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.biometrics.shared.model.PromptKind
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
@@ -40,17 +40,13 @@ class CredentialViewModelTest : SysuiTestCase() {
viewModel =
CredentialViewModel(
mContext,
- BiometricPromptCredentialInteractor(
- dispatcher,
- promptRepository,
- credentialInteractor
- )
+ PromptCredentialInteractor(dispatcher, promptRepository, credentialInteractor)
)
}
- @Test fun setsPinInputFlags() = setsInputFlags(PromptKind.PIN, expectFlags = true)
- @Test fun setsPasswordInputFlags() = setsInputFlags(PromptKind.PASSWORD, expectFlags = false)
- @Test fun setsPatternInputFlags() = setsInputFlags(PromptKind.PATTERN, expectFlags = false)
+ @Test fun setsPinInputFlags() = setsInputFlags(PromptKind.Pin, expectFlags = true)
+ @Test fun setsPasswordInputFlags() = setsInputFlags(PromptKind.Password, expectFlags = false)
+ @Test fun setsPatternInputFlags() = setsInputFlags(PromptKind.Pattern, expectFlags = false)
private fun setsInputFlags(type: PromptKind, expectFlags: Boolean) =
runTestWithKind(type) {
@@ -65,10 +61,10 @@ class CredentialViewModelTest : SysuiTestCase() {
job.cancel()
}
- @Test fun isStealthIgnoredByPin() = isStealthMode(PromptKind.PIN, expectStealth = false)
+ @Test fun isStealthIgnoredByPin() = isStealthMode(PromptKind.Pin, expectStealth = false)
@Test
- fun isStealthIgnoredByPassword() = isStealthMode(PromptKind.PASSWORD, expectStealth = false)
- @Test fun isStealthUsedByPattern() = isStealthMode(PromptKind.PATTERN, expectStealth = true)
+ fun isStealthIgnoredByPassword() = isStealthMode(PromptKind.Password, expectStealth = false)
+ @Test fun isStealthUsedByPattern() = isStealthMode(PromptKind.Pattern, expectStealth = true)
private fun isStealthMode(type: PromptKind, expectStealth: Boolean) =
runTestWithKind(type, init = { credentialInteractor.stealthMode = true }) {
@@ -119,7 +115,7 @@ class CredentialViewModelTest : SysuiTestCase() {
val attestations = mutableListOf<ByteArray?>()
val remainingAttempts = mutableListOf<RemainingAttempts?>()
- var header: HeaderViewModel? = null
+ var header: CredentialHeaderViewModel? = null
val job = launch {
launch { viewModel.validatedAttestation.toList(attestations) }
launch { viewModel.remainingAttempts.toList(remainingAttempts) }
@@ -147,7 +143,7 @@ class CredentialViewModelTest : SysuiTestCase() {
val attestations = mutableListOf<ByteArray?>()
val remainingAttempts = mutableListOf<RemainingAttempts?>()
- var header: HeaderViewModel? = null
+ var header: CredentialHeaderViewModel? = null
val job = launch {
launch { viewModel.validatedAttestation.toList(attestations) }
launch { viewModel.remainingAttempts.toList(remainingAttempts) }
@@ -169,7 +165,7 @@ class CredentialViewModelTest : SysuiTestCase() {
}
private fun runTestWithKind(
- kind: PromptKind = PromptKind.PIN,
+ kind: PromptKind = PromptKind.Pin,
init: () -> Unit = {},
block: suspend TestScope.() -> Unit,
) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
new file mode 100644
index 000000000000..689bb0023675
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PromptAuthStateTest : SysuiTestCase() {
+
+ @Test
+ fun notAuthenticated() {
+ with(PromptAuthState(isAuthenticated = false)) {
+ assertThat(isNotAuthenticated).isTrue()
+ assertThat(isAuthenticatedAndConfirmed).isFalse()
+ assertThat(isAuthenticatedByFace).isFalse()
+ assertThat(isAuthenticatedByFingerprint).isFalse()
+ }
+ }
+
+ @Test
+ fun authenticatedByUnknown() {
+ with(PromptAuthState(isAuthenticated = true)) {
+ assertThat(isNotAuthenticated).isFalse()
+ assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedByFace).isFalse()
+ assertThat(isAuthenticatedByFingerprint).isFalse()
+ }
+
+ with(PromptAuthState(isAuthenticated = true, needsUserConfirmation = true)) {
+ assertThat(isNotAuthenticated).isFalse()
+ assertThat(isAuthenticatedAndConfirmed).isFalse()
+ assertThat(isAuthenticatedByFace).isFalse()
+ assertThat(isAuthenticatedByFingerprint).isFalse()
+
+ assertThat(asConfirmed().isAuthenticatedAndConfirmed).isTrue()
+ }
+ }
+
+ @Test
+ fun authenticatedWithFace() {
+ with(
+ PromptAuthState(isAuthenticated = true, authenticatedModality = BiometricModality.Face)
+ ) {
+ assertThat(isNotAuthenticated).isFalse()
+ assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedByFace).isTrue()
+ assertThat(isAuthenticatedByFingerprint).isFalse()
+ }
+ }
+
+ @Test
+ fun authenticatedWithFingerprint() {
+ with(
+ PromptAuthState(
+ isAuthenticated = true,
+ authenticatedModality = BiometricModality.Fingerprint,
+ )
+ ) {
+ assertThat(isNotAuthenticated).isFalse()
+ assertThat(isAuthenticatedAndConfirmed).isTrue()
+ assertThat(isAuthenticatedByFace).isFalse()
+ assertThat(isAuthenticatedByFingerprint).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
new file mode 100644
index 000000000000..3ba6004e4532
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -0,0 +1,639 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import android.hardware.biometrics.PromptInfo
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthBiometricView
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
+import com.android.systemui.biometrics.domain.model.BiometricModalities
+import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.extractAuthenticatorTypes
+import com.android.systemui.biometrics.faceSensorPropertiesInternal
+import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+private const val USER_ID = 4
+private const val CHALLENGE = 2L
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(Parameterized::class)
+internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+
+ private val testScope = TestScope()
+ private val promptRepository = FakePromptRepository()
+
+ private lateinit var selector: PromptSelectorInteractor
+ private lateinit var viewModel: PromptViewModel
+
+ @Before
+ fun setup() {
+ selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
+ selector.resetPrompt()
+
+ viewModel = PromptViewModel(selector)
+ }
+
+ @Test
+ fun `start idle and show authenticating`() =
+ runGenericTest(doNotStart = true) {
+ val expectedSize =
+ if (testCase.shouldStartAsImplicitFlow) PromptSize.SMALL else PromptSize.MEDIUM
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val modalities by collectLastValue(viewModel.modalities)
+ val message by collectLastValue(viewModel.message)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isNotAuthenticated).isTrue()
+ with(modalities ?: throw Exception("missing modalities")) {
+ assertThat(hasFace).isEqualTo(testCase.face != null)
+ assertThat(hasFingerprint).isEqualTo(testCase.fingerprint != null)
+ }
+ assertThat(message).isEqualTo(PromptMessage.Empty)
+ assertThat(size).isEqualTo(expectedSize)
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN)
+
+ val startMessage = "here we go"
+ viewModel.showAuthenticating(startMessage, isRetry = false)
+
+ assertThat(message).isEqualTo(PromptMessage.Help(startMessage))
+ assertThat(authenticating).isTrue()
+ assertThat(authenticated?.isNotAuthenticated).isTrue()
+ assertThat(size).isEqualTo(expectedSize)
+ assertButtonsVisible(negative = expectedSize != PromptSize.SMALL)
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATING)
+ }
+
+ @Test
+ fun `shows authenticated - no errors`() = runGenericTest {
+ // this case can't happen until fingerprint is started
+ // trigger it now since no error has occurred in this test
+ val forceError = testCase.isCoex && testCase.authenticatedByFingerprint
+
+ if (forceError) {
+ assertThat(viewModel.fingerprintStartMode.first())
+ .isEqualTo(FingerprintStartMode.Pending)
+ viewModel.ensureFingerprintHasStarted(isDelayed = true)
+ }
+
+ showAuthenticated(
+ testCase.authenticatedModality,
+ testCase.expectConfirmation(atLeastOneFailure = forceError),
+ )
+ }
+
+ private suspend fun TestScope.showAuthenticated(
+ authenticatedModality: BiometricModality,
+ expectConfirmation: Boolean,
+ ) {
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val fpStartMode by collectLastValue(viewModel.fingerprintStartMode)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+
+ val authWithSmallPrompt =
+ testCase.shouldStartAsImplicitFlow &&
+ (fpStartMode == FingerprintStartMode.Pending || testCase.isFaceOnly)
+ assertThat(authenticating).isTrue()
+ assertThat(authenticated?.isNotAuthenticated).isTrue()
+ assertThat(size).isEqualTo(if (authWithSmallPrompt) PromptSize.SMALL else PromptSize.MEDIUM)
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATING)
+ assertButtonsVisible(negative = !authWithSmallPrompt)
+
+ val delay = 1000L
+ viewModel.showAuthenticated(authenticatedModality, delay)
+
+ assertThat(authenticated?.isAuthenticated).isTrue()
+ assertThat(authenticated?.delay).isEqualTo(delay)
+ assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
+ assertThat(size)
+ .isEqualTo(
+ if (authenticatedModality == BiometricModality.Fingerprint || expectConfirmation) {
+ PromptSize.MEDIUM
+ } else {
+ PromptSize.SMALL
+ }
+ )
+ assertThat(legacyState)
+ .isEqualTo(
+ if (expectConfirmation) {
+ AuthBiometricView.STATE_PENDING_CONFIRMATION
+ } else {
+ AuthBiometricView.STATE_AUTHENTICATED
+ }
+ )
+ assertButtonsVisible(
+ cancel = expectConfirmation,
+ confirm = expectConfirmation,
+ )
+ }
+
+ @Test
+ fun `shows temporary errors`() = runGenericTest {
+ val checkAtEnd = suspend { assertButtonsVisible(negative = true) }
+
+ showTemporaryErrors(restart = false) { checkAtEnd() }
+ showTemporaryErrors(restart = false, helpAfterError = "foo") { checkAtEnd() }
+ showTemporaryErrors(restart = true) { checkAtEnd() }
+ }
+
+ private suspend fun TestScope.showTemporaryErrors(
+ restart: Boolean,
+ helpAfterError: String = "",
+ block: suspend TestScope.() -> Unit = {},
+ ) {
+ val errorMessage = "oh no!"
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val message by collectLastValue(viewModel.message)
+ val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+ val canTryAgainNow by collectLastValue(viewModel.canTryAgainNow)
+
+ val errorJob = launch {
+ viewModel.showTemporaryError(
+ errorMessage,
+ authenticateAfterError = restart,
+ messageAfterError = helpAfterError,
+ )
+ }
+
+ assertThat(size).isEqualTo(PromptSize.MEDIUM)
+ assertThat(message).isEqualTo(PromptMessage.Error(errorMessage))
+ assertThat(messageVisible).isTrue()
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_ERROR)
+
+ // temporary error should disappear after a delay
+ errorJob.join()
+ if (helpAfterError.isNotBlank()) {
+ assertThat(message).isEqualTo(PromptMessage.Help(helpAfterError))
+ assertThat(messageVisible).isTrue()
+ } else {
+ assertThat(message).isEqualTo(PromptMessage.Empty)
+ assertThat(messageVisible).isFalse()
+ }
+ assertThat(legacyState)
+ .isEqualTo(
+ if (restart) {
+ AuthBiometricView.STATE_AUTHENTICATING
+ } else {
+ AuthBiometricView.STATE_HELP
+ }
+ )
+
+ assertThat(authenticating).isEqualTo(restart)
+ assertThat(authenticated?.isNotAuthenticated).isTrue()
+ assertThat(canTryAgainNow).isFalse()
+
+ block()
+ }
+
+ @Test
+ fun `no errors or temporary help after authenticated`() = runGenericTest {
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val message by collectLastValue(viewModel.message)
+ val messageIsShowing by collectLastValue(viewModel.isIndicatorMessageVisible)
+ val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ val verifyNoError = {
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+ assertThat(message).isEqualTo(PromptMessage.Empty)
+ assertThat(canTryAgain).isFalse()
+ }
+
+ val errorJob = launch { viewModel.showTemporaryError("error") }
+ verifyNoError()
+ errorJob.join()
+ verifyNoError()
+
+ val helpJob = launch { viewModel.showTemporaryHelp("hi") }
+ verifyNoError()
+ helpJob.join()
+ verifyNoError()
+
+ // persistent help is allowed
+ val stickyHelpMessage = "blah"
+ viewModel.showHelp(stickyHelpMessage)
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+ assertThat(message).isEqualTo(PromptMessage.Help(stickyHelpMessage))
+ assertThat(messageIsShowing).isTrue()
+ }
+
+ // @Test
+ fun `suppress errors`() = runGenericTest {
+ val errorMessage = "woot"
+ val message by collectLastValue(viewModel.message)
+
+ val errorJob = launch { viewModel.showTemporaryError(errorMessage) }
+ }
+
+ @Test
+ fun `authenticated at most once`() = runGenericTest {
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+ }
+
+ @Test
+ fun `authenticating cannot restart after authenticated`() = runGenericTest {
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+
+ viewModel.showAuthenticating("again!")
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+ }
+
+ @Test
+ fun `confirm authentication`() = runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val message by collectLastValue(viewModel.message)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+ val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+ assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
+ if (expectConfirmation) {
+ assertThat(size).isEqualTo(PromptSize.MEDIUM)
+ assertButtonsVisible(
+ cancel = true,
+ confirm = true,
+ )
+
+ viewModel.confirmAuthenticated()
+ assertThat(message).isEqualTo(PromptMessage.Empty)
+ assertButtonsVisible()
+ }
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+ assertThat(canTryAgain).isFalse()
+ }
+
+ @Test
+ fun `cannot confirm unless authenticated`() = runGenericTest {
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+
+ viewModel.confirmAuthenticated()
+ assertThat(authenticating).isTrue()
+ assertThat(authenticated?.isNotAuthenticated).isTrue()
+
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ // reconfirm should be a no-op
+ viewModel.confirmAuthenticated()
+ viewModel.confirmAuthenticated()
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isNotAuthenticated).isFalse()
+ }
+
+ @Test
+ fun `shows help - before authenticated`() = runGenericTest {
+ val helpMessage = "please help yourself to some cookies"
+ val message by collectLastValue(viewModel.message)
+ val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+
+ viewModel.showHelp(helpMessage)
+
+ assertThat(size).isEqualTo(PromptSize.MEDIUM)
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_HELP)
+ assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
+ assertThat(messageVisible).isTrue()
+
+ assertThat(viewModel.isAuthenticating.first()).isFalse()
+ assertThat(viewModel.isAuthenticated.first().isNotAuthenticated).isTrue()
+ }
+
+ @Test
+ fun `shows help - after authenticated`() = runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+ val helpMessage = "more cookies please"
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val message by collectLastValue(viewModel.message)
+ val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+
+ if (testCase.isCoex && testCase.authenticatedByFingerprint) {
+ viewModel.ensureFingerprintHasStarted(isDelayed = true)
+ }
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+ viewModel.showHelp(helpMessage)
+
+ assertThat(size).isEqualTo(PromptSize.MEDIUM)
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+ assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
+ assertThat(messageVisible).isTrue()
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+ assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
+ assertButtonsVisible(
+ cancel = expectConfirmation,
+ confirm = expectConfirmation,
+ )
+ }
+
+ @Test
+ fun `retries after failure`() = runGenericTest {
+ val errorMessage = "bad"
+ val helpMessage = "again?"
+ val expectTryAgainButton = testCase.isFaceOnly
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val message by collectLastValue(viewModel.message)
+ val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
+ val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+ viewModel.showAuthenticating("go")
+ val errorJob = launch {
+ viewModel.showTemporaryError(
+ errorMessage,
+ messageAfterError = helpMessage,
+ authenticateAfterError = false,
+ failedModality = testCase.authenticatedModality
+ )
+ }
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isFalse()
+ assertThat(message).isEqualTo(PromptMessage.Error(errorMessage))
+ assertThat(messageVisible).isTrue()
+ assertThat(canTryAgain).isEqualTo(testCase.authenticatedByFace)
+ assertButtonsVisible(negative = true, tryAgain = expectTryAgainButton)
+
+ errorJob.join()
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isFalse()
+ assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
+ assertThat(messageVisible).isTrue()
+ assertThat(canTryAgain).isEqualTo(testCase.authenticatedByFace)
+ assertButtonsVisible(negative = true, tryAgain = expectTryAgainButton)
+
+ val helpMessage2 = "foo"
+ viewModel.showAuthenticating(helpMessage2, isRetry = true)
+ assertThat(authenticating).isTrue()
+ assertThat(authenticated?.isAuthenticated).isFalse()
+ assertThat(message).isEqualTo(PromptMessage.Help(helpMessage2))
+ assertThat(messageVisible).isTrue()
+ assertButtonsVisible(negative = true)
+ }
+
+ @Test
+ fun `switch to credential fallback`() = runGenericTest {
+ val size by collectLastValue(viewModel.size)
+
+ // TODO(b/251476085): remove Spaghetti, migrate logic, and update this test
+ viewModel.onSwitchToCredential()
+
+ assertThat(size).isEqualTo(PromptSize.LARGE)
+ }
+
+ /** Asserts that the selected buttons are visible now. */
+ private suspend fun TestScope.assertButtonsVisible(
+ tryAgain: Boolean = false,
+ confirm: Boolean = false,
+ cancel: Boolean = false,
+ negative: Boolean = false,
+ credential: Boolean = false,
+ ) {
+ runCurrent()
+ assertThat(viewModel.isTryAgainButtonVisible.first()).isEqualTo(tryAgain)
+ assertThat(viewModel.isConfirmButtonVisible.first()).isEqualTo(confirm)
+ assertThat(viewModel.isCancelButtonVisible.first()).isEqualTo(cancel)
+ assertThat(viewModel.isNegativeButtonVisible.first()).isEqualTo(negative)
+ assertThat(viewModel.isCredentialButtonVisible.first()).isEqualTo(credential)
+ }
+
+ private fun runGenericTest(
+ doNotStart: Boolean = false,
+ allowCredentialFallback: Boolean = false,
+ block: suspend TestScope.() -> Unit
+ ) {
+ selector.initializePrompt(
+ requireConfirmation = testCase.confirmationRequested,
+ allowCredentialFallback = allowCredentialFallback,
+ fingerprint = testCase.fingerprint,
+ face = testCase.face,
+ )
+
+ // put the view model in the initial authenticating state, unless explicitly skipped
+ val startMode =
+ when {
+ doNotStart -> null
+ testCase.isCoex -> FingerprintStartMode.Delayed
+ else -> FingerprintStartMode.Normal
+ }
+ when (startMode) {
+ FingerprintStartMode.Normal -> {
+ viewModel.ensureFingerprintHasStarted(isDelayed = false)
+ viewModel.showAuthenticating()
+ }
+ FingerprintStartMode.Delayed -> {
+ viewModel.showAuthenticating()
+ }
+ else -> {
+ /* skip */
+ }
+ }
+
+ testScope.runTest { block() }
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data(): Collection<TestCase> = singleModalityTestCases + coexTestCases
+
+ private val singleModalityTestCases =
+ listOf(
+ TestCase(
+ face = faceSensorPropertiesInternal(strong = true).first(),
+ authenticatedModality = BiometricModality.Face,
+ ),
+ TestCase(
+ fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(),
+ authenticatedModality = BiometricModality.Fingerprint,
+ ),
+ TestCase(
+ face = faceSensorPropertiesInternal(strong = true).first(),
+ authenticatedModality = BiometricModality.Face,
+ confirmationRequested = true,
+ ),
+ TestCase(
+ fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(),
+ authenticatedModality = BiometricModality.Fingerprint,
+ confirmationRequested = true,
+ ),
+ )
+
+ private val coexTestCases =
+ listOf(
+ TestCase(
+ face = faceSensorPropertiesInternal(strong = true).first(),
+ fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(),
+ authenticatedModality = BiometricModality.Face,
+ ),
+ TestCase(
+ face = faceSensorPropertiesInternal(strong = true).first(),
+ fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(),
+ authenticatedModality = BiometricModality.Fingerprint,
+ ),
+ TestCase(
+ face = faceSensorPropertiesInternal(strong = true).first(),
+ fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(),
+ authenticatedModality = BiometricModality.Face,
+ confirmationRequested = true,
+ ),
+ TestCase(
+ face = faceSensorPropertiesInternal(strong = true).first(),
+ fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(),
+ authenticatedModality = BiometricModality.Fingerprint,
+ confirmationRequested = true,
+ ),
+ )
+ }
+}
+
+internal data class TestCase(
+ val fingerprint: FingerprintSensorPropertiesInternal? = null,
+ val face: FaceSensorPropertiesInternal? = null,
+ val authenticatedModality: BiometricModality,
+ val confirmationRequested: Boolean = false,
+) {
+ override fun toString(): String {
+ val modality =
+ when {
+ fingerprint != null && face != null -> "coex"
+ fingerprint != null -> "fingerprint only"
+ face != null -> "face only"
+ else -> "?"
+ }
+ return "[$modality, by: $authenticatedModality, confirm: $confirmationRequested]"
+ }
+
+ fun expectConfirmation(atLeastOneFailure: Boolean): Boolean =
+ when {
+ isCoex && authenticatedModality == BiometricModality.Face ->
+ atLeastOneFailure || confirmationRequested
+ isFaceOnly -> confirmationRequested
+ else -> false
+ }
+
+ val authenticatedByFingerprint: Boolean
+ get() = authenticatedModality == BiometricModality.Fingerprint
+
+ val authenticatedByFace: Boolean
+ get() = authenticatedModality == BiometricModality.Face
+
+ val isFaceOnly: Boolean
+ get() = face != null && fingerprint == null
+
+ val isFingerprintOnly: Boolean
+ get() = face == null && fingerprint != null
+
+ val isCoex: Boolean
+ get() = face != null && fingerprint != null
+
+ val shouldStartAsImplicitFlow: Boolean
+ get() = (isFaceOnly || isCoex) && !confirmationRequested
+}
+
+/** Initialize the test by selecting the give [fingerprint] or [face] configuration(s). */
+private fun PromptSelectorInteractor.initializePrompt(
+ fingerprint: FingerprintSensorPropertiesInternal? = null,
+ face: FaceSensorPropertiesInternal? = null,
+ requireConfirmation: Boolean = false,
+ allowCredentialFallback: Boolean = false,
+) {
+ val info =
+ PromptInfo().apply {
+ title = "t"
+ subtitle = "s"
+ authenticators = listOf(face, fingerprint).extractAuthenticatorTypes()
+ isDeviceCredentialAllowed = allowCredentialFallback
+ isConfirmationRequested = requireConfirmation
+ }
+ useBiometricsForAuthentication(
+ info,
+ requireConfirmation,
+ USER_ID,
+ CHALLENGE,
+ BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
+ )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 730f89dd76ba..9f5c181c3129 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -27,6 +27,7 @@ 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.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -75,7 +76,7 @@ class BouncerInteractorTest : SysuiTestCase() {
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
underTest.clearMessage()
- assertThat(message).isNull()
+ assertThat(message).isEmpty()
underTest.resetMessage()
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -107,7 +108,7 @@ class BouncerInteractorTest : SysuiTestCase() {
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
underTest.clearMessage()
- assertThat(message).isNull()
+ assertThat(message).isEmpty()
underTest.resetMessage()
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
@@ -139,7 +140,7 @@ class BouncerInteractorTest : SysuiTestCase() {
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
underTest.clearMessage()
- assertThat(message).isNull()
+ assertThat(message).isEmpty()
underTest.resetMessage()
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
@@ -201,6 +202,56 @@ class BouncerInteractorTest : SysuiTestCase() {
assertThat(message).isEqualTo(customMessage)
}
+ @Test
+ fun throttling() =
+ testScope.runTest {
+ val throttling by collectLastValue(underTest.throttling)
+ val message by collectLastValue(underTest.message)
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(throttling).isNull()
+ assertThat(message).isEqualTo("")
+ assertThat(isUnlocked).isFalse()
+ repeat(BouncerInteractor.THROTTLE_EVERY) { times ->
+ // Wrong PIN.
+ underTest.authenticate(listOf(6, 7, 8, 9))
+ if (times < BouncerInteractor.THROTTLE_EVERY - 1) {
+ assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
+ }
+ }
+ assertThat(throttling).isNotNull()
+ assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+
+ // Correct PIN, but throttled, so doesn't unlock:
+ underTest.authenticate(listOf(1, 2, 3, 4))
+ assertThat(isUnlocked).isFalse()
+ assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+
+ throttling?.totalDurationSec?.let { seconds ->
+ repeat(seconds) { time ->
+ advanceTimeBy(1000)
+ val remainingTime = seconds - time - 1
+ if (remainingTime > 0) {
+ assertTryAgainMessage(message, remainingTime)
+ }
+ }
+ }
+ assertThat(message).isEqualTo("")
+ assertThat(throttling).isNull()
+ assertThat(isUnlocked).isFalse()
+
+ // Correct PIN and no longer throttled so unlocks:
+ underTest.authenticate(listOf(1, 2, 3, 4))
+ assertThat(isUnlocked).isTrue()
+ }
+
+ private fun assertTryAgainMessage(
+ message: String?,
+ time: Int,
+ ) {
+ assertThat(message).isEqualTo("Try again in $time seconds.")
+ }
+
companion object {
private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN"
private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 954e67d77181..b942ccbb51f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -19,11 +19,15 @@ package com.android.systemui.bouncer.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,13 +44,12 @@ class BouncerViewModelTest : SysuiTestCase() {
utils.authenticationInteractor(
repository = utils.authenticationRepository(),
)
- private val underTest =
- utils.bouncerViewModel(
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = utils.sceneInteractor(),
- )
+ private val bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = utils.sceneInteractor(),
)
+ private val underTest = utils.bouncerViewModel(bouncerInteractor)
@Test
fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =
@@ -89,6 +92,65 @@ class BouncerViewModelTest : SysuiTestCase() {
.isEqualTo(AuthenticationMethodModel::class.sealedSubclasses.toSet())
}
+ @Test
+ fun isMessageUpdateAnimationsEnabled() =
+ testScope.runTest {
+ val isMessageUpdateAnimationsEnabled by
+ collectLastValue(underTest.isMessageUpdateAnimationsEnabled)
+ val throttling by collectLastValue(bouncerInteractor.throttling)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(isMessageUpdateAnimationsEnabled).isTrue()
+
+ repeat(BouncerInteractor.THROTTLE_EVERY) {
+ // Wrong PIN.
+ bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+ }
+ assertThat(isMessageUpdateAnimationsEnabled).isFalse()
+
+ throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+ assertThat(isMessageUpdateAnimationsEnabled).isTrue()
+ }
+
+ @Test
+ fun isInputEnabled() =
+ testScope.runTest {
+ val isInputEnabled by
+ collectLastValue(
+ underTest.authMethod.flatMapLatest { authViewModel ->
+ authViewModel?.isInputEnabled ?: emptyFlow()
+ }
+ )
+ val throttling by collectLastValue(bouncerInteractor.throttling)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(isInputEnabled).isTrue()
+
+ repeat(BouncerInteractor.THROTTLE_EVERY) {
+ // Wrong PIN.
+ bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+ }
+ assertThat(isInputEnabled).isFalse()
+
+ throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+ assertThat(isInputEnabled).isTrue()
+ }
+
+ @Test
+ fun throttlingDialogMessage() =
+ testScope.runTest {
+ val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+
+ repeat(BouncerInteractor.THROTTLE_EVERY) {
+ // Wrong PIN.
+ assertThat(throttlingDialogMessage).isNull()
+ bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+ }
+ assertThat(throttlingDialogMessage).isNotEmpty()
+
+ underTest.onThrottlingDialogDismissed()
+ assertThat(throttlingDialogMessage).isNull()
+ }
+
private fun authMethodsToTest(): List<AuthenticationMethodModel> {
return listOf(
AuthenticationMethodModel.None,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index e48b6386c739..b7b90de3b54a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -26,6 +26,8 @@ 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.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -57,6 +59,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
private val underTest =
PasswordBouncerViewModel(
interactor = bouncerInteractor,
+ isInputEnabled = MutableStateFlow(true).asStateFlow(),
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 6ce29e67982c..b588ba2b2574 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -27,6 +27,8 @@ import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -60,6 +62,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
applicationContext = context,
applicationScope = testScope.backgroundScope,
interactor = bouncerInteractor,
+ isInputEnabled = MutableStateFlow(true).asStateFlow(),
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index bb28520ad8a0..83f9687d7ac5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -27,6 +27,8 @@ 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.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
@@ -68,6 +70,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
PinBouncerViewModel(
applicationScope = testScope.backgroundScope,
interactor = bouncerInteractor,
+ isInputEnabled = MutableStateFlow(true).asStateFlow(),
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 548d26f2aaed..78a65a8473db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -98,7 +98,8 @@ class ResourceTrimmerTest : SysuiTestCase() {
)
testScope.runCurrent()
verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+ verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
}
@Test
@@ -115,7 +116,8 @@ class ResourceTrimmerTest : SysuiTestCase() {
)
testScope.runCurrent()
verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+ verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
}
@Test
@@ -161,7 +163,8 @@ class ResourceTrimmerTest : SysuiTestCase() {
keyguardRepository.setDozeAmount(1f)
testScope.runCurrent()
verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+ verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 3bcefcf6ffff..56698e0ec41c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -41,7 +41,6 @@ import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
-import com.android.internal.statusbar.IStatusBarService
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.R
@@ -133,7 +132,6 @@ class MediaDataManagerTest : SysuiTestCase() {
@Mock lateinit var activityStarter: ActivityStarter
@Mock lateinit var smartspaceManager: SmartspaceManager
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock lateinit var statusBarService: IStatusBarService
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -197,7 +195,6 @@ class MediaDataManagerTest : SysuiTestCase() {
logger = logger,
smartspaceManager = smartspaceManager,
keyguardUpdateMonitor = keyguardUpdateMonitor,
- statusBarService = statusBarService,
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -522,143 +519,12 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
- fun testOnNotificationAdded_emptyTitle_isRequired_notLoaded() {
- // When the manager has a notification with an empty title, and the app is required
- // to include a non-empty title
- whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
- whenever(controller.metadata)
- .thenReturn(
- metadataBuilder
- .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
- .build()
- )
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-
- // Then the media control is not added and we report a notification error
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(statusBarService)
- .onNotificationError(
- eq(PACKAGE_NAME),
- eq(mediaNotification.tag),
- eq(mediaNotification.id),
- eq(mediaNotification.uid),
- eq(mediaNotification.initialPid),
- eq(MEDIA_TITLE_ERROR_MESSAGE),
- eq(mediaNotification.user.identifier)
- )
- verify(listener, never())
- .onMediaDataLoaded(
- eq(KEY),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
- verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
- verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
- }
-
- @Test
- fun testOnNotificationAdded_blankTitle_isRequired_notLoaded() {
- // When the manager has a notification with a blank title, and the app is required
- // to include a non-empty title
- whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
- whenever(controller.metadata)
- .thenReturn(
- metadataBuilder
- .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
- .build()
- )
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-
- // Then the media control is not added and we report a notification error
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(statusBarService)
- .onNotificationError(
- eq(PACKAGE_NAME),
- eq(mediaNotification.tag),
- eq(mediaNotification.id),
- eq(mediaNotification.uid),
- eq(mediaNotification.initialPid),
- eq(MEDIA_TITLE_ERROR_MESSAGE),
- eq(mediaNotification.user.identifier)
- )
- verify(listener, never())
- .onMediaDataLoaded(
- eq(KEY),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
- verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
- verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
- }
-
- @Test
- fun testOnNotificationUpdated_invalidTitle_isRequired_logMediaRemoved() {
- // When the app is required to provide a non-blank title, and updates a previously valid
- // title to an empty one
- whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
- addNotificationAndLoad()
- val data = mediaDataCaptor.value
-
- verify(listener)
- .onMediaDataLoaded(
- eq(KEY),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
-
- reset(listener)
- whenever(controller.metadata)
- .thenReturn(
- metadataBuilder
- .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
- .build()
- )
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-
- // Then the media control is removed
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(statusBarService)
- .onNotificationError(
- eq(PACKAGE_NAME),
- eq(mediaNotification.tag),
- eq(mediaNotification.id),
- eq(mediaNotification.uid),
- eq(mediaNotification.initialPid),
- eq(MEDIA_TITLE_ERROR_MESSAGE),
- eq(mediaNotification.user.identifier)
- )
- verify(listener, never())
- .onMediaDataLoaded(
- eq(KEY),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
- verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
- }
-
- @Test
- fun testOnNotificationAdded_emptyTitle_notRequired_hasPlaceholder() {
+ fun testOnNotificationAdded_emptyTitle_hasPlaceholder() {
// When the manager has a notification with an empty title, and the app is not
// required to include a non-empty title
val mockPackageManager = mock(PackageManager::class.java)
context.setMockPackageManager(mockPackageManager)
whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
- whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false)
whenever(controller.metadata)
.thenReturn(
metadataBuilder
@@ -684,13 +550,12 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
- fun testOnNotificationAdded_blankTitle_notRequired_hasPlaceholder() {
+ fun testOnNotificationAdded_blankTitle_hasPlaceholder() {
// GIVEN that the manager has a notification with a blank title, and the app is not
// required to include a non-empty title
val mockPackageManager = mock(PackageManager::class.java)
context.setMockPackageManager(mockPackageManager)
whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
- whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false)
whenever(controller.metadata)
.thenReturn(
metadataBuilder
@@ -722,7 +587,6 @@ class MediaDataManagerTest : SysuiTestCase() {
val mockPackageManager = mock(PackageManager::class.java)
context.setMockPackageManager(mockPackageManager)
whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
- whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
whenever(controller.metadata)
.thenReturn(
metadataBuilder
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 8b070ef87504..079ef372cae6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -63,6 +63,7 @@ import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.Bubbles
@@ -649,7 +650,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
@Test
- fun onRoleHoldersChanged_notesRole_sameUser_shouldUpdateShortcuts() {
+ fun onRoleHoldersChanged_notesRole_shouldUpdateShortcuts() {
val user = userTracker.userHandle
val controller = spy(createNoteTaskController())
doNothing().whenever(controller).updateNoteTaskAsUser(any())
@@ -658,22 +659,41 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
verify(controller).updateNoteTaskAsUser(user)
}
+ // endregion
+
+ // region updateNoteTaskAsUser
+ @Test
+ fun updateNoteTaskAsUser_sameUser_shouldUpdateShortcuts() {
+ val user = userTracker.userHandle
+ val controller = spy(createNoteTaskController())
+ doNothing().whenever(controller).updateNoteTaskAsUserInternal(any())
+
+ controller.updateNoteTaskAsUser(user)
+
+ verify(controller).updateNoteTaskAsUserInternal(user)
+ verify(context, never()).startServiceAsUser(any(), any())
+ }
@Test
- fun onRoleHoldersChanged_notesRole_differentUser_shouldUpdateShortcutsInUserProcess() {
+ fun updateNoteTaskAsUser_differentUser_shouldUpdateShortcutsInUserProcess() {
// FakeUserTracker will default to UserHandle.SYSTEM.
val user = UserHandle.CURRENT
+ val controller = spy(createNoteTaskController(isEnabled = true))
+ doNothing().whenever(controller).updateNoteTaskAsUserInternal(any())
- createNoteTaskController(isEnabled = true).onRoleHoldersChanged(ROLE_NOTES, user)
+ controller.updateNoteTaskAsUser(user)
- verify(context).startServiceAsUser(any(), eq(user))
+ verify(controller, never()).updateNoteTaskAsUserInternal(any())
+ val intent = withArgCaptor { verify(context).startServiceAsUser(capture(), eq(user)) }
+ assertThat(intent).hasComponentClass(NoteTaskControllerUpdateService::class.java)
}
// endregion
- // region updateNoteTaskAsUser
+ // region internalUpdateNoteTaskAsUser
@Test
- fun updateNoteTaskAsUser_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
- createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
+ fun updateNoteTaskAsUserInternal_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
+ createNoteTaskController(isEnabled = true)
+ .updateNoteTaskAsUserInternal(userTracker.userHandle)
val actualComponent = argumentCaptor<ComponentName>()
verify(context.packageManager)
@@ -702,11 +722,12 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
@Test
- fun updateNoteTaskAsUser_noNotesRole_shouldDisableShortcuts() {
+ fun updateNoteTaskAsUserInternal_noNotesRole_shouldDisableShortcuts() {
whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle))
.thenReturn(emptyList())
- createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
+ createNoteTaskController(isEnabled = true)
+ .updateNoteTaskAsUserInternal(userTracker.userHandle)
val argument = argumentCaptor<ComponentName>()
verify(context.packageManager)
@@ -723,8 +744,9 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
@Test
- fun updateNoteTaskAsUser_flagDisabled_shouldDisableShortcuts() {
- createNoteTaskController(isEnabled = false).updateNoteTaskAsUser(userTracker.userHandle)
+ fun updateNoteTaskAsUserInternal_flagDisabled_shouldDisableShortcuts() {
+ createNoteTaskController(isEnabled = false)
+ .updateNoteTaskAsUserInternal(userTracker.userHandle)
val argument = argumentCaptor<ComponentName>()
verify(context.packageManager)
@@ -741,6 +763,20 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
// endregion
+ // startregion updateNoteTaskForAllUsers
+ @Test
+ fun updateNoteTaskForAllUsers_shouldRunUpdateForCurrentUserAndProfiles() {
+ userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+ val controller = spy(createNoteTaskController())
+ doNothing().whenever(controller).updateNoteTaskAsUser(any())
+
+ controller.updateNoteTaskForCurrentUserAndManagedProfiles()
+
+ verify(controller).updateNoteTaskAsUser(mainUserInfo.userHandle)
+ verify(controller).updateNoteTaskAsUser(workUserInfo.userHandle)
+ }
+ // endregion
+
// region getUserForHandlingNotesTaking
@Test
fun getUserForHandlingNotesTaking_cope_quickAffordance_shouldReturnWorkProfileUser() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 4e85b6c555ef..95bb3e0a4538 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -32,9 +32,12 @@ import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
@@ -71,19 +74,19 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
}
private fun createUnderTest(
- isEnabled: Boolean,
- bubbles: Bubbles?,
+ isEnabled: Boolean,
+ bubbles: Bubbles?,
): NoteTaskInitializer =
- NoteTaskInitializer(
- controller = controller,
- commandQueue = commandQueue,
- optionalBubbles = Optional.ofNullable(bubbles),
- isEnabled = isEnabled,
- roleManager = roleManager,
- userTracker = userTracker,
- keyguardUpdateMonitor = keyguardMonitor,
- backgroundExecutor = executor,
- )
+ NoteTaskInitializer(
+ controller = controller,
+ commandQueue = commandQueue,
+ optionalBubbles = Optional.ofNullable(bubbles),
+ isEnabled = isEnabled,
+ roleManager = roleManager,
+ userTracker = userTracker,
+ keyguardUpdateMonitor = keyguardMonitor,
+ backgroundExecutor = executor,
+ )
@Test
fun initialize_withUserUnlocked() {
@@ -93,8 +96,8 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
verify(commandQueue).addCallback(any())
verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
- verify(controller).setNoteTaskShortcutEnabled(any(), any())
- verify(keyguardMonitor, never()).registerCallback(any())
+ verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles()
+ verify(keyguardMonitor).registerCallback(any())
}
@Test
@@ -107,6 +110,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
verify(controller, never()).setNoteTaskShortcutEnabled(any(), any())
verify(keyguardMonitor).registerCallback(any())
+ assertThat(userTracker.callbacks).isNotEmpty()
}
@Test
@@ -116,12 +120,12 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
underTest.initialize()
verifyZeroInteractions(
- commandQueue,
- bubbles,
- controller,
- roleManager,
- userManager,
- keyguardMonitor,
+ commandQueue,
+ bubbles,
+ controller,
+ roleManager,
+ userManager,
+ keyguardMonitor,
)
}
@@ -132,12 +136,12 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
underTest.initialize()
verifyZeroInteractions(
- commandQueue,
- bubbles,
- controller,
- roleManager,
- userManager,
- keyguardMonitor,
+ commandQueue,
+ bubbles,
+ controller,
+ roleManager,
+ userManager,
+ keyguardMonitor,
)
}
@@ -146,7 +150,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
val expectedKeyEvent = KeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL)
val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
underTest.initialize()
- val callback = captureArgument { verify(commandQueue).addCallback(capture()) }
+ val callback = withArgCaptor { verify(commandQueue).addCallback(capture()) }
callback.handleSystemKey(expectedKeyEvent)
@@ -154,31 +158,49 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
}
@Test
- fun initialize_userUnlocked() {
+ fun initialize_userUnlocked_shouldUpdateNoteTask() {
whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(false)
val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
underTest.initialize()
- val callback = captureArgument { verify(keyguardMonitor).registerCallback(capture()) }
+ val callback = withArgCaptor { verify(keyguardMonitor).registerCallback(capture()) }
whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(true)
callback.onUserUnlocked()
- verify(controller).setNoteTaskShortcutEnabled(any(), any())
+
+ verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles()
}
@Test
- fun initialize_onRoleHoldersChanged() {
+ fun initialize_onRoleHoldersChanged_shouldRunOnRoleHoldersChanged() {
val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
underTest.initialize()
- val callback = captureArgument {
+ val callback = withArgCaptor {
verify(roleManager)
- .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL))
+ .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL))
}
callback.onRoleHoldersChanged(ROLE_NOTES, userTracker.userHandle)
verify(controller).onRoleHoldersChanged(ROLE_NOTES, userTracker.userHandle)
}
-}
-private inline fun <reified T : Any> captureArgument(block: ArgumentCaptor<T>.() -> Unit) =
- argumentCaptor<T>().apply(block).value
+ @Test
+ fun initialize_onProfilesChanged_shouldUpdateNoteTask() {
+ val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
+ underTest.initialize()
+
+ userTracker.callbacks.first().onProfilesChanged(emptyList())
+
+ verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles()
+ }
+
+ @Test
+ fun initialize_onUserChanged_shouldUpdateNoteTask() {
+ val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
+ underTest.initialize()
+
+ userTracker.callbacks.first().onUserChanged(0, mock())
+
+ verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index 2eca78a0412b..e92368df8663 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.shared.clocks
import android.testing.AndroidTestingRunner
import android.view.LayoutInflater
import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.TextAnimator
@@ -64,8 +65,8 @@ class AnimatableClockViewTest : SysuiTestCase() {
color = 200,
strokeWidth = -1F,
animate = false,
- duration = 350L,
- interpolator = null,
+ duration = 833L,
+ interpolator = Interpolators.EMPHASIZED_DECELERATE,
delay = 0L,
onAnimationEnd = null
)
@@ -98,8 +99,8 @@ class AnimatableClockViewTest : SysuiTestCase() {
color = 200,
strokeWidth = -1F,
animate = true,
- duration = 350L,
- interpolator = null,
+ duration = 833L,
+ interpolator = Interpolators.EMPHASIZED_DECELERATE,
delay = 0L,
onAnimationEnd = null
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index f4cd383f7c4c..1643e174ee13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.ComponentName;
import android.graphics.Rect;
-import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
@@ -443,15 +442,13 @@ public class CommandQueueTest extends SysuiTestCase {
final long operationId = 1;
final String packageName = "test";
final long requestId = 10;
- final int multiSensorConfig = BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
mCommandQueue.showAuthenticationDialog(promptInfo, receiver, sensorIds,
- credentialAllowed, requireConfirmation, userId, operationId, packageName, requestId,
- multiSensorConfig);
+ credentialAllowed, requireConfirmation, userId, operationId, packageName, requestId);
waitForIdleSync();
verify(mCallbacks).showAuthenticationDialog(eq(promptInfo), eq(receiver), eq(sensorIds),
eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(operationId),
- eq(packageName), eq(requestId), eq(multiSensorConfig));
+ eq(packageName), eq(requestId));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 551499e0fb55..7632d01d4d43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -392,23 +392,32 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
@Test
public void testSnapchild_targetIsZero() {
- doNothing().when(mSwipeHelper).superSnapChild(mView, 0, 0);
- mSwipeHelper.snapChild(mView, 0, 0);
+ doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 0, 0);
+ mSwipeHelper.snapChild(mNotificationRow, 0, 0);
- verify(mCallback, times(1)).onDragCancelled(mView);
- verify(mSwipeHelper, times(1)).superSnapChild(mView, 0, 0);
+ verify(mCallback, times(1)).onDragCancelled(mNotificationRow);
+ verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 0, 0);
verify(mSwipeHelper, times(1)).handleMenuCoveredOrDismissed();
}
@Test
public void testSnapchild_targetNotZero() {
+ doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 10, 0);
+ mSwipeHelper.snapChild(mNotificationRow, 10, 0);
+
+ verify(mCallback, times(1)).onDragCancelled(mNotificationRow);
+ verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 10, 0);
+ verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed();
+ }
+
+ @Test
+ public void testSnapchild_targetNotSwipeable() {
doNothing().when(mSwipeHelper).superSnapChild(mView, 10, 0);
mSwipeHelper.snapChild(mView, 10, 0);
- verify(mCallback, times(1)).onDragCancelled(mView);
- verify(mSwipeHelper, times(1)).superSnapChild(mView, 10, 0);
- verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed();
+ verify(mCallback).onDragCancelled(mView);
+ verify(mSwipeHelper, never()).superSnapChild(mView, 10, 0);
}
@Test
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 45a37cffa588..8f725bebfb16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -35,7 +35,6 @@ import android.app.KeyguardManager;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.os.SystemClock;
-import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Gravity;
@@ -47,7 +46,6 @@ import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -62,9 +60,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
-import com.android.systemui.util.DeviceConfigProxyFake;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
@@ -88,8 +83,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
View mDrawerVibrate;
View mDrawerMute;
View mDrawerNormal;
- private DeviceConfigProxyFake mDeviceConfigProxy;
- private FakeExecutor mExecutor;
private TestableLooper mTestableLooper;
private ConfigurationController mConfigurationController;
private int mOriginalOrientation;
@@ -131,8 +124,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
getContext().addMockSystemService(KeyguardManager.class, mKeyguard);
mTestableLooper = TestableLooper.get(this);
- mDeviceConfigProxy = new DeviceConfigProxyFake();
- mExecutor = new FakeExecutor(new FakeSystemClock());
when(mPostureController.getDevicePosture())
.thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
@@ -151,8 +142,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mVolumePanelFactory,
mActivityStarter,
mInteractionJankMonitor,
- mDeviceConfigProxy,
- mExecutor,
mCsdWarningDialogFactory,
mPostureController,
mTestableLooper.getLooper(),
@@ -173,9 +162,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1);
Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
-
- mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
}
private State createShellState() {
@@ -351,13 +337,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
* API does not exist. So we do the next best thing; we check the cached icon id.
*/
@Test
- public void notificationVolumeSeparated_theRingerIconChanges() {
- mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
-
- mExecutor.runAllReady(); // for the config change to take effect
-
- // assert icon is new based on res id
+ public void notificationVolumeSeparated_theRingerIconChangesToSpeakerIcon() {
+ // already separated. assert icon is new based on res id
assertEquals(mDialog.mVolumeRingerIconDrawableId,
R.drawable.ic_speaker_on);
assertEquals(mDialog.mVolumeRingerMuteIconDrawableId,
@@ -365,17 +346,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
}
@Test
- public void notificationVolumeNotSeparated_theRingerIconRemainsTheSame() {
- mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
-
- mExecutor.runAllReady();
-
- assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_volume_ringer);
- assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute);
- }
-
- @Test
public void testDialogDismissAnimation_notifyVisibleIsNotCalledBeforeAnimation() {
mDialog.dismissH(DISMISS_REASON_UNKNOWN);
// notifyVisible(false) should not be called immediately but only after the dismiss
@@ -408,8 +378,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mVolumePanelFactory,
mActivityStarter,
mInteractionJankMonitor,
- mDeviceConfigProxy,
- mExecutor,
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
@@ -447,8 +415,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mVolumePanelFactory,
mActivityStarter,
mInteractionJankMonitor,
- mDeviceConfigProxy,
- mExecutor,
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
@@ -485,8 +451,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mVolumePanelFactory,
mActivityStarter,
mInteractionJankMonitor,
- mDeviceConfigProxy,
- mExecutor,
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
@@ -525,8 +489,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mVolumePanelFactory,
mActivityStarter,
mInteractionJankMonitor,
- mDeviceConfigProxy,
- mExecutor,
mCsdWarningDialogFactory,
mPostureController,
mTestableLooper.getLooper(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index 96658c61109d..d270700aa856 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -1,7 +1,7 @@
package com.android.systemui.biometrics.data.repository
import android.hardware.biometrics.PromptInfo
-import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.biometrics.shared.model.PromptKind
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -20,26 +20,32 @@ class FakePromptRepository : PromptRepository {
private var _challenge = MutableStateFlow<Long?>(null)
override val challenge = _challenge.asStateFlow()
- private val _kind = MutableStateFlow(PromptKind.ANY_BIOMETRIC)
+ private val _kind = MutableStateFlow<PromptKind>(PromptKind.Biometric())
override val kind = _kind.asStateFlow()
+ private val _isConfirmationRequired = MutableStateFlow(false)
+ override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
+
override fun setPrompt(
promptInfo: PromptInfo,
userId: Int,
gatekeeperChallenge: Long?,
- kind: PromptKind
+ kind: PromptKind,
+ requireConfirmation: Boolean,
) {
_promptInfo.value = promptInfo
_userId.value = userId
_challenge.value = gatekeeperChallenge
_kind.value = kind
+ _isConfirmationRequired.value = requireConfirmation
}
override fun unsetPrompt() {
_promptInfo.value = null
_userId.value = null
_challenge.value = null
- _kind.value = PromptKind.ANY_BIOMETRIC
+ _kind.value = PromptKind.Biometric()
+ _isConfirmationRequired.value = false
}
fun setIsShowing(showing: Boolean) {
diff --git a/services/Android.bp b/services/Android.bp
index b0a0e5e44a8c..7b64b4714017 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -58,7 +58,12 @@ system_optimized_java_defaults {
optimize: false,
shrink: true,
ignore_warnings: false,
- proguard_flags_files: ["proguard.flags"],
+ proguard_flags_files: [
+ "proguard.flags",
+ // Ensure classes referenced in the framework-res manifest
+ // and implemented in system_server are kept.
+ ":framework-res{.aapt.proguardOptionsFile}",
+ ],
},
conditions_default: {
optimize: {
diff --git a/services/accessibility/lint-baseline.xml b/services/accessibility/lint-baseline.xml
new file mode 100644
index 000000000000..6bec8cf5f018
--- /dev/null
+++ b/services/accessibility/lint-baseline.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IAccessibilityManager permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingOrSelfPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java"
+ line="3992"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IAccessibilityManager permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingOrSelfPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java"
+ line="4007"
+ column="9"/>
+ </issue>
+
+</issues>
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index 4aeb4a4f8409..cd6de8749f47 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -349,7 +349,8 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand {
private int isFieldDetectionServiceEnabled(PrintWriter pw) {
final int userId = getNextIntArgRequired();
String name = mService.getFieldDetectionServiceName(userId);
- boolean enabled = !TextUtils.isEmpty(name);
+ boolean pccFlagEnabled = mService.isPccClassificationFlagEnabled();
+ boolean enabled = (!TextUtils.isEmpty(name)) && pccFlagEnabled;
pw.println(enabled);
return 0;
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 1098ce644c07..b6baf7e89a5d 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -2863,8 +2863,7 @@ public class UserBackupManagerService {
if (DEBUG) {
Slog.d(
TAG,
- addUserIdToLogMessage(
- mUserId, "Starting backup confirmation UI, token=" + token));
+ addUserIdToLogMessage(mUserId, "Starting backup confirmation UI"));
}
if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
Slog.e(
diff --git a/services/backup/lint-baseline.xml b/services/backup/lint-baseline.xml
index 28bb937cfd9c..93c9390feb9c 100644
--- a/services/backup/lint-baseline.xml
+++ b/services/backup/lint-baseline.xml
@@ -12,4 +12,28 @@
column="16"/>
</issue>
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IRestoreSession permission check should be converted to @EnforcePermission annotation">
+ <location
+ file="frameworks/base/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java"
+ line="88"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IRestoreSession permission check should be converted to @EnforcePermission annotation">
+ <location
+ file="frameworks/base/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java"
+ line="144"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IRestoreSession permission check should be converted to @EnforcePermission annotation">
+ <location
+ file="frameworks/base/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java"
+ line="207"/>
+ </issue>
+
</issues>
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
new file mode 100644
index 000000000000..715cf9a8807e
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
@@ -0,0 +1,111 @@
+/*
+ * 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.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageInfo;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Manages whether the content protection is enabled for an app using a blocklist.
+ *
+ * @hide
+ */
+class ContentProtectionBlocklistManager {
+
+ private static final String TAG = "ContentProtectionBlocklistManager";
+
+ private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
+ "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
+
+ @NonNull private final ContentProtectionPackageManager mContentProtectionPackageManager;
+
+ @Nullable private Set<String> mPackageNameBlocklist;
+
+ protected ContentProtectionBlocklistManager(
+ @NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
+ mContentProtectionPackageManager = contentProtectionPackageManager;
+ }
+
+ public boolean isAllowed(@NonNull String packageName) {
+ if (mPackageNameBlocklist == null) {
+ // List not loaded or failed to load, don't run on anything
+ return false;
+ }
+ if (mPackageNameBlocklist.contains(packageName)) {
+ return false;
+ }
+ PackageInfo packageInfo = mContentProtectionPackageManager.getPackageInfo(packageName);
+ if (packageInfo == null) {
+ return false;
+ }
+ if (!mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo)) {
+ return false;
+ }
+ if (mContentProtectionPackageManager.isSystemApp(packageInfo)) {
+ return false;
+ }
+ if (mContentProtectionPackageManager.isUpdatedSystemApp(packageInfo)) {
+ return false;
+ }
+ return true;
+ }
+
+ public void updateBlocklist(int blocklistSize) {
+ Slog.i(TAG, "Blocklist size updating to: " + blocklistSize);
+ mPackageNameBlocklist = readPackageNameBlocklist(blocklistSize);
+ }
+
+ @Nullable
+ private Set<String> readPackageNameBlocklist(int blocklistSize) {
+ if (blocklistSize <= 0) {
+ // Explicitly requested an empty blocklist
+ return Collections.emptySet();
+ }
+ List<String> lines = readLinesFromRawFile(PACKAGE_NAME_BLOCKLIST_FILENAME);
+ if (lines == null) {
+ return null;
+ }
+ return lines.stream().limit(blocklistSize).collect(Collectors.toSet());
+ }
+
+ @VisibleForTesting
+ @Nullable
+ protected List<String> readLinesFromRawFile(@NonNull String filename) {
+ try (FileReader fileReader = new FileReader(filename);
+ BufferedReader bufferedReader = new BufferedReader(fileReader)) {
+ return bufferedReader
+ .lines()
+ .map(line -> line.trim())
+ .filter(line -> !line.isBlank())
+ .collect(Collectors.toList());
+ } catch (Exception ex) {
+ Slog.e(TAG, "Failed to read: " + filename, ex);
+ return null;
+ }
+ }
+}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
index 5221468e125a..1847e5d708a3 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
@@ -34,8 +34,9 @@ import java.util.Arrays;
*
* @hide
*/
-final class ContentProtectionPackageManager {
- private static final String TAG = ContentProtectionPackageManager.class.getSimpleName();
+public class ContentProtectionPackageManager {
+
+ private static final String TAG = "ContentProtectionPackageManager";
private static final PackageInfoFlags PACKAGE_INFO_FLAGS =
PackageInfoFlags.of(PackageManager.GET_PERMISSIONS);
@@ -51,7 +52,7 @@ final class ContentProtectionPackageManager {
try {
return mPackageManager.getPackageInfo(packageName, PACKAGE_INFO_FLAGS);
} catch (NameNotFoundException ex) {
- Slog.w(TAG, "Package info not found: ", ex);
+ Slog.w(TAG, "Package info not found for: " + packageName, ex);
return null;
}
}
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index e2fd37e7177a..9554e63b882b 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -36,6 +36,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ExtconUEventObserver.ExtconInfo;
import java.io.FileDescriptor;
@@ -194,6 +195,8 @@ final class DockObserver extends SystemService {
@Override
public void onStart() {
publishBinderService(TAG, new BinderService());
+ // Logs dock state after setDockStateFromProviderLocked sets mReportedDockState
+ FrameworkStatsLog.write(FrameworkStatsLog.DOCK_STATE_CHANGED, mReportedDockState);
}
@Override
@@ -255,7 +258,6 @@ final class DockObserver extends SystemService {
+ mReportedDockState);
final int previousDockState = mPreviousDockState;
mPreviousDockState = mReportedDockState;
-
// Skip the dock intent if not yet provisioned.
final ContentResolver cr = getContext().getContentResolver();
if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 123cd3288343..55130e4cbbe6 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,3 +1,6 @@
+# BootReceiver
+per-file BootReceiver.java = gaillard@google.com
+
# Connectivity / Networking
per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java,VpnManagerService.java = file:/services/core/java/com/android/server/net/OWNERS
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b9e1bd033706..aac4e7f75f81 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -8298,8 +8298,8 @@ public final class ActiveServices {
r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService
: ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT,
0 /* api_sate */,
- 0 /* api_type */,
- 0 /* api_timestamp */,
+ null /* api_type */,
+ null /* api_timestamp */,
mAm.getUidStateLocked(r.appInfo.uid),
mAm.getUidProcessCapabilityLocked(r.appInfo.uid),
mAm.getUidStateLocked(r.mRecentCallingUid),
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 030d596a1676..8c1fd516028e 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -292,6 +292,15 @@ public class BroadcastConstants {
private static final String KEY_CORE_DEFER_UNTIL_ACTIVE = "bcast_core_defer_until_active";
private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = true;
+ /**
+ * For {@link BroadcastQueueModernImpl}: How frequently we should check for the pending
+ * cold start validity.
+ */
+ public long PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 30 * 1000;
+ private static final String KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS =
+ "pending_cold_start_check_interval_millis";
+ private static final long DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 30_000;
+
// Settings override tracking for this instance
private String mSettingsKey;
private SettingsObserver mSettingsObserver;
@@ -441,6 +450,9 @@ public class BroadcastConstants {
DEFAULT_MAX_HISTORY_SUMMARY_SIZE);
CORE_DEFER_UNTIL_ACTIVE = getDeviceConfigBoolean(KEY_CORE_DEFER_UNTIL_ACTIVE,
DEFAULT_CORE_DEFER_UNTIL_ACTIVE);
+ PENDING_COLD_START_CHECK_INTERVAL_MILLIS = getDeviceConfigLong(
+ KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS,
+ DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS);
}
// TODO: migrate BroadcastRecord to accept a BroadcastConstants
@@ -499,6 +511,8 @@ public class BroadcastConstants {
MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
pw.print(KEY_CORE_DEFER_UNTIL_ACTIVE,
CORE_DEFER_UNTIL_ACTIVE).println();
+ pw.print(KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS,
+ PENDING_COLD_START_CHECK_INTERVAL_MILLIS).println();
pw.decreaseIndent();
pw.println();
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 2a8c3787e58e..9e4e87975a28 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -248,6 +248,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private static final int MSG_DELIVERY_TIMEOUT_HARD = 3;
private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4;
private static final int MSG_CHECK_HEALTH = 5;
+ private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6;
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -284,6 +285,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
checkHealth();
return true;
}
+ case MSG_CHECK_PENDING_COLD_START_VALIDITY: {
+ checkPendingColdStartValidity();
+ return true;
+ }
}
return false;
};
@@ -450,10 +455,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// skip to look for another warm process
if (mRunningColdStart == null) {
mRunningColdStart = queue;
- } else {
+ } else if (isPendingColdStartValid()) {
// Move to considering next runnable queue
queue = nextQueue;
continue;
+ } else {
+ // Pending cold start is not valid, so clear it and move on.
+ clearInvalidPendingColdStart();
+ mRunningColdStart = queue;
}
}
@@ -486,11 +495,46 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
}
+ checkPendingColdStartValidity();
checkAndRemoveWaitingFor();
traceEnd(cookie);
}
+ private boolean isPendingColdStartValid() {
+ if (mRunningColdStart.app.getPid() > 0) {
+ // If the process has already started, check if it wasn't killed.
+ return !mRunningColdStart.app.isKilled();
+ } else {
+ // Otherwise, check if the process start is still pending.
+ return mRunningColdStart.app.isPendingStart();
+ }
+ }
+
+ private void clearInvalidPendingColdStart() {
+ logw("Clearing invalid pending cold start: " + mRunningColdStart);
+ onApplicationCleanupLocked(mRunningColdStart.app);
+ }
+
+ private void checkPendingColdStartValidity() {
+ // There are a few cases where a starting process gets killed but AMS doesn't report
+ // this event. So, once we start waiting for a pending cold start, periodically check
+ // if the pending start is still valid and if not, clear it so that the queue doesn't
+ // keep waiting for the process start forever.
+ synchronized (mService) {
+ // If there is no pending cold start, then nothing to do.
+ if (mRunningColdStart == null) {
+ return;
+ }
+ if (isPendingColdStartValid()) {
+ mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_PENDING_COLD_START_VALIDITY,
+ mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS);
+ } else {
+ clearInvalidPendingColdStart();
+ }
+ }
+ }
+
@Override
public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) {
// Process records can be recycled, so always start by looking up the
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index daa4ba4f1e16..9b3f24933f02 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -451,6 +451,10 @@ public class ForegroundServiceTypeLoggerModule {
@ForegroundServiceApiType int apiType, long timestamp) {
final long apiDurationBeforeFgsStart = r.mFgsEnterTime - timestamp;
final long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime;
+ final int[] apiTypes = new int[1];
+ apiTypes[0] = apiType;
+ final long[] timeStamps = new long[1];
+ timeStamps[0] = timestamp;
FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
r.appInfo.uid,
r.shortInstanceName,
@@ -475,8 +479,8 @@ public class ForegroundServiceTypeLoggerModule {
r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService
: ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT,
apiState,
- apiType,
- timestamp,
+ apiTypes,
+ timeStamps,
ActivityManager.PROCESS_STATE_UNKNOWN,
ActivityManager.PROCESS_CAPABILITY_NONE,
ActivityManager.PROCESS_STATE_UNKNOWN,
@@ -500,6 +504,10 @@ public class ForegroundServiceTypeLoggerModule {
apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
}
}
+ final int[] apiTypes = new int[1];
+ apiTypes[0] = apiType;
+ final long[] timeStamps = new long[1];
+ timeStamps[0] = timestamp;
FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
uid,
null,
@@ -522,8 +530,8 @@ public class ForegroundServiceTypeLoggerModule {
0,
0,
apiState,
- apiType,
- timestamp,
+ apiTypes,
+ timeStamps,
ActivityManager.PROCESS_STATE_UNKNOWN,
ActivityManager.PROCESS_CAPABILITY_NONE,
ActivityManager.PROCESS_STATE_UNKNOWN,
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 59904efb53e1..4eaee4aa7b79 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -348,21 +348,22 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
* use caller's BAL permission.
*/
public static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
- @Nullable ActivityOptions activityOptions, int callingUid) {
+ @Nullable ActivityOptions activityOptions, int callingUid,
+ @Nullable String callingPackage) {
if (activityOptions == null) {
// since the ActivityOptions were not created by the app itself, determine the default
// for the app
- return getDefaultBackgroundStartPrivileges(callingUid);
+ return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
}
return getBackgroundStartPrivilegesAllowedByCaller(activityOptions.toBundle(),
- callingUid);
+ callingUid, callingPackage);
}
private static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
- @Nullable Bundle options, int callingUid) {
+ @Nullable Bundle options, int callingUid, @Nullable String callingPackage) {
if (options == null || !options.containsKey(
ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)) {
- return getDefaultBackgroundStartPrivileges(callingUid);
+ return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
}
return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)
? BackgroundStartPrivileges.ALLOW_BAL
@@ -381,7 +382,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
android.Manifest.permission.LOG_COMPAT_CHANGE
})
public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(
- int callingUid) {
+ int callingUid, @Nullable String callingPackage) {
if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
// We temporarily allow BAL for system processes, while we verify that all valid use
// cases are opted in explicitly to grant their BAL permission.
@@ -390,7 +391,9 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
// as soon as that app is upgraded (or removed) BAL would be blocked. (b/283138430)
return BackgroundStartPrivileges.ALLOW_BAL;
}
- boolean isChangeEnabledForApp = CompatChanges.isChangeEnabled(
+ boolean isChangeEnabledForApp = callingPackage != null ? CompatChanges.isChangeEnabled(
+ DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingPackage,
+ UserHandle.getUserHandleForUid(callingUid)) : CompatChanges.isChangeEnabled(
DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingUid);
if (isChangeEnabledForApp) {
return BackgroundStartPrivileges.ALLOW_FGS;
@@ -646,7 +649,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
// temporarily allow receivers and services to open activities from background if the
// PendingIntent.send() caller was foreground at the time of sendInner() call
if (uid != callingUid && controller.mAtmInternal.isUidForeground(callingUid)) {
- return getBackgroundStartPrivilegesAllowedByCaller(options, callingUid);
+ return getBackgroundStartPrivilegesAllowedByCaller(options, callingUid, null);
}
return BackgroundStartPrivileges.NONE;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c59c2802dd67..0c7282bf3be1 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -45,7 +45,6 @@ import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
@@ -167,7 +166,6 @@ import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorManager;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.System;
import android.service.notification.ZenModeConfig;
@@ -187,10 +185,8 @@ import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
-
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
@@ -252,7 +248,6 @@ public class AudioService extends IAudioService.Stub
AudioSystemAdapter.OnVolRangeInitRequestListener {
private static final String TAG = "AS.AudioService";
- private static final boolean CONFIG_DEFAULT_VAL = false;
private final AudioSystemAdapter mAudioSystem;
private final SystemServerAdapter mSystemServer;
@@ -309,7 +304,7 @@ public class AudioService extends IAudioService.Stub
* indicates whether STREAM_NOTIFICATION is aliased to STREAM_RING
* not final due to test method, see {@link #setNotifAliasRingForTest(boolean)}.
*/
- private boolean mNotifAliasRing;
+ private boolean mNotifAliasRing = false;
/**
* Test method to temporarily override whether STREAM_NOTIFICATION is aliased to STREAM_RING,
@@ -1057,13 +1052,6 @@ public class AudioService extends IAudioService.Stub
mUseVolumeGroupAliases = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups);
- mNotifAliasRing = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
-
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- ActivityThread.currentApplication().getMainExecutor(),
- this::onDeviceConfigChange);
-
// Initialize volume
// Priority 1 - Android Property
// Priority 2 - Audio Policy Service
@@ -1157,6 +1145,11 @@ public class AudioService extends IAudioService.Stub
MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM];
}
+ int minAssistantVolume = SystemProperties.getInt("ro.config.assistant_vol_min", -1);
+ if (minAssistantVolume != -1) {
+ MIN_STREAM_VOLUME[AudioSystem.STREAM_ASSISTANT] = minAssistantVolume;
+ }
+
// Read following properties to configure max volume (number of steps) and default volume
// for STREAM_NOTIFICATION and STREAM_RING:
// config_audio_notif_vol_default
@@ -1277,22 +1270,6 @@ public class AudioService extends IAudioService.Stub
}
/**
- * Separating notification volume from ring is NOT of aliasing the corresponding streams
- * @param properties
- */
- private void onDeviceConfigChange(DeviceConfig.Properties properties) {
- Set<String> changeSet = properties.getKeyset();
- if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
- boolean newNotifAliasRing = !properties.getBoolean(
- SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
- if (mNotifAliasRing != newNotifAliasRing) {
- mNotifAliasRing = newNotifAliasRing;
- updateStreamVolumeAlias(true, TAG);
- }
- }
- }
-
- /**
* Called by handling of MSG_INIT_STREAMS_VOLUMES
*/
private void onInitStreamsAndVolumes() {
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index bf5e8ee27157..1989bc77583a 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -21,8 +21,6 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
-import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
-import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
@@ -44,7 +42,6 @@ import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -68,7 +65,6 @@ import com.android.server.biometrics.log.OperationContextExt;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
@@ -134,7 +130,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
// The current state, which can be either idle, called, or started
private @SessionState int mState = STATE_AUTH_IDLE;
- private @BiometricMultiSensorMode int mMultiSensorMode;
private int[] mSensors;
// TODO(b/197265902): merge into state
private boolean mCancelled;
@@ -255,7 +250,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
// SystemUI invokes that path.
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mSensors = new int[0];
- mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT;
mStatusBarService.showAuthenticationDialog(
mPromptInfo,
@@ -266,8 +260,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mUserId,
mOperationId,
mOpPackageName,
- mRequestId,
- mMultiSensorMode);
+ mRequestId);
} else if (!mPreAuthInfo.eligibleSensors.isEmpty()) {
// Some combination of biometric or biometric|credential is requested
setSensorsToStateWaitingForCookie(false /* isTryAgain */);
@@ -310,8 +303,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
for (int i = 0; i < mPreAuthInfo.eligibleSensors.size(); i++) {
mSensors[i] = mPreAuthInfo.eligibleSensors.get(i).id;
}
- mMultiSensorMode = getMultiSensorModeForNewSession(
- mPreAuthInfo.eligibleSensors);
mStatusBarService.showAuthenticationDialog(mPromptInfo,
mSysuiReceiver,
@@ -321,8 +312,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mUserId,
mOperationId,
mOpPackageName,
- mRequestId,
- mMultiSensorMode);
+ mRequestId);
mState = STATE_AUTH_STARTED;
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
@@ -438,7 +428,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
mPromptInfo.setAuthenticators(authenticators);
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
- mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT;
mSensors = new int[0];
mStatusBarService.showAuthenticationDialog(
@@ -450,8 +439,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mUserId,
mOperationId,
mOpPackageName,
- mRequestId,
- mMultiSensorMode);
+ mRequestId);
} else {
mClientReceiver.onError(modality, error, vendorCode);
return true;
@@ -545,13 +533,30 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
}
- void onDialogAnimatedIn() {
+ void onDialogAnimatedIn(boolean startFingerprintNow) {
if (mState != STATE_AUTH_STARTED) {
Slog.e(TAG, "onDialogAnimatedIn, unexpected state: " + mState);
return;
}
mState = STATE_AUTH_STARTED_UI_SHOWING;
+ if (startFingerprintNow) {
+ startAllPreparedFingerprintSensors();
+ } else {
+ Slog.d(TAG, "delaying fingerprint sensor start");
+ }
+ }
+
+ // call once anytime after onDialogAnimatedIn() to indicate it's appropriate to start the
+ // fingerprint sensor (i.e. face auth has failed or is not available)
+ void onStartFingerprint() {
+ if (mState != STATE_AUTH_STARTED
+ && mState != STATE_AUTH_STARTED_UI_SHOWING
+ && mState != STATE_AUTH_PAUSED
+ && mState != STATE_ERROR_PENDING_SYSUI) {
+ Slog.w(TAG, "onStartFingerprint, started from unexpected state: " + mState);
+ }
+
startAllPreparedFingerprintSensors();
}
@@ -919,25 +924,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
}
- @BiometricMultiSensorMode
- private static int getMultiSensorModeForNewSession(Collection<BiometricSensor> sensors) {
- boolean hasFace = false;
- boolean hasFingerprint = false;
-
- for (BiometricSensor sensor: sensors) {
- if (sensor.modality == TYPE_FACE) {
- hasFace = true;
- } else if (sensor.modality == TYPE_FINGERPRINT) {
- hasFingerprint = true;
- }
- }
-
- if (hasFace && hasFingerprint) {
- return BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
- }
- return BIOMETRIC_MULTI_SENSOR_DEFAULT;
- }
-
@Override
public String toString() {
return "State: " + mState
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 448843477ecd..0942d8527565 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -480,8 +480,13 @@ public class BiometricService extends SystemService {
}
@Override
- public void onDialogAnimatedIn() {
- mHandler.post(() -> handleOnDialogAnimatedIn(requestId));
+ public void onDialogAnimatedIn(boolean startFingerprintNow) {
+ mHandler.post(() -> handleOnDialogAnimatedIn(requestId, startFingerprintNow));
+ }
+
+ @Override
+ public void onStartFingerprintNow() {
+ mHandler.post(() -> handleOnStartFingerprintNow(requestId));
}
};
}
@@ -1237,7 +1242,7 @@ public class BiometricService extends SystemService {
}
}
- private void handleOnDialogAnimatedIn(long requestId) {
+ private void handleOnDialogAnimatedIn(long requestId, boolean startFingerprintNow) {
Slog.d(TAG, "handleOnDialogAnimatedIn");
final AuthSession session = getAuthSessionIfCurrent(requestId);
@@ -1246,7 +1251,19 @@ public class BiometricService extends SystemService {
return;
}
- session.onDialogAnimatedIn();
+ session.onDialogAnimatedIn(startFingerprintNow);
+ }
+
+ private void handleOnStartFingerprintNow(long requestId) {
+ Slog.d(TAG, "handleOnStartFingerprintNow");
+
+ final AuthSession session = getAuthSessionIfCurrent(requestId);
+ if (session == null) {
+ Slog.w(TAG, "handleOnStartFingerprintNow: AuthSession is not current");
+ return;
+ }
+
+ session.onStartFingerprint();
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index a486d16189fa..694dfd28d0cc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -70,9 +70,11 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
// Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible
// for that the queue will wait indefinitely until the field is cleared.
- if (clientMonitor instanceof StopUserClient<?> && !success) {
- Slog.w(getTag(),
- "StopUserClient failed(), is the HAL stuck? Clearing mStopUserClient");
+ if (clientMonitor instanceof StopUserClient<?>) {
+ if (!success) {
+ Slog.w(getTag(), "StopUserClient failed(), is the HAL stuck? "
+ + "Clearing mStopUserClient");
+ }
mStopUserClient = null;
}
if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index ffbf4e12f2ae..2ad41c2a7a02 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -89,7 +89,7 @@ public class Sensor {
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@NonNull private final Supplier<AidlSession> mLazySession;
- @Nullable private AidlSession mCurrentSession;
+ @Nullable AidlSession mCurrentSession;
@VisibleForTesting
public static class HalSessionCallback extends ISessionCallback.Stub {
@@ -486,7 +486,7 @@ public class Sensor {
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricContext biometricContext, AidlSession session) {
mTag = tag;
mProvider = provider;
mContext = context;
@@ -549,6 +549,14 @@ public class Sensor {
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
}
+ Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext) {
+ this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+ biometricContext, null);
+ }
+
@NonNull Supplier<AidlSession> getLazySession() {
return mLazySession;
}
@@ -557,7 +565,7 @@ public class Sensor {
return mSensorProperties;
}
- @Nullable AidlSession getSessionForUser(int userId) {
+ @VisibleForTesting @Nullable AidlSession getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
@@ -641,6 +649,8 @@ public class Sensor {
BiometricsProtoEnums.MODALITY_FACE,
BiometricsProtoEnums.ISSUE_HAL_DEATH,
-1 /* sensorId */);
+ } else if (client != null) {
+ client.cancel();
}
mScheduler.recordCrashState();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index c0dde721b962..56b85ceb8e6b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -90,7 +90,7 @@ public class Sensor {
@NonNull private final LockoutCache mLockoutCache;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
- @Nullable private AidlSession mCurrentSession;
+ @Nullable AidlSession mCurrentSession;
@NonNull private final Supplier<AidlSession> mLazySession;
@VisibleForTesting
@@ -439,7 +439,7 @@ public class Sensor {
@NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricContext biometricContext, AidlSession session) {
mTag = tag;
mProvider = provider;
mContext = context;
@@ -501,6 +501,16 @@ public class Sensor {
});
mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+ mCurrentSession = session;
+ }
+
+ Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
+ this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+ gestureAvailabilityDispatcher, biometricContext, null);
}
@NonNull Supplier<AidlSession> getLazySession() {
@@ -599,6 +609,8 @@ public class Sensor {
BiometricsProtoEnums.MODALITY_FINGERPRINT,
BiometricsProtoEnums.ISSUE_HAL_DEATH,
-1 /* sensorId */);
+ } else if (client != null) {
+ client.cancel();
}
mScheduler.recordCrashState();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index ca1abd683d4f..7d0d5a73f093 100755..100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -1126,6 +1126,16 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice {
removeActionExcept(clazz, null);
}
+ // Remove all running actions.
+ @ServiceThreadOnly
+ void removeAllActions() {
+ assertRunOnServiceThread();
+ for (HdmiCecFeatureAction action : mActions) {
+ action.finish(false);
+ }
+ mActions.clear();
+ }
+
// Remove all actions matched with the given Class type besides |exception|.
@ServiceThreadOnly
<T extends HdmiCecFeatureAction> void removeActionExcept(
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index a026c4b59ec5..184bdd778300 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -231,6 +231,14 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
super.disableDevice(initiatedByCec, callback);
assertRunOnServiceThread();
mService.unregisterTvInputCallback(mTvInputCallback);
+ // Removing actions and invoking the callback is similar to
+ // HdmiCecLocalDevicePlayback#disableDevice and HdmiCecLocalDeviceTv#disableDevice,
+ // with the difference that in those classes only specific actions are removed and
+ // here we remove all actions. We don't expect any issues with removing all actions
+ // at this time, but we have to pay attention in the future.
+ removeAllActions();
+ // Call the callback instantly or else it will be called 5 seconds later.
+ checkIfPendingActionsCleared();
}
@Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index dd974d1c5ea6..d2266e3a367b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -889,22 +889,31 @@ public class LockSettingsService extends ILockSettings.Stub {
}
- private void migrateOldDataAfterSystemReady() {
- // Migrate the FRP credential to the persistent data block
+ @VisibleForTesting
+ void migrateOldDataAfterSystemReady() {
+ // Write the FRP persistent data block if needed.
+ //
+ // The original purpose of this code was to write the FRP block for the first time, when
+ // upgrading from Android 8.1 or earlier which didn't use the FRP block. This code has
+ // since been repurposed to also fix the "bad" (non-forwards-compatible) FRP block written
+ // by Android 14 Beta 2. For this reason, the database key used here has been renamed from
+ // "migrated_frp" to "migrated_frp2" to cause migrateFrpCredential() to run again on devices
+ // where it had run before.
if (LockPatternUtils.frpCredentialEnabled(mContext)
- && !getBoolean("migrated_frp", false, 0)) {
+ && !getBoolean("migrated_frp2", false, 0)) {
migrateFrpCredential();
- setBoolean("migrated_frp", true, 0);
+ setBoolean("migrated_frp2", true, 0);
}
}
/**
- * Migrate the credential for the FRP credential owner user if the following are satisfied:
- * - the user has a secure credential
- * - the FRP credential is not set up
+ * Write the FRP persistent data block if the following are satisfied:
+ * - the user who owns the FRP credential has a nonempty credential
+ * - the FRP persistent data block doesn't exist or uses the "bad" format from Android 14 Beta 2
*/
private void migrateFrpCredential() {
- if (mStorage.readPersistentDataBlock() != PersistentData.NONE) {
+ PersistentData data = mStorage.readPersistentDataBlock();
+ if (data != PersistentData.NONE && !data.isBadFormatFromAndroid14Beta()) {
return;
}
for (UserInfo userInfo : mUserManager.getUsers()) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 731ecadc1372..2fa637e030f1 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -606,6 +606,11 @@ class LockSettingsStorage {
this.payload = payload;
}
+ public boolean isBadFormatFromAndroid14Beta() {
+ return (this.type == TYPE_SP_GATEKEEPER || this.type == TYPE_SP_WEAVER)
+ && SyntheticPasswordManager.PasswordData.isBadFormatFromAndroid14Beta(this.payload);
+ }
+
public static PersistentData fromBytes(byte[] frpData) {
if (frpData == null || frpData.length == 0) {
return NONE;
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 8b8c5f600255..e8fd6f88359c 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -152,9 +152,6 @@ class SyntheticPasswordManager {
// The security strength of the synthetic password, in bytes
private static final int SYNTHETIC_PASSWORD_SECURITY_STRENGTH = 256 / 8;
- public static final short PASSWORD_DATA_V1 = 1;
- public static final short PASSWORD_DATA_V2 = 2;
-
private static final int PASSWORD_SCRYPT_LOG_N = 11;
private static final int PASSWORD_SCRYPT_LOG_R = 3;
private static final int PASSWORD_SCRYPT_LOG_P = 1;
@@ -373,27 +370,33 @@ class SyntheticPasswordManager {
return result;
}
+ /**
+ * Returns true if the given serialized PasswordData begins with the value 2 as a short.
+ * This detects the "bad" (non-forwards-compatible) PasswordData format that was temporarily
+ * used during development of Android 14. For more details, see fromBytes() below.
+ */
+ public static boolean isBadFormatFromAndroid14Beta(byte[] data) {
+ return data != null && data.length >= 2 && data[0] == 0 && data[1] == 2;
+ }
+
public static PasswordData fromBytes(byte[] data) {
PasswordData result = new PasswordData();
ByteBuffer buffer = ByteBuffer.allocate(data.length);
buffer.put(data, 0, data.length);
buffer.flip();
- /*
- * Originally this file did not contain a version number. However, its first field was
- * 'credentialType' as an 'int'. Since 'credentialType' could only be in the range
- * [-1, 4] and this file uses big endian byte order, the first two bytes were redundant,
- * and when interpreted as a 'short' could only contain -1 or 0. Therefore, we've now
- * reclaimed these two bytes for a 'short' version number and shrunk 'credentialType'
- * to a 'short'.
- */
- short version = buffer.getShort();
- if (version == ((short) 0) || version == (short) -1) {
- version = PASSWORD_DATA_V1;
- } else if (version != PASSWORD_DATA_V2) {
- throw new IllegalArgumentException("Unknown PasswordData version: " + version);
- }
- result.credentialType = buffer.getShort();
+ /*
+ * The serialized PasswordData is supposed to begin with credentialType as an int.
+ * However, all credentialType values fit in a short and the byte order is big endian,
+ * so the first two bytes don't convey any non-redundant information. For this reason,
+ * temporarily during development of Android 14, the first two bytes were "stolen" from
+ * credentialType to use for a data format version number.
+ *
+ * However, this change was reverted as it was a non-forwards-compatible change. (See
+ * toBytes() for why this data format needs to be forwards-compatible.) Therefore,
+ * recover from this misstep by ignoring the first two bytes.
+ */
+ result.credentialType = (short) buffer.getInt();
result.scryptLogN = buffer.get();
result.scryptLogR = buffer.get();
result.scryptLogP = buffer.get();
@@ -407,7 +410,7 @@ class SyntheticPasswordManager {
} else {
result.passwordHandle = null;
}
- if (version == PASSWORD_DATA_V2) {
+ if (buffer.remaining() >= Integer.BYTES) {
result.pinLength = buffer.getInt();
} else {
result.pinLength = PIN_LENGTH_UNAVAILABLE;
@@ -415,16 +418,25 @@ class SyntheticPasswordManager {
return result;
}
+ /**
+ * Serializes this PasswordData into a byte array.
+ * <p>
+ * Careful: all changes to the format of the serialized PasswordData must be forwards
+ * compatible. I.e., older versions of Android must still accept the latest PasswordData.
+ * This is because a serialized PasswordData is stored in the Factory Reset Protection (FRP)
+ * persistent data block. It's possible that a device has FRP set up on a newer version of
+ * Android, is factory reset, and then is set up with an older version of Android.
+ */
public byte[] toBytes() {
- ByteBuffer buffer = ByteBuffer.allocate(2 * Short.BYTES + 3 * Byte.BYTES
+ ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 3 * Byte.BYTES
+ Integer.BYTES + salt.length + Integer.BYTES +
(passwordHandle != null ? passwordHandle.length : 0) + Integer.BYTES);
+ // credentialType must fit in a short. For an explanation, see fromBytes().
if (credentialType < Short.MIN_VALUE || credentialType > Short.MAX_VALUE) {
throw new IllegalArgumentException("Unknown credential type: " + credentialType);
}
- buffer.putShort(PASSWORD_DATA_V2);
- buffer.putShort((short) credentialType);
+ buffer.putInt(credentialType);
buffer.put(scryptLogN);
buffer.put(scryptLogR);
buffer.put(scryptLogP);
@@ -1564,8 +1576,10 @@ class SyntheticPasswordManager {
}
return result;
} else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+ Slog.e(TAG, "Gatekeeper verification of synthetic password failed with RESPONSE_RETRY");
return VerifyCredentialResponse.fromTimeout(response.getTimeout());
} else {
+ Slog.e(TAG, "Gatekeeper verification of synthetic password failed with RESPONSE_ERROR");
return VerifyCredentialResponse.ERROR;
}
}
diff --git a/services/core/java/com/android/server/os/OWNERS b/services/core/java/com/android/server/os/OWNERS
index 19573323e5ad..70be161ba9a8 100644
--- a/services/core/java/com/android/server/os/OWNERS
+++ b/services/core/java/com/android/server/os/OWNERS
@@ -1,2 +1,5 @@
# Bugreporting
per-file Bugreport* = file:/platform/frameworks/native:/cmds/dumpstate/OWNERS
+
+# NativeTombstone
+per-file NativeTombstone* = gaillard@google.com
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 06db5be349d4..4f00dc37caa6 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2140,7 +2140,7 @@ final class InstallPackageHelper {
private void updateSettingsInternalLI(AndroidPackage pkg,
int[] allUsers, InstallRequest installRequest) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettingsInternal");
final String pkgName = pkg.getPackageName();
final int[] installedForUsers = installRequest.getOriginUsers();
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 395c5d5913b8..d855ac034db7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -216,6 +216,9 @@ class PackageManagerShellCommand extends ShellCommand {
final PrintWriter pw = getOutPrintWriter();
try {
switch (cmd) {
+ case "help":
+ onHelp();
+ return 0;
case "path":
return runPath();
case "dump":
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 7a5664f8135e..5288e85aa490 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -37,6 +37,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.PackageTagsList;
import android.os.Process;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.service.voice.VoiceInteractionManagerInternal;
import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
@@ -68,6 +69,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
private static final String ACTIVITY_RECOGNITION_TAGS =
"android:activity_recognition_allow_listed_tags";
private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";";
+ private static final boolean SYSPROP_HOTWORD_DETECTION_SERVICE_REQUIRED =
+ SystemProperties.getBoolean("ro.hotword.detection_service_required", false);
@NonNull
private final Object mLock = new Object();
@@ -199,10 +202,16 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
}
}
- private static boolean isHotwordDetectionServiceRequired(PackageManager pm) {
+ /**
+ * @hide
+ */
+ public static boolean isHotwordDetectionServiceRequired(PackageManager pm) {
// The HotwordDetectionService APIs aren't ready yet for Auto or TV.
- return !(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
- || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
+ if (pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ return false;
+ }
+ return SYSPROP_HOTWORD_DETECTION_SERVICE_REQUIRED;
}
@Override
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 701e9212aad8..b0fe628c513d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2204,6 +2204,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_shortPressOnSleepBehavior);
mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_allowStartActivityForLongPressOnPowerInSetup);
+ mShortPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior);
+ mLongPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior);
+ mDoublePressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_doublePressOnStemPrimaryBehavior);
+ mTriplePressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_triplePressOnStemPrimaryBehavior);
mHapticTextHandleEnabled = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enableHapticTextHandle);
@@ -2610,14 +2618,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
}
- mShortPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior);
- mLongPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior);
- mDoublePressOnStemPrimaryBehavior = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_doublePressOnStemPrimaryBehavior);
- mTriplePressOnStemPrimaryBehavior = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_triplePressOnStemPrimaryBehavior);
}
public void updateSettings() {
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 4a57592aa1ae..fae6262207d5 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -251,10 +251,11 @@ public class BatteryStatsImpl extends BatteryStats {
private static final LongCounter[] ZERO_LONG_COUNTER_ARRAY =
new LongCounter[]{ZERO_LONG_COUNTER};
- private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
@VisibleForTesting
+ protected KernelWakelockReader mKernelWakelockReader;
+ @VisibleForTesting
protected KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader;
@VisibleForTesting
protected KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
@@ -1763,6 +1764,7 @@ public class BatteryStatsImpl extends BatteryStats {
mCpuUidFreqTimeReader = new KernelCpuUidFreqTimeReader(true, clock);
mCpuUidActiveTimeReader = new KernelCpuUidActiveTimeReader(true, clock);
mCpuUidClusterTimeReader = new KernelCpuUidClusterTimeReader(true, clock);
+ mKernelWakelockReader = new KernelWakelockReader();
}
/**
@@ -2675,19 +2677,18 @@ public class BatteryStatsImpl extends BatteryStats {
* The reported count from /proc/wakelocks when unplug() was last
* called.
*/
- int mUnpluggedReportedCount;
+ int mBaseReportedCount;
/**
* The most recent reported total_time from /proc/wakelocks.
*/
long mCurrentReportedTotalTimeUs;
-
/**
* The reported total_time from /proc/wakelocks when unplug() was last
* called.
*/
- long mUnpluggedReportedTotalTimeUs;
+ long mBaseReportedTotalTimeUs;
/**
* Whether we are currently in a discharge cycle.
@@ -2708,9 +2709,9 @@ public class BatteryStatsImpl extends BatteryStats {
public SamplingTimer(Clock clock, TimeBase timeBase, Parcel in) {
super(clock, 0, timeBase, in);
mCurrentReportedCount = in.readInt();
- mUnpluggedReportedCount = in.readInt();
+ mBaseReportedCount = in.readInt();
mCurrentReportedTotalTimeUs = in.readLong();
- mUnpluggedReportedTotalTimeUs = in.readLong();
+ mBaseReportedTotalTimeUs = in.readLong();
mTrackingReportedValues = in.readInt() == 1;
mTimeBaseRunning = timeBase.isRunning();
}
@@ -2736,8 +2737,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void endSample(long elapsedRealtimeUs) {
mTotalTimeUs = computeRunTimeLocked(0 /* unused by us */, elapsedRealtimeUs);
mCount = computeCurrentCountLocked();
- mUnpluggedReportedTotalTimeUs = mCurrentReportedTotalTimeUs = 0;
- mUnpluggedReportedCount = mCurrentReportedCount = 0;
+ mBaseReportedTotalTimeUs = mCurrentReportedTotalTimeUs = 0;
+ mBaseReportedCount = mCurrentReportedCount = 0;
mTrackingReportedValues = false;
}
@@ -2762,10 +2763,21 @@ public class BatteryStatsImpl extends BatteryStats {
* @param count total number of times the event being sampled occurred.
*/
public void update(long totalTimeUs, int count, long elapsedRealtimeUs) {
+ update(totalTimeUs, 0, count, elapsedRealtimeUs);
+ }
+
+ /**
+ * Updates the current recorded values. See {@link #update(long, int, long)}
+ *
+ * @param activeTimeUs Time that the currently active wake lock has been held.
+ */
+ public void update(long totalTimeUs, long activeTimeUs, int count,
+ long elapsedRealtimeUs) {
if (mTimeBaseRunning && !mTrackingReportedValues) {
- // Updating the reported value for the first time.
- mUnpluggedReportedTotalTimeUs = totalTimeUs;
- mUnpluggedReportedCount = count;
+ // Updating the reported value for the first time. If the wake lock is currently
+ // active, mark the time it was acquired as the base timestamp.
+ mBaseReportedTotalTimeUs = totalTimeUs - activeTimeUs;
+ mBaseReportedCount = activeTimeUs == 0 ? count : count - 1;
}
mTrackingReportedValues = true;
@@ -2800,8 +2812,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void onTimeStarted(long elapsedRealtimeUs, long baseUptimeUs, long baseRealtimeUs) {
super.onTimeStarted(elapsedRealtimeUs, baseUptimeUs, baseRealtimeUs);
if (mTrackingReportedValues) {
- mUnpluggedReportedTotalTimeUs = mCurrentReportedTotalTimeUs;
- mUnpluggedReportedCount = mCurrentReportedCount;
+ mBaseReportedTotalTimeUs = mCurrentReportedTotalTimeUs;
+ mBaseReportedCount = mCurrentReportedCount;
}
mTimeBaseRunning = true;
}
@@ -2816,30 +2828,30 @@ public class BatteryStatsImpl extends BatteryStats {
public void logState(Printer pw, String prefix) {
super.logState(pw, prefix);
pw.println(prefix + "mCurrentReportedCount=" + mCurrentReportedCount
- + " mUnpluggedReportedCount=" + mUnpluggedReportedCount
+ + " mBaseReportedCount=" + mBaseReportedCount
+ " mCurrentReportedTotalTime=" + mCurrentReportedTotalTimeUs
- + " mUnpluggedReportedTotalTime=" + mUnpluggedReportedTotalTimeUs);
+ + " mBaseReportedTotalTimeUs=" + mBaseReportedTotalTimeUs);
}
@Override
protected long computeRunTimeLocked(long curBatteryRealtime, long elapsedRealtimeUs) {
return mTotalTimeUs + (mTimeBaseRunning && mTrackingReportedValues
- ? mCurrentReportedTotalTimeUs - mUnpluggedReportedTotalTimeUs : 0);
+ ? mCurrentReportedTotalTimeUs - mBaseReportedTotalTimeUs : 0);
}
@Override
protected int computeCurrentCountLocked() {
return mCount + (mTimeBaseRunning && mTrackingReportedValues
- ? mCurrentReportedCount - mUnpluggedReportedCount : 0);
+ ? mCurrentReportedCount - mBaseReportedCount : 0);
}
@Override
public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
super.writeToParcel(out, elapsedRealtimeUs);
out.writeInt(mCurrentReportedCount);
- out.writeInt(mUnpluggedReportedCount);
+ out.writeInt(mBaseReportedCount);
out.writeLong(mCurrentReportedTotalTimeUs);
- out.writeLong(mUnpluggedReportedTotalTimeUs);
+ out.writeLong(mBaseReportedTotalTimeUs);
out.writeInt(mTrackingReportedValues ? 1 : 0);
}
@@ -2847,8 +2859,8 @@ public class BatteryStatsImpl extends BatteryStats {
public boolean reset(boolean detachIfReset, long elapsedRealtimeUs) {
super.reset(detachIfReset, elapsedRealtimeUs);
mTrackingReportedValues = false;
- mUnpluggedReportedTotalTimeUs = 0;
- mUnpluggedReportedCount = 0;
+ mBaseReportedTotalTimeUs = 0;
+ mBaseReportedCount = 0;
return true;
}
}
@@ -13398,6 +13410,10 @@ public class BatteryStatsImpl extends BatteryStats {
* Read and distribute kernel wake lock use across apps.
*/
public void updateKernelWakelocksLocked(long elapsedRealtimeUs) {
+ if (mKernelWakelockReader == null) {
+ return;
+ }
+
final KernelWakelockStats wakelockStats = mKernelWakelockReader.readKernelWakelockStats(
mTmpWakelockStats);
if (wakelockStats == null) {
@@ -13416,8 +13432,8 @@ public class BatteryStatsImpl extends BatteryStats {
mKernelWakelockStats.put(name, kwlt);
}
- kwlt.update(kws.mTotalTime, kws.mCount, elapsedRealtimeUs);
- kwlt.setUpdateVersion(kws.mVersion);
+ kwlt.update(kws.totalTimeUs, kws.activeTimeUs, kws.count, elapsedRealtimeUs);
+ kwlt.setUpdateVersion(kws.version);
}
int numWakelocksSetStale = 0;
@@ -13471,7 +13487,7 @@ public class BatteryStatsImpl extends BatteryStats {
Slog.d(TAG, String.format("Added entry %d and updated timer to: "
+ "mUnpluggedReportedTotalTimeUs %d size %d", bandwidthEntries.keyAt(i),
mKernelMemoryStats.get(
- bandwidthEntries.keyAt(i)).mUnpluggedReportedTotalTimeUs,
+ bandwidthEntries.keyAt(i)).mBaseReportedTotalTimeUs,
mKernelMemoryStats.size()));
}
}
diff --git a/services/core/java/com/android/server/power/stats/KernelWakelockReader.java b/services/core/java/com/android/server/power/stats/KernelWakelockReader.java
index 0377d8a0f23d..3fffcf713c31 100644
--- a/services/core/java/com/android/server/power/stats/KernelWakelockReader.java
+++ b/services/core/java/com/android/server/power/stats/KernelWakelockReader.java
@@ -42,30 +42,31 @@ public class KernelWakelockReader {
private static final String sWakeupSourceFile = "/d/wakeup_sources";
private static final String sSysClassWakeupDir = "/sys/class/wakeup";
- private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
- Process.PROC_TAB_TERM|Process.PROC_OUT_STRING| // 0: name
- Process.PROC_QUOTES,
- Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 1: count
- Process.PROC_TAB_TERM,
- Process.PROC_TAB_TERM,
- Process.PROC_TAB_TERM,
- Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 5: totalTime
+ private static final int[] PROC_WAKELOCKS_FORMAT = new int[]{
+ Process.PROC_TAB_TERM | Process.PROC_OUT_STRING // 0: name
+ | Process.PROC_QUOTES,
+ Process.PROC_TAB_TERM | Process.PROC_OUT_LONG, // 1: count
+ Process.PROC_TAB_TERM,
+ Process.PROC_TAB_TERM | Process.PROC_OUT_LONG, // 3: activeSince
+ Process.PROC_TAB_TERM,
+ Process.PROC_TAB_TERM | Process.PROC_OUT_LONG, // 5: totalTime
};
- private static final int[] WAKEUP_SOURCES_FORMAT = new int[] {
- Process.PROC_TAB_TERM|Process.PROC_OUT_STRING, // 0: name
- Process.PROC_TAB_TERM|Process.PROC_COMBINE|
- Process.PROC_OUT_LONG, // 1: count
- Process.PROC_TAB_TERM|Process.PROC_COMBINE,
- Process.PROC_TAB_TERM|Process.PROC_COMBINE,
- Process.PROC_TAB_TERM|Process.PROC_COMBINE,
- Process.PROC_TAB_TERM|Process.PROC_COMBINE,
- Process.PROC_TAB_TERM|Process.PROC_COMBINE
- |Process.PROC_OUT_LONG, // 6: totalTime
+ private static final int[] WAKEUP_SOURCES_FORMAT = new int[]{
+ Process.PROC_TAB_TERM | Process.PROC_OUT_STRING, // 0: name
+ Process.PROC_TAB_TERM | Process.PROC_COMBINE
+ | Process.PROC_OUT_LONG, // 1: count
+ Process.PROC_TAB_TERM | Process.PROC_COMBINE,
+ Process.PROC_TAB_TERM | Process.PROC_COMBINE,
+ Process.PROC_TAB_TERM | Process.PROC_COMBINE,
+ Process.PROC_TAB_TERM | Process.PROC_COMBINE
+ | Process.PROC_OUT_LONG, // 5: activeSince
+ Process.PROC_TAB_TERM | Process.PROC_COMBINE
+ | Process.PROC_OUT_LONG, // 6: totalTime
};
private final String[] mProcWakelocksName = new String[3];
- private final long[] mProcWakelocksData = new long[3];
+ private final long[] mProcWakelocksData = new long[4];
private ISuspendControlServiceInternal mSuspendControlService = null;
private byte[] mKernelWakelockBuffer = new byte[32 * 1024];
@@ -74,7 +75,7 @@ public class KernelWakelockReader {
* @param staleStats Existing object to update.
* @return the updated data.
*/
- public final KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) {
+ public KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) {
boolean useSystemSuspend = (new File(sSysClassWakeupDir)).exists();
if (useSystemSuspend) {
@@ -180,14 +181,16 @@ public class KernelWakelockReader {
*/
private KernelWakelockStats getWakelockStatsFromSystemSuspend(
final KernelWakelockStats staleStats) {
- WakeLockInfo[] wlStats = null;
- try {
- mSuspendControlService = waitForSuspendControlService();
- } catch (ServiceNotFoundException e) {
- Slog.wtf(TAG, "Required service suspend_control not available", e);
- return null;
+ if (mSuspendControlService == null) {
+ try {
+ mSuspendControlService = waitForSuspendControlService();
+ } catch (ServiceNotFoundException e) {
+ Slog.wtf(TAG, "Required service suspend_control not available", e);
+ return null;
+ }
}
+ WakeLockInfo[] wlStats;
try {
wlStats = mSuspendControlService.getWakeLockStats();
updateWakelockStats(wlStats, staleStats);
@@ -210,13 +213,16 @@ public class KernelWakelockReader {
for (WakeLockInfo info : wlStats) {
if (!staleStats.containsKey(info.name)) {
staleStats.put(info.name, new KernelWakelockStats.Entry((int) info.activeCount,
- info.totalTime * 1000 /* ms to us */, sKernelWakelockUpdateVersion));
+ info.totalTime * 1000 /* ms to us */,
+ info.isActive ? info.activeTime * 1000 : 0,
+ sKernelWakelockUpdateVersion));
} else {
KernelWakelockStats.Entry kwlStats = staleStats.get(info.name);
- kwlStats.mCount = (int) info.activeCount;
+ kwlStats.count = (int) info.activeCount;
// Convert milliseconds to microseconds
- kwlStats.mTotalTime = info.totalTime * 1000;
- kwlStats.mVersion = sKernelWakelockUpdateVersion;
+ kwlStats.totalTimeUs = info.totalTime * 1000;
+ kwlStats.activeTimeUs = info.isActive ? info.activeTime * 1000 : 0;
+ kwlStats.version = sKernelWakelockUpdateVersion;
}
}
@@ -232,6 +238,7 @@ public class KernelWakelockReader {
String name;
int count;
long totalTime;
+ long activeTime;
int startIndex;
int endIndex;
@@ -268,26 +275,30 @@ public class KernelWakelockReader {
count = (int) wlData[1];
if (wakeup_sources) {
- // convert milliseconds to microseconds
- totalTime = wlData[2] * 1000;
+ // convert milliseconds to microseconds
+ activeTime = wlData[2] * 1000;
+ totalTime = wlData[3] * 1000;
} else {
- // convert nanoseconds to microseconds with rounding.
- totalTime = (wlData[2] + 500) / 1000;
+ // convert nanoseconds to microseconds with rounding.
+ activeTime = (wlData[2] + 500) / 1000;
+ totalTime = (wlData[3] + 500) / 1000;
}
if (parsed && name.length() > 0) {
if (!staleStats.containsKey(name)) {
staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime,
- sKernelWakelockUpdateVersion));
+ activeTime, sKernelWakelockUpdateVersion));
} else {
KernelWakelockStats.Entry kwlStats = staleStats.get(name);
- if (kwlStats.mVersion == sKernelWakelockUpdateVersion) {
- kwlStats.mCount += count;
- kwlStats.mTotalTime += totalTime;
+ if (kwlStats.version == sKernelWakelockUpdateVersion) {
+ kwlStats.count += count;
+ kwlStats.totalTimeUs += totalTime;
+ kwlStats.activeTimeUs = activeTime;
} else {
- kwlStats.mCount = count;
- kwlStats.mTotalTime = totalTime;
- kwlStats.mVersion = sKernelWakelockUpdateVersion;
+ kwlStats.count = count;
+ kwlStats.totalTimeUs = totalTime;
+ kwlStats.activeTimeUs = activeTime;
+ kwlStats.version = sKernelWakelockUpdateVersion;
}
}
} else if (!parsed) {
@@ -326,7 +337,7 @@ public class KernelWakelockReader {
public KernelWakelockStats removeOldStats(final KernelWakelockStats staleStats) {
Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator();
while (itr.hasNext()) {
- if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
+ if (itr.next().version != sKernelWakelockUpdateVersion) {
itr.remove();
}
}
diff --git a/services/core/java/com/android/server/power/stats/KernelWakelockStats.java b/services/core/java/com/android/server/power/stats/KernelWakelockStats.java
index 502b7a008cb8..3ed3803fb5bc 100644
--- a/services/core/java/com/android/server/power/stats/KernelWakelockStats.java
+++ b/services/core/java/com/android/server/power/stats/KernelWakelockStats.java
@@ -22,14 +22,16 @@ import java.util.HashMap;
*/
public class KernelWakelockStats extends HashMap<String, KernelWakelockStats.Entry> {
public static class Entry {
- public int mCount;
- public long mTotalTime;
- public int mVersion;
+ public int count;
+ public long totalTimeUs;
+ public long activeTimeUs;
+ public int version;
- Entry(int count, long totalTime, int version) {
- mCount = count;
- mTotalTime = totalTime;
- mVersion = version;
+ Entry(int count, long totalTimeUs, long activeTimeUs, int version) {
+ this.count = count;
+ this.totalTimeUs = totalTimeUs;
+ this.activeTimeUs = activeTimeUs;
+ this.version = version;
}
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 837971f17f93..d50a208c1d7e 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1733,7 +1733,7 @@ public class StatsPullAtomService extends SystemService {
String name = ent.getKey();
KernelWakelockStats.Entry kws = ent.getValue();
pulledData.add(FrameworkStatsLog.buildStatsEvent(
- atomTag, name, kws.mCount, kws.mVersion, kws.mTotalTime));
+ atomTag, name, kws.count, kws.version, kws.totalTimeUs));
}
return StatsManager.PULL_SUCCESS;
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 363d2fdf7f4c..044d30b368da 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -53,7 +53,6 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Icon;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
-import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
@@ -949,14 +948,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, long operationId, String opPackageName, long requestId,
- @BiometricMultiSensorMode int multiSensorConfig) {
+ int userId, long operationId, String opPackageName, long requestId) {
enforceBiometricDialog();
if (mBar != null) {
try {
mBar.showAuthenticationDialog(promptInfo, receiver, sensorIds, credentialAllowed,
- requireConfirmation, userId, operationId, opPackageName, requestId,
- multiSensorConfig);
+ requireConfirmation, userId, operationId, opPackageName, requestId);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index dc2a97466dea..58c31d565cf3 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -39,7 +39,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -65,8 +64,6 @@ import java.util.function.Supplier;
*/
final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
- private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;
-
/**
* An absolute threshold at/below which the system clock confidence can be upgraded. i.e. if the
* detector receives a high-confidence time and the current system clock is +/- this value from
@@ -122,9 +119,8 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
mConfigOriginPrioritiesSupplier = new ConfigOriginPrioritiesSupplier(context);
mServerFlagsOriginPrioritiesSupplier =
new ServerFlagsOriginPrioritiesSupplier(mServerFlags);
- mSystemClockUpdateThresholdMillis =
- SystemProperties.getInt("ro.sys.time_detector_update_diff",
- SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT);
+ mSystemClockUpdateThresholdMillis = context.getResources().getInteger(
+ R.integer.config_timeDetectorAutoUpdateDiffMillis);
// Wire up the config change listeners for anything that could affect ConfigurationInternal.
// Use the main thread for event delivery, listeners can post to their chosen thread.
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 9add53751e2f..a079875a23e4 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -82,6 +82,7 @@ import android.os.IBinder;
import android.os.IInterface;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -2514,6 +2515,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
* Propagate a wake event to the wallpaper engine(s).
*/
public void notifyWakingUp(int x, int y, @NonNull Bundle extras) {
+ checkCallerIsSystemOrSystemUi();
synchronized (mLock) {
if (mIsLockscreenLiveWallpaperEnabled) {
for (WallpaperData data : getActiveWallpapers()) {
@@ -2551,6 +2553,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
* Propagate a sleep event to the wallpaper engine(s).
*/
public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) {
+ checkCallerIsSystemOrSystemUi();
synchronized (mLock) {
if (mIsLockscreenLiveWallpaperEnabled) {
for (WallpaperData data : getActiveWallpapers()) {
@@ -3684,6 +3687,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mActivityManager.getPackageImportance(callingPackage) == IMPORTANCE_FOREGROUND);
}
+ /** Check that the caller is either system_server or systemui */
+ private void checkCallerIsSystemOrSystemUi() {
+ if (Binder.getCallingUid() != Process.myUid() && mContext.checkCallingPermission(
+ android.Manifest.permission.STATUS_BAR_SERVICE) != PERMISSION_GRANTED) {
+ throw new SecurityException("Access denied: only system processes can call this");
+ }
+ }
+
/**
* Certain user types do not support wallpapers (e.g. managed profiles). The check is
* implemented through through the OP_WRITE_WALLPAPER AppOp.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d84c01309286..3db031510317 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3570,6 +3570,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Tell window manager to prepare for this one to be removed.
setVisibility(false);
+ // Propagate the last IME visibility in the same task, so the IME can show
+ // automatically if the next activity has a focused editable view.
+ if (mLastImeShown && mTransitionController.isShellTransitionsEnabled()) {
+ final ActivityRecord nextRunning = task.topRunningActivity();
+ if (nextRunning != null) {
+ nextRunning.mLastImeShown = true;
+ }
+ }
if (getTaskFragment().getPausingActivity() == null) {
ProtoLog.v(WM_DEBUG_STATES, "Finish needs to pause: %s", this);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1360a956dc0b..750ed986f567 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5342,6 +5342,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return null;
}
+ /**
+ * Returns the {@link WindowProcessController} for the app process for the given uid and pid.
+ *
+ * If no such {@link WindowProcessController} is found, it does not belong to an app, or the
+ * pid does not match the uid {@code null} is returned.
+ */
WindowProcessController getProcessController(int pid, int uid) {
final WindowProcessController proc = mProcessMap.getProcess(pid);
if (proc == null) return null;
@@ -5351,6 +5357,27 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return null;
}
+ /**
+ * Returns the package name if (and only if) the package name can be uniquely determined.
+ * Otherwise returns {@code null}.
+ *
+ * The provided pid must match the provided uid, otherwise this also returns null.
+ */
+ @Nullable String getPackageNameIfUnique(int uid, int pid) {
+ final WindowProcessController proc = mProcessMap.getProcess(pid);
+ if (proc == null || proc.mUid != uid) {
+ Slog.w(TAG, "callingPackage for (uid=" + uid + ", pid=" + pid + ") has no WPC");
+ return null;
+ }
+ List<String> realCallingPackages = proc.getPackageList();
+ if (realCallingPackages.size() != 1) {
+ Slog.w(TAG, "callingPackage for (uid=" + uid + ", pid=" + pid + ") is ambiguous: "
+ + realCallingPackages);
+ return null;
+ }
+ return realCallingPackages.get(0);
+ }
+
/** A uid is considered to be foreground if it has a visible non-toast window. */
@HotPath(caller = HotPath.START_SERVICE)
boolean hasActiveVisibleWindow(int uid) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index dc49e8cea18b..b216578262b4 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -180,7 +180,8 @@ public class BackgroundActivityStartController {
Intent intent,
ActivityOptions checkedOptions) {
return checkBackgroundActivityStart(callingUid, callingPid, callingPackage,
- realCallingUid, realCallingPid, callerApp, originatingPendingIntent,
+ realCallingUid, realCallingPid,
+ callerApp, originatingPendingIntent,
backgroundStartPrivileges, intent, checkedOptions) == BAL_BLOCK;
}
@@ -288,11 +289,13 @@ public class BackgroundActivityStartController {
}
}
+ String realCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
+
// Legacy behavior allows to use caller foreground state to bypass BAL restriction.
// The options here are the options passed by the sender and not those on the intent.
final BackgroundStartPrivileges balAllowedByPiSender =
PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
- checkedOptions, realCallingUid);
+ checkedOptions, realCallingUid, realCallingPackage);
final boolean logVerdictChangeByPiDefaultChange = checkedOptions == null
|| checkedOptions.getPendingIntentBackgroundActivityStartMode()
@@ -460,8 +463,11 @@ public class BackgroundActivityStartController {
// If we are here, it means all exemptions not based on PI sender failed, so we'll block
// unless resultIfPiSenderAllowsBal is an allow and the PI sender allows BAL
- String realCallingPackage = callingUid == realCallingUid ? callingPackage :
- mService.mContext.getPackageManager().getNameForUid(realCallingUid);
+ if (realCallingPackage == null) {
+ realCallingPackage = (callingUid == realCallingUid ? callingPackage :
+ mService.mContext.getPackageManager().getNameForUid(realCallingUid))
+ + "[debugOnly]";
+ }
String stateDumpLog = " [callingPackage: " + callingPackage
+ "; callingUid: " + callingUid
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e51f3120ed44..b7327528b069 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -271,13 +271,13 @@ public class DisplayPolicy {
private WindowState mSystemUiControllingWindow;
// Candidate window to determine the color of navigation bar. The window needs to be top
- // fullscreen-app windows or dim layers that are intersecting with the window frame of status
- // bar.
+ // fullscreen-app windows or dim layers that are intersecting with the window frame of
+ // navigation bar.
private WindowState mNavBarColorWindowCandidate;
- // The window to determine opacity and background of translucent navigation bar. The window
- // needs to be opaque.
- private WindowState mNavBarBackgroundWindow;
+ // Candidate window to determine opacity and background of translucent navigation bar.
+ // The window frame must intersect the frame of navigation bar.
+ private WindowState mNavBarBackgroundWindowCandidate;
/**
* A collection of {@link AppearanceRegion} to indicate that which region of status bar applies
@@ -961,12 +961,6 @@ public class DisplayPolicy {
if (!win.mSession.mCanSetUnrestrictedGestureExclusion) {
attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
}
-
- final InsetsSourceProvider provider = win.getControllableInsetProvider();
- if (provider != null && provider.getSource().insetsRoundedCornerFrame()
- != attrs.insetsRoundedCornerFrame) {
- provider.getSource().setInsetsRoundedCornerFrame(attrs.insetsRoundedCornerFrame);
- }
}
/**
@@ -1104,9 +1098,11 @@ public class DisplayPolicy {
} else {
overrideProviders = null;
}
- mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(
- provider.getId(), provider.getType()).setWindowContainer(
- win, frameProvider, overrideProviders);
+ final InsetsSourceProvider sourceProvider = mDisplayContent
+ .getInsetsStateController().getOrCreateSourceProvider(provider.getId(),
+ provider.getType());
+ sourceProvider.getSource().setFlags(provider.getFlags());
+ sourceProvider.setWindowContainer(win, frameProvider, overrideProviders);
mInsetsSourceWindowsExceptIme.add(win);
}
}
@@ -1387,7 +1383,7 @@ public class DisplayPolicy {
mBottomGestureHost = null;
mTopFullscreenOpaqueWindowState = null;
mNavBarColorWindowCandidate = null;
- mNavBarBackgroundWindow = null;
+ mNavBarBackgroundWindowCandidate = null;
mStatusBarAppearanceRegionList.clear();
mLetterboxDetails.clear();
mStatusBarBackgroundWindows.clear();
@@ -1514,8 +1510,8 @@ public class DisplayPolicy {
mNavBarColorWindowCandidate = win;
addSystemBarColorApp(win);
}
- if (mNavBarBackgroundWindow == null) {
- mNavBarBackgroundWindow = win;
+ if (mNavBarBackgroundWindowCandidate == null) {
+ mNavBarBackgroundWindowCandidate = win;
}
}
@@ -1539,12 +1535,19 @@ public class DisplayPolicy {
}
if (isOverlappingWithNavBar(win) && mNavBarColorWindowCandidate == null) {
mNavBarColorWindowCandidate = win;
+ addSystemBarColorApp(win);
}
- } else if (appWindow && attached == null && mNavBarColorWindowCandidate == null
+ } else if (appWindow && attached == null
+ && (mNavBarColorWindowCandidate == null || mNavBarBackgroundWindowCandidate == null)
&& win.getFrame().contains(
getBarContentFrameForWindow(win, Type.navigationBars()))) {
- mNavBarColorWindowCandidate = win;
- addSystemBarColorApp(win);
+ if (mNavBarColorWindowCandidate == null) {
+ mNavBarColorWindowCandidate = win;
+ addSystemBarColorApp(win);
+ }
+ if (mNavBarBackgroundWindowCandidate == null) {
+ mNavBarBackgroundWindowCandidate = win;
+ }
}
}
@@ -2465,7 +2468,7 @@ public class DisplayPolicy {
return win.isFullyTransparentBarAllowed(getBarContentFrameForWindow(win, type));
}
- private boolean drawsBarBackground(WindowState win) {
+ private static boolean drawsBarBackground(WindowState win) {
if (win == null) {
return true;
}
@@ -2505,7 +2508,11 @@ public class DisplayPolicy {
*/
private int configureNavBarOpacity(int appearance, boolean multiWindowTaskVisible,
boolean freeformRootTaskVisible) {
- final boolean drawBackground = drawsBarBackground(mNavBarBackgroundWindow);
+ final WindowState navBackgroundWin = chooseNavigationBackgroundWindow(
+ mNavBarBackgroundWindowCandidate,
+ mDisplayContent.mInputMethodWindow,
+ mNavigationBarPosition);
+ final boolean drawBackground = navBackgroundWin != null;
if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
if (drawBackground) {
@@ -2525,7 +2532,7 @@ public class DisplayPolicy {
}
}
- if (!isFullyTransparentAllowed(mNavBarBackgroundWindow, Type.navigationBars())) {
+ if (!isFullyTransparentAllowed(navBackgroundWin, Type.navigationBars())) {
appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
}
@@ -2536,6 +2543,20 @@ public class DisplayPolicy {
return appearance & ~APPEARANCE_OPAQUE_NAVIGATION_BARS;
}
+ @VisibleForTesting
+ @Nullable
+ static WindowState chooseNavigationBackgroundWindow(WindowState candidate,
+ WindowState imeWindow, @NavigationBarPosition int navBarPosition) {
+ if (imeWindow != null && imeWindow.isVisible() && navBarPosition == NAV_BAR_BOTTOM
+ && drawsBarBackground(imeWindow)) {
+ return imeWindow;
+ }
+ if (drawsBarBackground(candidate)) {
+ return candidate;
+ }
+ return null;
+ }
+
private boolean isImmersiveMode(WindowState win) {
if (win == null) {
return false;
@@ -2708,9 +2729,9 @@ public class DisplayPolicy {
pw.print(prefix); pw.print("mNavBarColorWindowCandidate=");
pw.println(mNavBarColorWindowCandidate);
}
- if (mNavBarBackgroundWindow != null) {
- pw.print(prefix); pw.print("mNavBarBackgroundWindow=");
- pw.println(mNavBarBackgroundWindow);
+ if (mNavBarBackgroundWindowCandidate != null) {
+ pw.print(prefix); pw.print("mNavBarBackgroundWindowCandidate=");
+ pw.println(mNavBarBackgroundWindowCandidate);
}
if (mLastStatusBarAppearanceRegions != null) {
pw.print(prefix); pw.println("mLastStatusBarAppearanceRegions=");
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index b7eaf259ea7a..7f845e6c1ead 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -175,7 +175,7 @@ class InsetsSourceProvider {
if (windowContainer == null) {
setServerVisible(false);
mSource.setVisibleFrame(null);
- mSource.setInsetsRoundedCornerFrame(false);
+ mSource.setFlags(0, 0xffffffff);
mSourceFrame.setEmpty();
} else {
mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index d83c8612b9e9..c2439888db43 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1428,7 +1428,8 @@ final class LetterboxUiController {
for (int i = state.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = state.sourceAt(i);
if (source.getType() == WindowInsets.Type.navigationBars()
- && source.insetsRoundedCornerFrame() && source.isVisible()) {
+ && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)
+ && source.isVisible()) {
return source;
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index bb6f8056acda..9ce788686e85 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -49,6 +49,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.SurfaceControl.METADATA_TASK_ID;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
@@ -2858,7 +2859,7 @@ class Task extends TaskFragment {
getDisplayContent().getInsetsStateController().getRawInsetsState();
for (int i = state.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = state.sourceAt(i);
- if (source.insetsRoundedCornerFrame()) {
+ if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) {
animationBounds.inset(source.calculateVisibleInsets(animationBounds));
}
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index c763cfac4e88..0ce794fdb2ba 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -382,11 +382,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks,
// so ChangeInfo#hasChanged() can return true to report the transition info.
for (int i = mChanges.size() - 1; i >= 0; --i) {
- final WindowContainer<?> wc = mChanges.keyAt(i);
- if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) continue;
- if (isInTransientHide(wc)) {
- mChanges.valueAt(i).mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
- }
+ updateTransientFlags(mChanges.valueAt(i));
}
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
@@ -581,7 +577,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
for (WindowContainer<?> curr = getAnimatableParent(wc);
curr != null && !mChanges.containsKey(curr);
curr = getAnimatableParent(curr)) {
- mChanges.put(curr, new ChangeInfo(curr));
+ final ChangeInfo info = new ChangeInfo(curr);
+ updateTransientFlags(info);
+ mChanges.put(curr, info);
if (isReadyGroup(curr)) {
mReadyTracker.addGroup(curr);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
@@ -600,6 +598,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
ChangeInfo info = mChanges.get(wc);
if (info == null) {
info = new ChangeInfo(wc);
+ updateTransientFlags(info);
mChanges.put(wc, info);
}
mParticipants.add(wc);
@@ -615,6 +614,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
+ private void updateTransientFlags(@NonNull ChangeInfo info) {
+ final WindowContainer<?> wc = info.mContainer;
+ // Only look at tasks, taskfragments, or activities
+ if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) return;
+ if (!isInTransientHide(wc)) return;
+ info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+ }
+
private void recordDisplay(DisplayContent dc) {
if (dc == null || mTargetDisplays.contains(dc)) return;
mTargetDisplays.add(dc);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0152666a830d..4bc4c266c114 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4141,7 +4141,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
getDisplayContent().getInsetsStateController().getSourceProviders();
for (int i = providers.size(); i >= 0; i--) {
final InsetsSourceProvider insetProvider = providers.valueAt(i);
- if (!insetProvider.getSource().insetsRoundedCornerFrame()) {
+ if (!insetProvider.getSource().hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 28220f4bf3be..a71329637733 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9231,7 +9231,6 @@ public class WindowManagerService extends IWindowManager.Stub
boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
final Task imeTargetWindowTask;
- boolean hadRequestedShowIme = false;
synchronized (mGlobalLock) {
final WindowState imeTargetWindow = mWindowMap.get(imeTargetWindowToken);
if (imeTargetWindow == null) {
@@ -9241,14 +9240,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (imeTargetWindowTask == null) {
return false;
}
- if (imeTargetWindow.mActivityRecord != null) {
- hadRequestedShowIme = imeTargetWindow.mActivityRecord.mLastImeShown;
+ if (imeTargetWindow.mActivityRecord != null
+ && imeTargetWindow.mActivityRecord.mLastImeShown) {
+ return true;
}
}
final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId,
imeTargetWindowTask.mUserId, false /* isLowResolution */,
false /* restoreFromDisk */);
- return snapshot != null && snapshot.hasImeSurface() || hadRequestedShowIme;
+ return snapshot != null && snapshot.hasImeSurface();
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index dbd9e4b8ea68..3672820c13ad 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -721,6 +721,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
}
+ List<String> getPackageList() {
+ synchronized (mPkgList) {
+ return new ArrayList<>(mPkgList);
+ }
+ }
+
void addActivityIfNeeded(ActivityRecord r) {
// even if we already track this activity, note down that it has been launched
setLastActivityLaunchTime(r);
diff --git a/services/core/lint-baseline.xml b/services/core/lint-baseline.xml
index 69e13b38873a..070bd4b1c5a9 100644
--- a/services/core/lint-baseline.xml
+++ b/services/core/lint-baseline.xml
@@ -137,4 +137,12 @@
line="1448"/>
</issue>
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IWindowManager permission check should be converted to @EnforcePermission annotation">
+ <location
+ file="out/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java"
+ line="7158"/>
+ </issue>
+
</issues>
diff --git a/services/lint-baseline.xml b/services/lint-baseline.xml
new file mode 100644
index 000000000000..8489c17dd878
--- /dev/null
+++ b/services/lint-baseline.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_CARRIER_APP_INFO,"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+ line="46"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_CARRIER_APP_INFO,"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+ line="54"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_CARRIER_APP_INFO,"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+ line="67"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS,"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+ line="76"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" getContext().enforceCallingOrSelfPermission(Manifest.permission.QUERY_ALL_PACKAGES,"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+ line="107"
+ column="13"/>
+ </issue>
+
+</issues>
diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
index 106c3a83e373..a3f65af6bc82 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
@@ -22,7 +22,7 @@ import android.os.Message
import android.os.SystemClock
import android.os.UserHandle
import android.util.AtomicFile
-import android.util.Log
+import android.util.Slog
import android.util.SparseLongArray
import com.android.internal.annotations.GuardedBy
import com.android.internal.os.BackgroundThread
@@ -98,7 +98,7 @@ class AccessPersistence(
AtomicFile(this).readWithReserveCopy { it.parseBinaryXml(block) }
true
} catch (e: FileNotFoundException) {
- Log.i(LOG_TAG, "$this not found")
+ Slog.i(LOG_TAG, "$this not found")
false
} catch (e: Exception) {
throw IllegalStateException("Failed to read $this", e)
@@ -176,7 +176,7 @@ class AccessPersistence(
try {
AtomicFile(this).writeWithReserveCopy { it.serializeBinaryXml(block) }
} catch (e: Exception) {
- Log.e(LOG_TAG, "Failed to serialize $this", e)
+ Slog.e(LOG_TAG, "Failed to serialize $this", e)
}
}
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 4096132d5741..f14434b6a79c 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -16,7 +16,7 @@
package com.android.server.permission.access
-import android.util.Log
+import android.util.Slog
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.SystemConfig
@@ -326,7 +326,7 @@ class AccessPolicy private constructor(
VERSION_LATEST
}
version == VERSION_LATEST -> {}
- else -> Log.w(
+ else -> Slog.w(
LOG_TAG, "Unexpected version $version for package $packageName," +
"latest version is $VERSION_LATEST"
)
@@ -343,7 +343,7 @@ class AccessPolicy private constructor(
}
}
}
- else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state")
+ else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state")
}
}
}
@@ -372,7 +372,7 @@ class AccessPolicy private constructor(
}
}
else -> {
- Log.w(
+ Slog.w(
LOG_TAG,
"Ignoring unknown tag $tagName when parsing user state for user $userId"
)
@@ -387,12 +387,12 @@ class AccessPolicy private constructor(
forEachTag {
when (tagName) {
TAG_PACKAGE -> parsePackageVersion(packageVersions)
- else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing package versions")
+ else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing package versions")
}
}
packageVersions.forEachReversedIndexed { packageVersionIndex, packageName, _ ->
if (packageName !in state.externalState.packageStates) {
- Log.w(LOG_TAG, "Dropping unknown $packageName when parsing package versions")
+ Slog.w(LOG_TAG, "Dropping unknown $packageName when parsing package versions")
packageVersions.removeAt(packageVersionIndex)
userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
index d83beab0975e..96d315e923ba 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
@@ -17,7 +17,7 @@
package com.android.server.permission.access.appop
import android.os.Process
-import android.util.Log
+import android.util.Slog
import com.android.server.LocalServices
import com.android.server.appop.AppOpMigrationHelper
import com.android.server.permission.access.MutableAccessState
@@ -40,7 +40,7 @@ class AppIdAppOpMigration {
val packageNames = state.externalState.appIdPackageNames[appId]
// Non-application UIDs may not have an Android package but may still have app op state.
if (packageNames == null && appId >= Process.FIRST_APPLICATION_UID) {
- Log.w(LOG_TAG, "Dropping unknown app ID $appId when migrating app op state")
+ Slog.w(LOG_TAG, "Dropping unknown app ID $appId when migrating app op state")
return@forEach
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
index 175ec4bfadd1..4c7e94688d00 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
@@ -17,7 +17,7 @@
package com.android.server.permission.access.appop
import android.os.Process
-import android.util.Log
+import android.util.Slog
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.AccessState
@@ -46,14 +46,14 @@ class AppIdAppOpPersistence : BaseAppOpPersistence() {
forEachTag {
when (tagName) {
TAG_APP_ID -> parseAppId(appIdAppOpModes)
- else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
+ else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
}
}
userState.appIdAppOpModes.forEachReversedIndexed { appIdIndex, appId, _ ->
// Non-application UIDs may not have an Android package but may still have app op state.
if (appId !in state.externalState.appIdPackageNames &&
appId >= Process.FIRST_APPLICATION_UID) {
- Log.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state")
+ Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state")
appIdAppOpModes.removeAt(appIdIndex)
userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
index 53e53927cef1..a267637dd0c4 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
@@ -16,7 +16,7 @@
package com.android.server.permission.access.appop
-import android.util.Log
+import android.util.Slog
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.AccessState
@@ -40,7 +40,7 @@ abstract class BaseAppOpPersistence {
forEachTag {
when (tagName) {
TAG_APP_OP -> parseAppOp(appOpModes)
- else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
+ else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
}
}
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
index 3044db692eb9..03311a238410 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
@@ -16,7 +16,7 @@
package com.android.server.permission.access.appop
-import android.util.Log
+import android.util.Slog
import com.android.server.LocalServices
import com.android.server.appop.AppOpMigrationHelper
import com.android.server.permission.access.MutableAccessState
@@ -38,7 +38,7 @@ class PackageAppOpMigration {
val packageAppOpModes = userState.mutatePackageAppOpModes()
legacyPackageAppOpModes.forEach { (packageName, legacyAppOpModes) ->
if (packageName !in state.externalState.packageStates) {
- Log.w(LOG_TAG, "Dropping unknown package $packageName when migrating app op state")
+ Slog.w(LOG_TAG, "Dropping unknown package $packageName when migrating app op state")
return@forEach
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt
index 347002a80f08..169bd6970a78 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt
@@ -16,7 +16,7 @@
package com.android.server.permission.access.appop
-import android.util.Log
+import android.util.Slog
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.AccessState
@@ -46,12 +46,12 @@ class PackageAppOpPersistence : BaseAppOpPersistence() {
forEachTag {
when (tagName) {
TAG_PACKAGE -> parsePackage(packageAppOpModes)
- else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
+ else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
}
}
packageAppOpModes.forEachReversedIndexed { packageNameIndex, packageName, _ ->
if (packageName !in state.externalState.packageStates) {
- Log.w(LOG_TAG, "Dropping unknown package $packageName when parsing app-op state")
+ Slog.w(LOG_TAG, "Dropping unknown package $packageName when parsing app-op state")
packageAppOpModes.removeAt(packageNameIndex)
userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
index d53ec80bf29d..691ed8f10220 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
@@ -16,7 +16,7 @@
package com.android.server.permission.access.permission
-import android.util.Log
+import android.util.Slog
import com.android.server.LocalServices
import com.android.server.permission.access.MutableAccessState
import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
@@ -51,7 +51,7 @@ class AppIdPermissionMigration {
)
permissions[permission.name] = permission
if (DEBUG_MIGRATION) {
- Log.v(LOG_TAG, "Migrated permission: ${permission.name}, type: " +
+ Slog.v(LOG_TAG, "Migrated permission: ${permission.name}, type: " +
"${permission.type}, appId: ${permission.appId}, protectionLevel: " +
"${permission.protectionLevel}, tree: $isPermissionTree"
)
@@ -75,7 +75,7 @@ class AppIdPermissionMigration {
legacyAppIdPermissionStates.forEach { (appId, legacyPermissionStates) ->
val packageNames = state.externalState.appIdPackageNames[appId]
if (packageNames == null) {
- Log.w(LOG_TAG, "Dropping unknown app ID $appId when migrating permission state")
+ Slog.w(LOG_TAG, "Dropping unknown app ID $appId when migrating permission state")
return@forEach
}
@@ -85,7 +85,7 @@ class AppIdPermissionMigration {
(permissionName, legacyPermissionState) ->
val permission = state.systemState.permissions[permissionName]
if (permission == null) {
- Log.w(
+ Slog.w(
LOG_TAG, "Dropping unknown permission $permissionName for app ID $appId" +
" when migrating permission state"
)
@@ -138,7 +138,7 @@ class AppIdPermissionMigration {
val oldGrantState = legacyPermissionState.isGranted
val newGrantState = PermissionFlags.isPermissionGranted(flags)
val flagsMismatch = legacyPermissionState.flags != PermissionFlags.toApiFlags(flags)
- Log.v(
+ Slog.v(
LOG_TAG, "Migrated appId: $appId, permission: " +
"${permission.name}, user: $userId, oldGrantState: $oldGrantState" +
", oldFlags: $oldFlagString, newFlags: $newFlagString, grantMismatch: " +
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
index 615bbdc71f85..fffc7031326a 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
@@ -17,7 +17,7 @@
package com.android.server.permission.access.permission
import android.content.pm.PermissionInfo
-import android.util.Log
+import android.util.Slog
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.AccessState
@@ -27,6 +27,7 @@ import com.android.server.permission.access.MutableAppIdPermissionFlags
import com.android.server.permission.access.WriteMode
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.andInv
import com.android.server.permission.access.util.attribute
import com.android.server.permission.access.util.attributeInt
import com.android.server.permission.access.util.attributeIntHex
@@ -38,6 +39,7 @@ import com.android.server.permission.access.util.getAttributeIntHexOrThrow
import com.android.server.permission.access.util.getAttributeIntOrThrow
import com.android.server.permission.access.util.getAttributeValue
import com.android.server.permission.access.util.getAttributeValueOrThrow
+import com.android.server.permission.access.util.hasBits
import com.android.server.permission.access.util.tag
import com.android.server.permission.access.util.tagName
@@ -63,7 +65,7 @@ class AppIdPermissionPersistence {
forEachTag {
when (val tagName = tagName) {
TAG_PERMISSION -> parsePermission(permissions)
- else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing permissions")
+ else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing permissions")
}
}
permissions.forEachReversedIndexed { permissionIndex, _, permission ->
@@ -71,7 +73,7 @@ class AppIdPermissionPersistence {
val externalState = state.externalState
if (packageName !in externalState.packageStates &&
packageName !in externalState.disabledSystemPackageStates) {
- Log.w(
+ Slog.w(
LOG_TAG,
"Dropping permission with unknown package $packageName when parsing permissions"
)
@@ -95,7 +97,7 @@ class AppIdPermissionPersistence {
when (type) {
Permission.TYPE_MANIFEST -> {}
Permission.TYPE_CONFIG -> {
- Log.w(LOG_TAG, "Ignoring unexpected config permission $name")
+ Slog.w(LOG_TAG, "Ignoring unexpected config permission $name")
return
}
Permission.TYPE_DYNAMIC -> {
@@ -105,7 +107,7 @@ class AppIdPermissionPersistence {
}
}
else -> {
- Log.w(LOG_TAG, "Ignoring permission $name with unknown type $type")
+ Slog.w(LOG_TAG, "Ignoring permission $name with unknown type $type")
return
}
}
@@ -134,7 +136,7 @@ class AppIdPermissionPersistence {
Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {}
Permission.TYPE_CONFIG -> return
else -> {
- Log.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type")
+ Slog.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type")
return
}
}
@@ -164,12 +166,12 @@ class AppIdPermissionPersistence {
forEachTag {
when (tagName) {
TAG_APP_ID -> parseAppId(appIdPermissionFlags)
- else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
+ else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
}
}
appIdPermissionFlags.forEachReversedIndexed { appIdIndex, appId, _ ->
if (appId !in state.externalState.appIdPackageNames) {
- Log.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state")
+ Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state")
appIdPermissionFlags.removeAt(appIdIndex)
userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
}
@@ -183,7 +185,7 @@ class AppIdPermissionPersistence {
forEachTag {
when (tagName) {
TAG_PERMISSION -> parseAppIdPermission(permissionFlags)
- else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
+ else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
}
}
}
@@ -225,7 +227,13 @@ class AppIdPermissionPersistence {
private fun BinaryXmlSerializer.serializeAppIdPermission(name: String, flags: Int) {
tag(TAG_PERMISSION) {
attributeInterned(ATTR_NAME, name)
- attributeInt(ATTR_FLAGS, flags)
+ // Never serialize one-time permissions as granted.
+ val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) {
+ flags andInv PermissionFlags.RUNTIME_GRANTED
+ } else {
+ flags
+ }
+ attributeInt(ATTR_FLAGS, serializedFlags)
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index e395d75f0c9e..0e62d25554c8 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -22,7 +22,7 @@ import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
import android.content.pm.SigningDetails
import android.os.Build
-import android.util.Log
+import android.util.Slog
import com.android.internal.os.RoSystemProperties
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
@@ -339,14 +339,14 @@ class AppIdPermissionPolicy : SchemePolicy() {
val originalPackageState = newState.externalState.packageStates[originalPackageName]
?: return false
if (!originalPackageState.isSystem) {
- Log.w(
+ Slog.w(
LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
" original package not in system partition"
)
return false
}
if (originalPackageState.androidPackage != null) {
- Log.w(
+ Slog.w(
LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
" original package still exists"
)
@@ -361,7 +361,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
// app is non-instant in at least one user.
val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp }
if (isInstantApp) {
- Log.w(
+ Slog.w(
LOG_TAG, "Ignoring permission groups declared in package" +
" ${packageState.packageName}: instant apps cannot declare permission groups"
)
@@ -383,7 +383,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
// non-system apps, we now allow system apps to override permission groups similar
// to permissions so that we no longer need to rely on the scan order.
if (!packageState.isSystem) {
- Log.w(
+ Slog.w(
LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
" package $newPackageName: already declared in another" +
" package $oldPackageName"
@@ -391,14 +391,14 @@ class AppIdPermissionPolicy : SchemePolicy() {
return@forEachIndexed
}
if (newState.externalState.packageStates[oldPackageName]?.isSystem == true) {
- Log.w(
+ Slog.w(
LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
" system package $newPackageName: already declared in another" +
" system package $oldPackageName"
)
return@forEachIndexed
}
- Log.w(
+ Slog.w(
LOG_TAG, "Overriding permission group $permissionGroupName with" +
" new declaration in system package $newPackageName: originally" +
" declared in another package $oldPackageName"
@@ -429,7 +429,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
val permissionTree = findPermissionTree(permissionName)
val newPackageName = newPermissionInfo.packageName
if (permissionTree != null && newPackageName != permissionTree.packageName) {
- Log.w(
+ Slog.w(
LOG_TAG, "Ignoring permission $permissionName declared in package" +
" $newPackageName: base permission tree ${permissionTree.name} is" +
" declared in another package ${permissionTree.packageName}"
@@ -441,7 +441,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
val oldPackageName = oldPermission.packageName
// Only allow system apps to redefine non-system permissions.
if (!packageState.isSystem) {
- Log.w(
+ Slog.w(
LOG_TAG, "Ignoring permission $permissionName declared in package" +
" $newPackageName: already declared in another package" +
" $oldPackageName"
@@ -455,7 +455,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
appId = packageState.appId
)
} else if (newState.externalState.packageStates[oldPackageName]?.isSystem != true) {
- Log.w(
+ Slog.w(
LOG_TAG, "Overriding permission $permissionName with new declaration in" +
" system package $newPackageName: originally declared in another" +
" package $oldPackageName"
@@ -474,7 +474,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
oldPermission.gids, oldPermission.areGidsPerUser
)
} else {
- Log.w(
+ Slog.w(
LOG_TAG, "Ignoring permission $permissionName declared in system package" +
" $newPackageName: already declared in another system package" +
" $oldPackageName"
@@ -498,7 +498,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
// the group is already granted. Hence if the group of
// a granted permission changes we need to revoke it to
// avoid having permissions of the new group auto-granted.
- Log.w(
+ Slog.w(
LOG_TAG, "Revoking runtime permission $permissionName for" +
" appId $appId and userId $userId as the permission" +
" group changed from ${oldPermission.groupName}" +
@@ -506,7 +506,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
)
}
if (isPermissionTypeChanged) {
- Log.w(
+ Slog.w(
LOG_TAG, "Revoking permission $permissionName for" +
" appId $appId and userId $userId as the permission" +
" type changed."
@@ -660,7 +660,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
!oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage
if ((isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded) &&
oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED)) {
- Log.v(LOG_TAG, "Revoking storage permission: $permissionName for appId: " +
+ Slog.v(LOG_TAG, "Revoking storage permission: $permissionName for appId: " +
" $appId and user: $userId")
val newFlags = oldFlags andInv (
PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK
@@ -749,12 +749,17 @@ class AppIdPermissionPolicy : SchemePolicy() {
// If this is an existing, non-system package,
// then we can't add any new permissions to it.
// Except if this is a permission that was added to the platform
- val newFlags = if (!wasRevoked || isRequestedByInstalledPackage ||
+ var newFlags = if (!wasRevoked || isRequestedByInstalledPackage ||
isRequestedBySystemPackage || isCompatibilityPermission) {
PermissionFlags.INSTALL_GRANTED
} else {
PermissionFlags.INSTALL_REVOKED
}
+ if (permission.isAppOp) {
+ newFlags = newFlags or (
+ oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)
+ )
+ }
setPermissionFlags(appId, userId, permissionName, newFlags)
}
} else if (permission.isSignature || permission.isInternal) {
@@ -784,6 +789,11 @@ class AppIdPermissionPolicy : SchemePolicy() {
0
}
}
+ if (permission.isAppOp) {
+ newFlags = newFlags or (
+ oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)
+ )
+ }
// Different from the old implementation, which seemingly allows granting an
// unallowlisted privileged permission via development or role but revokes it upon next
// reconciliation, we now properly allows that because the privileged protection flag
@@ -917,7 +927,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
}
setPermissionFlags(appId, userId, permissionName, newFlags)
} else {
- Log.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" +
+ Slog.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" +
"for permission ${permission.name} while evaluating permission state" +
"for appId $appId and userId $userId")
}
@@ -977,7 +987,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) {
if (compatibilityPermission.name == permissionName &&
androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) {
- Log.i(
+ Slog.i(
LOG_TAG, "Auto-granting $permissionName to old package" +
" ${androidPackage.packageName}"
)
@@ -1041,7 +1051,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
if (!newState.externalState.isSystemReady) {
// Apps that are in updated apex's do not need to be allowlisted
if (!packageState.isApkInUpdatedApex) {
- Log.w(
+ Slog.w(
LOG_TAG, "Privileged permission ${permission.name} for package" +
" ${packageState.packageName} (${packageState.path}) not in" +
" privileged permission allowlist"
@@ -1084,7 +1094,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
if (nonApexAllowlistState != null) {
// TODO(andreionea): Remove check as soon as all apk-in-apex
// permission allowlists are migrated.
- Log.w(
+ Slog.w(
LOG_TAG, "Package $packageName is an APK in APEX but has permission" +
" allowlist on the system image, please bundle the allowlist in the" +
" $apexModuleName APEX instead"
@@ -1274,7 +1284,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
// if the permission's protectionLevel does not have the extra vendorPrivileged
// flag.
if (packageState.isVendor && !permission.isVendorPrivileged) {
- Log.w(
+ Slog.w(
LOG_TAG, "Permission $permissionName cannot be granted to privileged" +
" vendor app $packageName because it isn't a vendorPrivileged" +
" permission"
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
index 8ed874798d84..875183cc941b 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
@@ -18,7 +18,7 @@ package com.android.server.permission.access.permission
import android.Manifest
import android.os.Build
-import android.util.Log
+import android.util.Slog
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
@@ -42,7 +42,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) {
) {
val packageName = packageState.packageName
if (version <= 3) {
- Log.v(
+ Slog.v(
LOG_TAG, "Allowlisting and upgrading background location permission for " +
"package: $packageName, version: $version, user:$userId"
)
@@ -50,7 +50,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) {
upgradeBackgroundLocationPermission(packageState, userId)
}
if (version <= 10) {
- Log.v(
+ Slog.v(
LOG_TAG, "Upgrading access media location permission for package: $packageName" +
", version: $version, user: $userId"
)
@@ -58,7 +58,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) {
}
// Enable isAtLeastT check, when moving subsystem to mainline.
if (version <= 12 /*&& SdkLevel.isAtLeastT()*/) {
- Log.v(
+ Slog.v(
LOG_TAG, "Upgrading scoped permissions for package: $packageName" +
", version: $version, user: $userId"
)
@@ -159,7 +159,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) {
userId: Int,
permissionName: String
) {
- Log.v(
+ Slog.v(
LOG_TAG, "Granting runtime permission for package: ${packageState.packageName}, " +
"permission: $permissionName, userId: $userId"
)
@@ -171,7 +171,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) {
val appId = packageState.appId
var flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
if (flags.hasAnyBit(MASK_ANY_FIXED)) {
- Log.v(
+ Slog.v(
LOG_TAG,
"Not allowed to grant $permissionName to package ${packageState.packageName}"
)
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index ac437768c5ac..605f6ba00c4f 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -50,7 +50,7 @@ import android.util.ArraySet
import android.util.DebugUtils
import android.util.IndentingPrintWriter
import android.util.IntArray as GrowingIntArray
-import android.util.Log
+import android.util.Slog
import android.util.SparseBooleanArray
import com.android.internal.compat.IPlatformCompat
import com.android.internal.logging.MetricsLogger
@@ -470,7 +470,7 @@ class PermissionService(
val packageState =
packageManagerInternal.getPackageStateInternal(androidPackage.packageName)
if (packageState == null) {
- Log.e(
+ Slog.e(
LOG_TAG, "checkUidPermission: PackageState not found for AndroidPackage" +
" $androidPackage"
)
@@ -587,7 +587,7 @@ class PermissionService(
val packageState = packageManagerLocal.withUnfilteredSnapshot()
.use { it.getPackageState(packageName) }
if (packageState == null) {
- Log.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName")
+ Slog.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName")
return emptySet()
}
@@ -682,7 +682,7 @@ class PermissionService(
if (isDebugEnabled &&
PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) {
val callingUidName = packageManagerInternal.getNameForUid(callingUid)
- Log.i(
+ Slog.i(
LOG_TAG, "$methodName(packageName = $packageName," +
" permissionName = $permissionName" +
(if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") +
@@ -692,7 +692,7 @@ class PermissionService(
}
if (!userManagerInternal.exists(userId)) {
- Log.w(LOG_TAG, "$methodName: Unknown user $userId")
+ Slog.w(LOG_TAG, "$methodName: Unknown user $userId")
return
}
@@ -722,7 +722,7 @@ class PermissionService(
// throws when package exists but isn't visible, we now return in both cases to avoid
// leaking the package existence.
if (androidPackage == null) {
- Log.w(LOG_TAG, "$methodName: Unknown package $packageName")
+ Slog.w(LOG_TAG, "$methodName: Unknown package $packageName")
return
}
@@ -760,7 +760,7 @@ class PermissionService(
PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED,
PackageInstaller.SessionParams.PERMISSION_STATE_DENIED -> {}
else -> {
- Log.w(
+ Slog.w(
LOG_TAG, "setRequestedPermissionStates: Unknown permission state" +
" $permissionState for permission $permissionName"
)
@@ -879,7 +879,7 @@ class PermissionService(
if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) {
if (reportError) {
- Log.e(
+ Slog.e(
LOG_TAG, "$methodName: Cannot change system fixed permission $permissionName" +
" for package $packageName"
)
@@ -889,7 +889,7 @@ class PermissionService(
if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED) && !overridePolicyFixed) {
if (reportError) {
- Log.e(
+ Slog.e(
LOG_TAG, "$methodName: Cannot change policy fixed permission $permissionName" +
" for package $packageName"
)
@@ -899,7 +899,7 @@ class PermissionService(
if (isGranted && oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) {
if (reportError) {
- Log.e(
+ Slog.e(
LOG_TAG, "$methodName: Cannot grant hard-restricted non-exempt permission" +
" $permissionName to package $packageName"
)
@@ -915,7 +915,7 @@ class PermissionService(
)
if (!softRestrictedPermissionPolicy.mayGrantPermission()) {
if (reportError) {
- Log.e(
+ Slog.e(
LOG_TAG, "$methodName: Cannot grant soft-restricted non-exempt permission" +
" $permissionName to package $packageName"
)
@@ -960,7 +960,7 @@ class PermissionService(
override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
if (!userManagerInternal.exists(userId)) {
- Log.w(LOG_TAG, "getPermissionFlags: Unknown user $userId")
+ Slog.w(LOG_TAG, "getPermissionFlags: Unknown user $userId")
return 0
}
@@ -977,14 +977,14 @@ class PermissionService(
val packageState = packageManagerLocal.withFilteredSnapshot()
.use { it.getPackageState(packageName) }
if (packageState == null) {
- Log.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName")
+ Slog.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName")
return 0
}
service.getState {
val permission = with(policy) { getPermissions()[permissionName] }
if (permission == null) {
- Log.w(LOG_TAG, "getPermissionFlags: Unknown permission $permissionName")
+ Slog.w(LOG_TAG, "getPermissionFlags: Unknown permission $permissionName")
return 0
}
@@ -1000,7 +1000,7 @@ class PermissionService(
userId: Int
): Boolean {
if (!userManagerInternal.exists(userId)) {
- Log.w(LOG_TAG, "isPermissionRevokedByPolicy: Unknown user $userId")
+ Slog.w(LOG_TAG, "isPermissionRevokedByPolicy: Unknown user $userId")
return false
}
@@ -1044,7 +1044,7 @@ class PermissionService(
userId: Int
): Boolean {
if (!userManagerInternal.exists(userId)) {
- Log.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId")
+ Slog.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId")
return false
}
@@ -1080,7 +1080,7 @@ class PermissionService(
BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId
)
} catch (e: RemoteException) {
- Log.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" +
+ Slog.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" +
" compatibility change is enabled", e)
false
}
@@ -1111,7 +1111,7 @@ class PermissionService(
PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
)
val callingUidName = packageManagerInternal.getNameForUid(callingUid)
- Log.i(
+ Slog.i(
LOG_TAG, "updatePermissionFlags(packageName = $packageName," +
" permissionName = $permissionName, flagMask = $flagMaskString," +
" flagValues = $flagValuesString, userId = $userId," +
@@ -1120,7 +1120,7 @@ class PermissionService(
}
if (!userManagerInternal.exists(userId)) {
- Log.w(LOG_TAG, "updatePermissionFlags: Unknown user $userId")
+ Slog.w(LOG_TAG, "updatePermissionFlags: Unknown user $userId")
return
}
@@ -1168,7 +1168,7 @@ class PermissionService(
// leaking the package existence.
if (androidPackage == null ||
packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)) {
- Log.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName")
+ Slog.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName")
return
}
@@ -1211,7 +1211,7 @@ class PermissionService(
PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
)
val callingUidName = packageManagerInternal.getNameForUid(callingUid)
- Log.i(
+ Slog.i(
LOG_TAG, "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," +
" flagValues = $flagValuesString, userId = $userId," +
" callingUid = $callingUidName ($callingUid))", RuntimeException()
@@ -1219,7 +1219,7 @@ class PermissionService(
}
if (!userManagerInternal.exists(userId)) {
- Log.w(LOG_TAG, "updatePermissionFlagsForAllApps: Unknown user $userId")
+ Slog.w(LOG_TAG, "updatePermissionFlagsForAllApps: Unknown user $userId")
return
}
@@ -1295,7 +1295,7 @@ class PermissionService(
val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
if (!isPermissionRequested && oldFlags == 0) {
- Log.w(
+ Slog.w(
LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" +
" $packageName"
)
@@ -1316,7 +1316,7 @@ class PermissionService(
Preconditions.checkArgumentNonnegative(userId, "userId cannot be null")
if (!userManagerInternal.exists(userId)) {
- Log.w(LOG_TAG, "AllowlistedRestrictedPermission api: Unknown user $userId")
+ Slog.w(LOG_TAG, "AllowlistedRestrictedPermission api: Unknown user $userId")
return null
}
@@ -1456,7 +1456,7 @@ class PermissionService(
private fun enforceRestrictedPermission(permissionName: String): Boolean {
val permission = service.getState { with(policy) { getPermissions()[permissionName] } }
if (permission == null) {
- Log.w(LOG_TAG, "permission definition for $permissionName does not exist")
+ Slog.w(LOG_TAG, "permission definition for $permissionName does not exist")
return false
}
@@ -1712,7 +1712,7 @@ class PermissionService(
} catch (e: Exception) {
when (e) {
is TimeoutException, is InterruptedException, is ExecutionException -> {
- Log.e(LOG_TAG, "Cannot create permission backup for user $userId", e)
+ Slog.e(LOG_TAG, "Cannot create permission backup for user $userId", e)
null
}
else -> throw e
@@ -2389,7 +2389,7 @@ class PermissionService(
) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED
isInSetup || isInDeferredSetup
} catch (e: Settings.SettingNotFoundException) {
- Log.w(LOG_TAG, "Failed to check if the user is in restore: $e")
+ Slog.w(LOG_TAG, "Failed to check if the user is in restore: $e")
false
}
}
@@ -2412,7 +2412,7 @@ class PermissionService(
try {
listener.onPermissionsChanged(uid)
} catch (e: RemoteException) {
- Log.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e)
+ Slog.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e)
}
}
}
diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
index 2c29332a638b..bd829351941c 100644
--- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
@@ -18,7 +18,7 @@ package com.android.server.permission.access.util
import android.os.FileUtils
import android.util.AtomicFile
-import android.util.Log
+import android.util.Slog
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
@@ -35,12 +35,12 @@ inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) {
} catch (e: FileNotFoundException) {
throw e
} catch (e: Exception) {
- Log.wtf("AccessPersistence", "Failed to read $this", e)
+ Slog.wtf("AccessPersistence", "Failed to read $this", e)
val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy")
try {
AtomicFile(reserveFile).openRead().use(block)
} catch (e2: Exception) {
- Log.e("AccessPersistence", "Failed to read $reserveFile", e2)
+ Slog.e("AccessPersistence", "Failed to read $reserveFile", e2)
throw e
}
}
@@ -62,7 +62,7 @@ inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) {
}
}
} catch (e: Exception) {
- Log.e("AccessPersistence", "Failed to write $reserveFile", e)
+ Slog.e("AccessPersistence", "Failed to write $reserveFile", e)
}
}
diff --git a/services/print/lint-baseline.xml b/services/print/lint-baseline.xml
new file mode 100644
index 000000000000..1bf031a9e289
--- /dev/null
+++ b/services/print/lint-baseline.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+
+ <issue
+ id="SimpleManualPermissionEnforcement"
+ message="IPrintManager permission check should be converted to @EnforcePermission annotation"
+ errorLine1=" mContext.enforceCallingOrSelfPermission("
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/services/print/java/com/android/server/print/PrintManagerService.java"
+ line="401"
+ column="13"/>
+ </issue>
+
+</issues>
diff --git a/services/proguard.flags b/services/proguard.flags
index c31abbb5c0d6..e11e613adb5c 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -42,19 +42,6 @@
-keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface
-keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface
-# Global entities normally kept through explicit Manifest entries
-# TODO(b/210510433): Revisit and consider generating from frameworks/base/core/res/AndroidManifest.xml,
-# by including that manifest with the library rule that triggers optimization.
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.app.Activity
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.app.Service
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.app.backup.BackupAgent
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.content.BroadcastReceiver
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.content.ContentProvider
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.preference.Preference
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.view.View {
- public <init>(...);
-}
-
# Various classes subclassed in or referenced via JNI in ethernet-service
-keep public class android.net.** { *; }
-keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index ad5f0d7233ca..636576492362 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -269,7 +269,9 @@ public class BroadcastQueueTest {
deliverRes = res;
break;
}
+ res.setPendingStart(true);
mHandlerThread.getThreadHandler().post(() -> {
+ res.setPendingStart(false);
synchronized (mAms) {
switch (behavior) {
case SUCCESS:
@@ -281,6 +283,10 @@ public class BroadcastQueueTest {
mActiveProcesses.remove(deliverRes);
mQueue.onApplicationTimeoutLocked(deliverRes);
break;
+ case KILLED_WITHOUT_NOTIFY:
+ mActiveProcesses.remove(res);
+ res.setKilled(true);
+ break;
default:
throw new UnsupportedOperationException();
}
@@ -310,6 +316,7 @@ public class BroadcastQueueTest {
mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
mConstants.TIMEOUT = 100;
mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
+ mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
@@ -381,6 +388,8 @@ public class BroadcastQueueTest {
FAIL_TIMEOUT_PREDECESSOR,
/** Process fails by immediately returning null */
FAIL_NULL,
+ /** Process is killed without reporting to BroadcastQueue */
+ KILLED_WITHOUT_NOTIFY,
}
private enum ProcessBehavior {
@@ -522,6 +531,11 @@ public class BroadcastQueueTest {
return info;
}
+ static BroadcastFilter withPriority(BroadcastFilter filter, int priority) {
+ filter.setPriority(priority);
+ return filter;
+ }
+
static ResolveInfo makeManifestReceiver(String packageName, String name) {
return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
}
@@ -1261,6 +1275,46 @@ public class BroadcastQueueTest {
new ComponentName(PACKAGE_GREEN, CLASS_GREEN));
}
+ /**
+ * Verify that when BroadcastQueue doesn't get notified when a process gets killed, it
+ * doesn't get stuck.
+ */
+ @Test
+ public void testKillWithoutNotify() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+
+ mNextProcessStartBehavior.set(ProcessStartBehavior.KILLED_WITHOUT_NOTIFY);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+ withPriority(makeRegisteredReceiver(receiverBlueApp), 5),
+ withPriority(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), 0))));
+
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE))));
+
+ waitForIdle();
+ final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+ getUidForPackage(PACKAGE_YELLOW));
+ final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE,
+ getUidForPackage(PACKAGE_ORANGE));
+
+ if (mImpl == Impl.MODERN) {
+ // Modern queue does not retry sending a broadcast once any broadcast delivery fails.
+ assertNull(receiverGreenApp);
+ } else {
+ verifyScheduleReceiver(times(1), receiverGreenApp, airplane);
+ }
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+ verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
+ verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
+ }
+
@Test
public void testCold_Success() throws Exception {
doCold(ProcessStartBehavior.SUCCESS);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 154aa7d4e78e..4268eb924225 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -231,7 +231,14 @@ public class AuthSessionTest {
public void testMultiAuth_singleSensor_fingerprintSensorStartsAfterDialogAnimationCompletes()
throws Exception {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
- testMultiAuth_fingerprintSensorStartsAfterUINotifies();
+ testMultiAuth_fingerprintSensorStartsAfterUINotifies(true /* startFingerprintNow */);
+ }
+
+ @Test
+ public void testMultiAuth_singleSensor_fingerprintSensorDoesNotStartAfterDialogAnimationCompletes()
+ throws Exception {
+ setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
+ testMultiAuth_fingerprintSensorStartsAfterUINotifies(false /* startFingerprintNow */);
}
@Test
@@ -239,10 +246,18 @@ public class AuthSessionTest {
throws Exception {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class));
- testMultiAuth_fingerprintSensorStartsAfterUINotifies();
+ testMultiAuth_fingerprintSensorStartsAfterUINotifies(true /* startFingerprintNow */);
}
- public void testMultiAuth_fingerprintSensorStartsAfterUINotifies()
+ @Test
+ public void testMultiAuth_fingerprintSensorDoesNotStartAfterDialogAnimationCompletes()
+ throws Exception {
+ setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
+ setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class));
+ testMultiAuth_fingerprintSensorStartsAfterUINotifies(false /* startFingerprintNow */);
+ }
+
+ public void testMultiAuth_fingerprintSensorStartsAfterUINotifies(boolean startFingerprintNow)
throws Exception {
final long operationId = 123;
final int userId = 10;
@@ -282,13 +297,21 @@ public class AuthSessionTest {
// fingerprint sensor does not start even if all cookies are received
assertEquals(STATE_AUTH_STARTED, session.getState());
verify(mStatusBarService).showAuthenticationDialog(any(), any(), any(),
- anyBoolean(), anyBoolean(), anyInt(), anyLong(), any(), anyLong(), anyInt());
+ anyBoolean(), anyBoolean(), anyInt(), anyLong(), any(), anyLong());
// Notify AuthSession that the UI is shown. Then, fingerprint sensor should be started.
- session.onDialogAnimatedIn();
+ session.onDialogAnimatedIn(startFingerprintNow);
assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
- assertEquals(BiometricSensor.STATE_AUTHENTICATING,
+ assertEquals(startFingerprintNow ? BiometricSensor.STATE_AUTHENTICATING
+ : BiometricSensor.STATE_COOKIE_RETURNED,
session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
+
+ // start fingerprint sensor if it was delayed
+ if (!startFingerprintNow) {
+ session.onStartFingerprint();
+ assertEquals(BiometricSensor.STATE_AUTHENTICATING,
+ session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
+ }
}
@Test
@@ -316,14 +339,14 @@ public class AuthSessionTest {
verify(impl, never()).startPreparedClient(anyInt());
// First invocation should start the client monitor.
- session.onDialogAnimatedIn();
+ session.onDialogAnimatedIn(true /* startFingerprintNow */);
assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
verify(impl).startPreparedClient(anyInt());
// Subsequent invocations should not start the client monitor again.
- session.onDialogAnimatedIn();
- session.onDialogAnimatedIn();
- session.onDialogAnimatedIn();
+ session.onDialogAnimatedIn(true /* startFingerprintNow */);
+ session.onDialogAnimatedIn(false /* startFingerprintNow */);
+ session.onDialogAnimatedIn(true /* startFingerprintNow */);
assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
verify(impl, times(1)).startPreparedClient(anyInt());
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 520e1c84c74e..67be37616d5f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -18,7 +18,7 @@ package com.android.server.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
@@ -311,8 +311,7 @@ public class BiometricServiceTest {
anyInt() /* userId */,
anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- eq(TEST_REQUEST_ID),
- eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
+ eq(TEST_REQUEST_ID));
}
@Test
@@ -397,8 +396,7 @@ public class BiometricServiceTest {
anyInt() /* userId */,
anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- eq(TEST_REQUEST_ID),
- eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
+ eq(TEST_REQUEST_ID));
}
@Test
@@ -516,7 +514,7 @@ public class BiometricServiceTest {
assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState());
// startPreparedClient invoked
- mBiometricService.mAuthSession.onDialogAnimatedIn();
+ mBiometricService.mAuthSession.onDialogAnimatedIn(true /* startFingerprintNow */);
verify(mBiometricService.mSensors.get(0).impl)
.startPreparedClient(cookieCaptor.getValue());
@@ -530,8 +528,7 @@ public class BiometricServiceTest {
anyInt() /* userId */,
anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- eq(TEST_REQUEST_ID),
- eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
+ eq(TEST_REQUEST_ID));
// Hardware authenticated
final byte[] HAT = generateRandomHAT();
@@ -587,8 +584,7 @@ public class BiometricServiceTest {
anyInt() /* userId */,
anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- eq(TEST_REQUEST_ID),
- eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
+ eq(TEST_REQUEST_ID));
}
@Test
@@ -752,8 +748,7 @@ public class BiometricServiceTest {
anyInt() /* userId */,
anyLong() /* operationId */,
anyString(),
- anyLong() /* requestId */,
- eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
+ anyLong() /* requestId */);
}
@Test
@@ -854,8 +849,7 @@ public class BiometricServiceTest {
anyInt() /* userId */,
anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- eq(TEST_REQUEST_ID),
- eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
+ eq(TEST_REQUEST_ID));
}
@Test
@@ -935,8 +929,7 @@ public class BiometricServiceTest {
anyInt() /* userId */,
anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- eq(TEST_REQUEST_ID),
- eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
+ eq(TEST_REQUEST_ID));
}
@Test
@@ -1432,8 +1425,7 @@ public class BiometricServiceTest {
anyInt() /* userId */,
anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- eq(requestId),
- eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
+ eq(requestId));
// Requesting strong and credential, when credential is setup
resetReceivers();
@@ -1456,8 +1448,7 @@ public class BiometricServiceTest {
anyInt() /* userId */,
anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- eq(requestId),
- eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
+ eq(requestId));
// Un-downgrading the authenticator allows successful strong auth
for (BiometricSensor sensor : mBiometricService.mSensors) {
@@ -1482,8 +1473,7 @@ public class BiometricServiceTest {
anyInt() /* userId */,
anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- eq(requestId),
- eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
+ eq(requestId));
}
@Test(expected = IllegalStateException.class)
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 25bd9bcf8d5c..be9f52e00b16 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -17,12 +17,14 @@
package com.android.server.biometrics.sensors.face.aidl;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -41,6 +43,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -82,6 +85,10 @@ public class SensorTest {
private AuthSessionCoordinator mAuthSessionCoordinator;
@Mock
FaceProvider mFaceProvider;
+ @Mock
+ BaseClientMonitor mClientMonitor;
+ @Mock
+ AidlSession mCurrentSession;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -161,6 +168,39 @@ public class SensorTest {
assertNull(sensor.getSessionForUser(USER_ID));
}
+ @Test
+ public void onBinderDied_cancelNonInterruptableClient() {
+ mLooper.dispatchAll();
+
+ when(mCurrentSession.getUserId()).thenReturn(USER_ID);
+ when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID);
+ when(mClientMonitor.isInterruptable()).thenReturn(false);
+
+ final SensorProps sensorProps = new SensorProps();
+ sensorProps.commonProps = new CommonProps();
+ sensorProps.commonProps.sensorId = 1;
+ final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+ sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
+ sensorProps.commonProps.maxEnrollmentsPerUser, null,
+ sensorProps.sensorType, sensorProps.supportsDetectInteraction,
+ sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
+ final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
+ internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession);
+ mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
+ sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
+ USER_ID, mHalCallback);
+
+ mScheduler.scheduleClientMonitor(mClientMonitor);
+
+ assertNotNull(mScheduler.getCurrentClient());
+
+ sensor.onBinderDied();
+
+ verify(mClientMonitor).cancel();
+ assertNull(sensor.getSessionForUser(USER_ID));
+ assertNull(mScheduler.getCurrentClient());
+ }
+
private void verifyNotLocked() {
assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID));
verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 0c1346696b58..15d7601dde34 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -17,17 +17,23 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.face.SensorProps;
import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Handler;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -37,11 +43,13 @@ import androidx.test.filters.SmallTest;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import org.junit.Before;
import org.junit.Test;
@@ -76,6 +84,14 @@ public class SensorTest {
private BiometricContext mBiometricContext;
@Mock
private AuthSessionCoordinator mAuthSessionCoordinator;
+ @Mock
+ FingerprintProvider mFingerprintProvider;
+ @Mock
+ GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+ @Mock
+ private AidlSession mCurrentSession;
+ @Mock
+ private BaseClientMonitor mClientMonitor;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -130,6 +146,40 @@ public class SensorTest {
verifyNotLocked();
}
+ @Test
+ public void onBinderDied_cancelNonInterruptableClient() {
+ mLooper.dispatchAll();
+
+ when(mCurrentSession.getUserId()).thenReturn(USER_ID);
+ when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID);
+ when(mClientMonitor.isInterruptable()).thenReturn(false);
+
+ final SensorProps sensorProps = new SensorProps();
+ sensorProps.commonProps = new CommonProps();
+ sensorProps.commonProps.sensorId = 1;
+ final FingerprintSensorPropertiesInternal internalProp = new
+ FingerprintSensorPropertiesInternal(
+ sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
+ sensorProps.commonProps.maxEnrollmentsPerUser, null,
+ sensorProps.sensorType, false /* resetLockoutRequiresHardwareAuthToken */);
+ final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext,
+ null /* handler */, internalProp, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession);
+ mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
+ sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
+ USER_ID, mHalCallback);
+
+ mScheduler.scheduleClientMonitor(mClientMonitor);
+
+ assertNotNull(mScheduler.getCurrentClient());
+
+ sensor.onBinderDied();
+
+ verify(mClientMonitor).cancel();
+ assertNull(sensor.getSessionForUser(USER_ID));
+ assertNull(mScheduler.getCurrentClient());
+ }
+
private void verifyNotLocked() {
assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID));
verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
new file mode 100644
index 000000000000..ba9956a63dca
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+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.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test for {@link ContentProtectionBlocklistManager}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:
+ * com.android.server.contentprotection.ContentProtectionBlocklistManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionBlocklistManagerTest {
+
+ private static final String FIRST_PACKAGE_NAME = "com.test.first.package.name";
+
+ private static final String SECOND_PACKAGE_NAME = "com.test.second.package.name";
+
+ private static final String UNLISTED_PACKAGE_NAME = "com.test.unlisted.package.name";
+
+ private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
+ "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
+
+ private static final PackageInfo PACKAGE_INFO = new PackageInfo();
+
+ private static final List<String> LINES =
+ ImmutableList.of(FIRST_PACKAGE_NAME, SECOND_PACKAGE_NAME);
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock private ContentProtectionPackageManager mMockContentProtectionPackageManager;
+
+ private final List<String> mReadRawFiles = new ArrayList<>();
+
+ private ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+
+ @Before
+ public void setup() {
+ mContentProtectionBlocklistManager = new TestContentProtectionBlocklistManager();
+ }
+
+ @Test
+ public void isAllowed_blocklistNotLoaded() {
+ boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ assertThat(mReadRawFiles).isEmpty();
+ verifyZeroInteractions(mMockContentProtectionPackageManager);
+ }
+
+ @Test
+ public void isAllowed_inBlocklist() {
+ mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+
+ boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verifyZeroInteractions(mMockContentProtectionPackageManager);
+ }
+
+ @Test
+ public void isAllowed_packageInfoNotFound() {
+ mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+ when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+ .thenReturn(null);
+
+ boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionPackageManager, never())
+ .hasRequestedInternetPermissions(any());
+ verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
+ verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+ }
+
+ @Test
+ public void isAllowed_notRequestedInternet() {
+ mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+ when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+ .thenReturn(PACKAGE_INFO);
+ when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+ .thenReturn(false);
+
+ boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
+ verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+ }
+
+ @Test
+ public void isAllowed_systemApp() {
+ mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+ when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+ .thenReturn(PACKAGE_INFO);
+ when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+ .thenReturn(true);
+ when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
+
+ boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+ }
+
+ @Test
+ public void isAllowed_updatedSystemApp() {
+ mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+ when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+ .thenReturn(PACKAGE_INFO);
+ when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+ .thenReturn(true);
+ when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
+ when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
+ .thenReturn(true);
+
+ boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ }
+
+ @Test
+ public void isAllowed_allowed() {
+ mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+ when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+ .thenReturn(PACKAGE_INFO);
+ when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+ .thenReturn(true);
+ when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(false);
+ when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
+ .thenReturn(false);
+
+ boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+ assertThat(actual).isTrue();
+ }
+
+ @Test
+ public void updateBlocklist_negativeSize() {
+ mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ -1);
+ assertThat(mReadRawFiles).isEmpty();
+
+ mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+ verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
+ }
+
+ @Test
+ public void updateBlocklist_zeroSize() {
+ mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 0);
+ assertThat(mReadRawFiles).isEmpty();
+
+ mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+ verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
+ }
+
+ @Test
+ public void updateBlocklist_positiveSize_belowTotal() {
+ mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 1);
+ assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
+
+ mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+ mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
+
+ verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
+ verify(mMockContentProtectionPackageManager).getPackageInfo(SECOND_PACKAGE_NAME);
+ }
+
+ @Test
+ public void updateBlocklist_positiveSize_aboveTotal() {
+ mContentProtectionBlocklistManager.updateBlocklist(LINES.size() + 1);
+ assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
+
+ mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+ mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
+
+ verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
+ verify(mMockContentProtectionPackageManager, never()).getPackageInfo(SECOND_PACKAGE_NAME);
+ }
+
+ private final class TestContentProtectionBlocklistManager
+ extends ContentProtectionBlocklistManager {
+
+ TestContentProtectionBlocklistManager() {
+ super(mMockContentProtectionPackageManager);
+ }
+
+ @Override
+ protected List<String> readLinesFromRawFile(@NonNull String filename) {
+ mReadRawFiles.add(filename);
+ return LINES;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
index 2b49b8ab64d2..a242cdec89db 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -23,6 +23,8 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import android.app.PropertyInvalidatedCache;
import android.app.admin.DevicePolicyManager;
@@ -38,8 +40,9 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.nio.ByteBuffer;
-/** Test setting a lockscreen credential and then verify it under USER_FRP */
+/** Tests that involve the Factory Reset Protection (FRP) credential. */
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -148,4 +151,68 @@ public class LockscreenFrpTest extends BaseLockSettingsServiceTests {
mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
.getResponseCode());
}
+
+ // The FRP block that gets written by the current version of Android must still be accepted by
+ // old versions of Android. This test tries to detect non-forward-compatible changes in
+ // PasswordData#toBytes(), which would break that.
+ @Test
+ public void testFrpBlock_isForwardsCompatible() {
+ mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+ PersistentData data = mStorage.readPersistentDataBlock();
+ ByteBuffer buffer = ByteBuffer.wrap(data.payload);
+
+ final int credentialType = buffer.getInt();
+ assertEquals(CREDENTIAL_TYPE_PIN, credentialType);
+
+ final byte scryptLogN = buffer.get();
+ assertTrue(scryptLogN >= 0);
+
+ final byte scryptLogR = buffer.get();
+ assertTrue(scryptLogR >= 0);
+
+ final byte scryptLogP = buffer.get();
+ assertTrue(scryptLogP >= 0);
+
+ final int saltLength = buffer.getInt();
+ assertTrue(saltLength > 0);
+ final byte[] salt = new byte[saltLength];
+ buffer.get(salt);
+
+ final int passwordHandleLength = buffer.getInt();
+ assertTrue(passwordHandleLength > 0);
+ final byte[] passwordHandle = new byte[passwordHandleLength];
+ buffer.get(passwordHandle);
+ }
+
+ @Test
+ public void testFrpBlock_inBadAndroid14FormatIsAutomaticallyFixed() {
+ mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+
+ // Write a "bad" FRP block with PasswordData beginning with the bytes [0, 2].
+ byte[] badPasswordData = new byte[] {
+ 0, 2, /* version 2 */
+ 0, 3, /* CREDENTIAL_TYPE_PIN */
+ 11, /* scryptLogN */
+ 22, /* scryptLogR */
+ 33, /* scryptLogP */
+ 0, 0, 0, 5, /* salt.length */
+ 1, 2, -1, -2, 55, /* salt */
+ 0, 0, 0, 6, /* passwordHandle.length */
+ 2, 3, -2, -3, 44, 1, /* passwordHandle */
+ 0, 0, 0, 6, /* pinLength */
+ };
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_GATEKEEPER, PRIMARY_USER_ID, 0,
+ badPasswordData);
+
+ // Execute the code that should fix the FRP block.
+ assertFalse(mStorage.getBoolean("migrated_frp2", false, 0));
+ mService.migrateOldDataAfterSystemReady();
+ assertTrue(mStorage.getBoolean("migrated_frp2", false, 0));
+
+ // Verify that the FRP block has been fixed.
+ PersistentData data = mStorage.readPersistentDataBlock();
+ assertEquals(PersistentData.TYPE_SP_GATEKEEPER, data.type);
+ ByteBuffer buffer = ByteBuffer.wrap(data.payload);
+ assertEquals(CREDENTIAL_TYPE_PIN, buffer.getInt());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index bfb6b0f1b6c7..ce0347dbe4ac 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -23,6 +23,7 @@ import static android.content.pm.UserInfo.FLAG_PRIMARY;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
import static org.junit.Assert.assertEquals;
@@ -625,11 +626,9 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
}
@Test
- public void testPasswordDataV2VersionCredentialTypePin_deserialize() {
- // Test that we can deserialize existing PasswordData and don't inadvertently change the
- // wire format.
+ public void testDeserializePasswordData_forPinWithLengthAvailable() {
byte[] serialized = new byte[] {
- 0, 2, 0, 2, /* CREDENTIAL_TYPE_PASSWORD_OR_PIN */
+ 0, 0, 0, 3, /* CREDENTIAL_TYPE_PIN */
11, /* scryptLogN */
22, /* scryptLogR */
33, /* scryptLogP */
@@ -637,25 +636,24 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
1, 2, -1, -2, 55, /* salt */
0, 0, 0, 6, /* passwordHandle.length */
2, 3, -2, -3, 44, 1, /* passwordHandle */
- 0, 0, 0, 5, /* pinLength */
+ 0, 0, 0, 6, /* pinLength */
};
+ assertFalse(PasswordData.isBadFormatFromAndroid14Beta(serialized));
PasswordData deserialized = PasswordData.fromBytes(serialized);
assertEquals(11, deserialized.scryptLogN);
assertEquals(22, deserialized.scryptLogR);
assertEquals(33, deserialized.scryptLogP);
- assertEquals(5, deserialized.pinLength);
- assertEquals(CREDENTIAL_TYPE_PASSWORD_OR_PIN, deserialized.credentialType);
+ assertEquals(CREDENTIAL_TYPE_PIN, deserialized.credentialType);
assertArrayEquals(PAYLOAD, deserialized.salt);
assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
+ assertEquals(6, deserialized.pinLength);
}
@Test
- public void testPasswordDataV2VersionNegativePinLengthNoCredential_deserialize() {
- // Test that we can deserialize existing PasswordData and don't inadvertently change the
- // wire format.
+ public void testDeserializePasswordData_forPinWithLengthExplicitlyUnavailable() {
byte[] serialized = new byte[] {
- 0, 2, -1, -1, /* CREDENTIAL_TYPE_NONE */
+ 0, 0, 0, 3, /* CREDENTIAL_TYPE_PIN */
11, /* scryptLogN */
22, /* scryptLogR */
33, /* scryptLogP */
@@ -663,23 +661,53 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
1, 2, -1, -2, 55, /* salt */
0, 0, 0, 6, /* passwordHandle.length */
2, 3, -2, -3, 44, 1, /* passwordHandle */
- -1, -1, -1, -2, /* pinLength */
+ -1, -1, -1, -1, /* pinLength */
};
PasswordData deserialized = PasswordData.fromBytes(serialized);
assertEquals(11, deserialized.scryptLogN);
assertEquals(22, deserialized.scryptLogR);
assertEquals(33, deserialized.scryptLogP);
- assertEquals(-2, deserialized.pinLength);
- assertEquals(CREDENTIAL_TYPE_NONE, deserialized.credentialType);
+ assertEquals(CREDENTIAL_TYPE_PIN, deserialized.credentialType);
assertArrayEquals(PAYLOAD, deserialized.salt);
assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
+ assertEquals(PIN_LENGTH_UNAVAILABLE, deserialized.pinLength);
}
@Test
- public void testPasswordDataV1VersionNoCredential_deserialize() {
- // Test that we can deserialize existing PasswordData and don't inadvertently change the
- // wire format.
+ public void testDeserializePasswordData_forPinWithVersionNumber() {
+ // Test deserializing a PasswordData that has a version number in the first two bytes.
+ // Files like this were created by some Android 14 beta versions. This version number was a
+ // mistake and should be ignored by the deserializer.
+ byte[] serialized = new byte[] {
+ 0, 2, /* version 2 */
+ 0, 3, /* CREDENTIAL_TYPE_PIN */
+ 11, /* scryptLogN */
+ 22, /* scryptLogR */
+ 33, /* scryptLogP */
+ 0, 0, 0, 5, /* salt.length */
+ 1, 2, -1, -2, 55, /* salt */
+ 0, 0, 0, 6, /* passwordHandle.length */
+ 2, 3, -2, -3, 44, 1, /* passwordHandle */
+ 0, 0, 0, 6, /* pinLength */
+ };
+ assertTrue(PasswordData.isBadFormatFromAndroid14Beta(serialized));
+ PasswordData deserialized = PasswordData.fromBytes(serialized);
+
+ assertEquals(11, deserialized.scryptLogN);
+ assertEquals(22, deserialized.scryptLogR);
+ assertEquals(33, deserialized.scryptLogP);
+ assertEquals(CREDENTIAL_TYPE_PIN, deserialized.credentialType);
+ assertArrayEquals(PAYLOAD, deserialized.salt);
+ assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
+ assertEquals(6, deserialized.pinLength);
+ }
+
+ @Test
+ public void testDeserializePasswordData_forNoneCred() {
+ // Test that a PasswordData that uses CREDENTIAL_TYPE_NONE and lacks the PIN length field
+ // can be deserialized. Files like this were created by Android 13 and earlier. Android 14
+ // and later no longer create PasswordData for CREDENTIAL_TYPE_NONE.
byte[] serialized = new byte[] {
-1, -1, -1, -1, /* CREDENTIAL_TYPE_NONE */
11, /* scryptLogN */
@@ -695,16 +723,17 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
assertEquals(11, deserialized.scryptLogN);
assertEquals(22, deserialized.scryptLogR);
assertEquals(33, deserialized.scryptLogP);
- assertEquals(PIN_LENGTH_UNAVAILABLE, deserialized.pinLength);
assertEquals(CREDENTIAL_TYPE_NONE, deserialized.credentialType);
assertArrayEquals(PAYLOAD, deserialized.salt);
assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
+ assertEquals(PIN_LENGTH_UNAVAILABLE, deserialized.pinLength);
}
@Test
- public void testPasswordDataV1VersionCredentialTypePin_deserialize() {
- // Test that we can deserialize existing PasswordData and don't inadvertently change the
- // wire format.
+ public void testDeserializePasswordData_forPasswordOrPin() {
+ // Test that a PasswordData that uses CREDENTIAL_TYPE_PASSWORD_OR_PIN and lacks the PIN
+ // length field can be deserialized. Files like this were created by Android 10 and
+ // earlier. Android 11 eliminated CREDENTIAL_TYPE_PASSWORD_OR_PIN.
byte[] serialized = new byte[] {
0, 0, 0, 2, /* CREDENTIAL_TYPE_PASSWORD_OR_PIN */
11, /* scryptLogN */
@@ -720,10 +749,10 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
assertEquals(11, deserialized.scryptLogN);
assertEquals(22, deserialized.scryptLogR);
assertEquals(33, deserialized.scryptLogP);
- assertEquals(PIN_LENGTH_UNAVAILABLE, deserialized.pinLength);
assertEquals(CREDENTIAL_TYPE_PASSWORD_OR_PIN, deserialized.credentialType);
assertArrayEquals(PAYLOAD, deserialized.salt);
assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
+ assertEquals(PIN_LENGTH_UNAVAILABLE, deserialized.pinLength);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index ee4b839dda5a..b2dad7348525 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -23,6 +23,7 @@ import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -55,6 +56,7 @@ import com.android.internal.os.LongArrayMultiStateCounter;
import com.android.internal.os.PowerProfile;
import com.google.common.collect.ImmutableList;
+import com.google.common.truth.LongSubject;
import org.junit.Before;
import org.junit.Test;
@@ -77,6 +79,9 @@ public class BatteryStatsImplTest {
private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
@Mock
private PowerProfile mPowerProfile;
+ @Mock
+ private KernelWakelockReader mKernelWakelockReader;
+ private KernelWakelockStats mKernelWakelockStats = new KernelWakelockStats();
private final MockClock mMockClock = new MockClock();
private MockBatteryStatsImpl mBatteryStatsImpl;
@@ -89,10 +94,13 @@ public class BatteryStatsImplTest {
when(mKernelUidCpuFreqTimeReader.readFreqs(any())).thenReturn(CPU_FREQS);
when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
+ when(mKernelWakelockReader.readKernelWakelockStats(
+ any(KernelWakelockStats.class))).thenReturn(mKernelWakelockStats);
mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock)
.setPowerProfile(mPowerProfile)
.setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
- .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
+ .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader)
+ .setKernelWakelockReader(mKernelWakelockReader);
}
@Test
@@ -559,6 +567,48 @@ public class BatteryStatsImplTest {
}
@Test
+ public void kernelWakelocks() {
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+
+ mKernelWakelockStats.put("lock1", new KernelWakelockStats.Entry(42, 1000, 314, 0));
+ mKernelWakelockStats.put("lock2", new KernelWakelockStats.Entry(6, 2000, 0, 0));
+
+ mMockClock.realtime = 5000;
+
+ // The fist call makes a snapshot of the initial state of the wakelocks
+ mBatteryStatsImpl.updateKernelWakelocksLocked(mMockClock.realtime * 1000);
+
+ assertThat(mBatteryStatsImpl.getKernelWakelockStats()).hasSize(2);
+
+ mMockClock.realtime += 2000;
+
+ assertThatKernelWakelockTotalTime("lock1").isEqualTo(314); // active
+ assertThatKernelWakelockTotalTime("lock2").isEqualTo(0); // inactive
+
+ mKernelWakelockStats.put("lock1", new KernelWakelockStats.Entry(43, 1100, 414, 0));
+ mKernelWakelockStats.put("lock2", new KernelWakelockStats.Entry(6, 2222, 0, 0));
+
+ mMockClock.realtime += 3000;
+
+ // Compute delta from the initial snapshot
+ mBatteryStatsImpl.updateKernelWakelocksLocked(mMockClock.realtime * 1000);
+
+ mMockClock.realtime += 4000;
+
+ assertThatKernelWakelockTotalTime("lock1").isEqualTo(414);
+
+ // Wake lock not active. Expect relative total time as reported by Kernel:
+ // 2_222 - 2_000 = 222
+ assertThatKernelWakelockTotalTime("lock2").isEqualTo(222);
+ }
+
+ private LongSubject assertThatKernelWakelockTotalTime(String name) {
+ return assertWithMessage("Kernel wakelock " + name + " at " + mMockClock.realtime)
+ .that(mBatteryStatsImpl.getKernelWakelockStats().get(name)
+ .getTotalTimeLocked(mMockClock.realtime * 1000, 0));
+ }
+
+ @Test
public void testGetBluetoothBatteryStats() {
when(mPowerProfile.getAveragePower(
PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE)).thenReturn(3.0);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 6b21eb0ea729..e6454e4ce040 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -32,7 +32,6 @@ import static org.mockito.Mockito.mock;
import android.app.ActivityManager;
import android.app.usage.NetworkStatsManager;
-import android.hardware.radio.V1_5.AccessNetwork;
import android.os.BatteryStats;
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.Uid.Sensor;
@@ -1727,27 +1726,38 @@ public class BatteryStatsNoteTest extends TestCase {
}
}
- specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.UNKNOWN,
+ specificInfoList.add(new ActivityStatsTechSpecificInfo(
+ AccessNetworkConstants.AccessNetworkType.UNKNOWN,
ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
- specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.GERAN,
+ specificInfoList.add(new ActivityStatsTechSpecificInfo(
+ AccessNetworkConstants.AccessNetworkType.GERAN,
ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
- specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.UTRAN,
+ specificInfoList.add(new ActivityStatsTechSpecificInfo(
+ AccessNetworkConstants.AccessNetworkType.UTRAN,
ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
- specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.EUTRAN,
+ specificInfoList.add(new ActivityStatsTechSpecificInfo(
+ AccessNetworkConstants.AccessNetworkType.EUTRAN,
ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
- specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.CDMA2000,
+ specificInfoList.add(new ActivityStatsTechSpecificInfo(
+ AccessNetworkConstants.AccessNetworkType.CDMA2000,
ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
- specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.IWLAN,
+ specificInfoList.add(new ActivityStatsTechSpecificInfo(
+ AccessNetworkConstants.AccessNetworkType.IWLAN,
ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
- specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+ specificInfoList.add(new ActivityStatsTechSpecificInfo(
+ AccessNetworkConstants.AccessNetworkType.NGRAN,
ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
- specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+ specificInfoList.add(new ActivityStatsTechSpecificInfo(
+ AccessNetworkConstants.AccessNetworkType.NGRAN,
ServiceState.FREQUENCY_RANGE_LOW, new int[txLevelCount], 0));
- specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+ specificInfoList.add(new ActivityStatsTechSpecificInfo(
+ AccessNetworkConstants.AccessNetworkType.NGRAN,
ServiceState.FREQUENCY_RANGE_MID, new int[txLevelCount], 0));
- specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+ specificInfoList.add(new ActivityStatsTechSpecificInfo(
+ AccessNetworkConstants.AccessNetworkType.NGRAN,
ServiceState.FREQUENCY_RANGE_HIGH, new int[txLevelCount], 0));
- specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+ specificInfoList.add(new ActivityStatsTechSpecificInfo(
+ AccessNetworkConstants.AccessNetworkType.NGRAN,
ServiceState.FREQUENCY_RANGE_MMWAVE, new int[txLevelCount], 0));
final ActivityStatsTechSpecificInfo[] specificInfos = specificInfoList.toArray(
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java b/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
index 70643d999a4e..c0f3c775ffe5 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
@@ -43,8 +43,13 @@ public class KernelWakelockReaderTest extends TestCase {
}
public ProcFileBuilder addLine(String name, int count, long timeMillis) {
+ return addLine(name, count, timeMillis, 0);
+ }
+
+ public ProcFileBuilder addLine(String name, int count, long timeMillis, long activeTimeMs) {
ensureHeader();
- mStringBuilder.append(name).append("\t").append(count).append("\t0\t0\t0\t0\t")
+ mStringBuilder.append(name).append("\t").append(count).append("\t0\t0\t0\t")
+ .append(activeTimeMs).append("\t")
.append(timeMillis).append("\t0\t0\t0\n");
return this;
}
@@ -66,10 +71,19 @@ public class KernelWakelockReaderTest extends TestCase {
* @return the created WakeLockInfo object.
*/
private WakeLockInfo createWakeLockInfo(String name, int activeCount, long totalTime) {
+ return createWakeLockInfo(name, activeCount, totalTime, 0);
+ }
+
+ private WakeLockInfo createWakeLockInfo(String name, int activeCount, long totalTime,
+ long activeTimeMs) {
WakeLockInfo info = new WakeLockInfo();
info.name = name;
info.activeCount = activeCount;
info.totalTime = totalTime;
+ info.activeTime = activeTimeMs;
+ if (activeTimeMs != 0) {
+ info.isActive = true;
+ }
return info;
}
@@ -118,7 +132,7 @@ public class KernelWakelockReaderTest extends TestCase {
@SmallTest
public void testOneWakelock() throws Exception {
byte[] buffer = new ProcFileBuilder()
- .addLine("Wakelock", 34, 123) // Milliseconds
+ .addLine("Wakelock", 34, 123, 456) // Milliseconds
.getBytes();
KernelWakelockStats staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true,
@@ -129,8 +143,9 @@ public class KernelWakelockReaderTest extends TestCase {
assertTrue(staleStats.containsKey("Wakelock"));
KernelWakelockStats.Entry entry = staleStats.get("Wakelock");
- assertEquals(34, entry.mCount);
- assertEquals(123 * 1000, entry.mTotalTime); // Microseconds
+ assertEquals(34, entry.count);
+ assertEquals(123 * 1000, entry.totalTimeUs); // Microseconds
+ assertEquals(456 * 1000, entry.activeTimeUs); // Microseconds
}
@SmallTest
@@ -163,8 +178,8 @@ public class KernelWakelockReaderTest extends TestCase {
assertTrue(staleStats.containsKey("Wakelock"));
KernelWakelockStats.Entry entry = staleStats.get("Wakelock");
- assertEquals(2, entry.mCount);
- assertEquals(20 * 1000, entry.mTotalTime); // Microseconds
+ assertEquals(2, entry.count);
+ assertEquals(20 * 1000, entry.totalTimeUs); // Microseconds
}
@SmallTest
@@ -203,7 +218,7 @@ public class KernelWakelockReaderTest extends TestCase {
@SmallTest
public void testOneWakeLockInfo() {
WakeLockInfo[] wlStats = new WakeLockInfo[1];
- wlStats[0] = createWakeLockInfo("WakeLock", 20, 1000); // Milliseconds
+ wlStats[0] = createWakeLockInfo("WakeLock", 20, 1000, 500); // Milliseconds
KernelWakelockStats staleStats = mReader.updateWakelockStats(wlStats,
new KernelWakelockStats());
@@ -213,8 +228,9 @@ public class KernelWakelockReaderTest extends TestCase {
assertTrue(staleStats.containsKey("WakeLock"));
KernelWakelockStats.Entry entry = staleStats.get("WakeLock");
- assertEquals(20, entry.mCount);
- assertEquals(1000 * 1000, entry.mTotalTime); // Microseconds
+ assertEquals(20, entry.count);
+ assertEquals(1000 * 1000, entry.totalTimeUs); // Microseconds
+ assertEquals(500 * 1000, entry.activeTimeUs); // Microseconds
}
@SmallTest
@@ -232,12 +248,12 @@ public class KernelWakelockReaderTest extends TestCase {
assertTrue(staleStats.containsKey("WakeLock2"));
KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1");
- assertEquals(10, entry1.mCount);
- assertEquals(1000 * 1000, entry1.mTotalTime); // Microseconds
+ assertEquals(10, entry1.count);
+ assertEquals(1000 * 1000, entry1.totalTimeUs); // Microseconds
KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2");
- assertEquals(20, entry2.mCount);
- assertEquals(2000 * 1000, entry2.mTotalTime); // Microseconds
+ assertEquals(20, entry2.count);
+ assertEquals(2000 * 1000, entry2.totalTimeUs); // Microseconds
}
@SmallTest
@@ -253,8 +269,8 @@ public class KernelWakelockReaderTest extends TestCase {
assertTrue(staleStats.containsKey("WakeLock1"));
KernelWakelockStats.Entry entry = staleStats.get("WakeLock1");
- assertEquals(10, entry.mCount);
- assertEquals(1000 * 1000, entry.mTotalTime); // Microseconds
+ assertEquals(10, entry.count);
+ assertEquals(1000 * 1000, entry.totalTimeUs); // Microseconds
wlStats[0] = createWakeLockInfo("WakeLock2", 20, 2000); // Milliseconds
@@ -265,8 +281,8 @@ public class KernelWakelockReaderTest extends TestCase {
assertFalse(staleStats.containsKey("WakeLock1"));
assertTrue(staleStats.containsKey("WakeLock2"));
entry = staleStats.get("WakeLock2");
- assertEquals(20, entry.mCount);
- assertEquals(2000 * 1000, entry.mTotalTime); // Micro seconds
+ assertEquals(20, entry.count);
+ assertEquals(2000 * 1000, entry.totalTimeUs); // Micro seconds
}
// -------------------- Aggregate Wakelock Stats Tests --------------------
@@ -298,8 +314,8 @@ public class KernelWakelockReaderTest extends TestCase {
assertTrue(staleStats.containsKey("Wakelock"));
KernelWakelockStats.Entry entry = staleStats.get("Wakelock");
- assertEquals(34, entry.mCount);
- assertEquals(1000 * 123, entry.mTotalTime); // Microseconds
+ assertEquals(34, entry.count);
+ assertEquals(1000 * 123, entry.totalTimeUs); // Microseconds
}
@SmallTest
@@ -317,8 +333,8 @@ public class KernelWakelockReaderTest extends TestCase {
assertTrue(staleStats.containsKey("WakeLock"));
KernelWakelockStats.Entry entry = staleStats.get("WakeLock");
- assertEquals(10, entry.mCount);
- assertEquals(1000 * 1000, entry.mTotalTime); // Microseconds
+ assertEquals(10, entry.count);
+ assertEquals(1000 * 1000, entry.totalTimeUs); // Microseconds
}
@SmallTest
@@ -337,13 +353,13 @@ public class KernelWakelockReaderTest extends TestCase {
assertTrue(staleStats.containsKey("WakeLock1"));
KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1");
- assertEquals(34, entry1.mCount);
- assertEquals(123 * 1000, entry1.mTotalTime); // Microseconds
+ assertEquals(34, entry1.count);
+ assertEquals(123 * 1000, entry1.totalTimeUs); // Microseconds
assertTrue(staleStats.containsKey("WakeLock2"));
KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2");
- assertEquals(10, entry2.mCount);
- assertEquals(1000 * 1000, entry2.mTotalTime); // Microseconds
+ assertEquals(10, entry2.count);
+ assertEquals(1000 * 1000, entry2.totalTimeUs); // Microseconds
}
@SmallTest
@@ -368,20 +384,20 @@ public class KernelWakelockReaderTest extends TestCase {
assertTrue(staleStats.containsKey("WakeLock4"));
KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1");
- assertEquals(34, entry1.mCount);
- assertEquals(123 * 1000, entry1.mTotalTime); // Microseconds
+ assertEquals(34, entry1.count);
+ assertEquals(123 * 1000, entry1.totalTimeUs); // Microseconds
KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2");
- assertEquals(46, entry2.mCount);
- assertEquals(345 * 1000, entry2.mTotalTime); // Microseconds
+ assertEquals(46, entry2.count);
+ assertEquals(345 * 1000, entry2.totalTimeUs); // Microseconds
KernelWakelockStats.Entry entry3 = staleStats.get("WakeLock3");
- assertEquals(10, entry3.mCount);
- assertEquals(1000 * 1000, entry3.mTotalTime); // Microseconds
+ assertEquals(10, entry3.count);
+ assertEquals(1000 * 1000, entry3.totalTimeUs); // Microseconds
KernelWakelockStats.Entry entry4 = staleStats.get("WakeLock4");
- assertEquals(20, entry4.mCount);
- assertEquals(2000 * 1000, entry4.mTotalTime); // Microseconds
+ assertEquals(20, entry4.count);
+ assertEquals(2000 * 1000, entry4.totalTimeUs); // Microseconds
buffer = new ProcFileBuilder()
.addLine("WakeLock1", 45, 789) // Milliseconds
@@ -401,11 +417,11 @@ public class KernelWakelockReaderTest extends TestCase {
assertFalse(staleStats.containsKey("WakeLock3"));
entry1 = staleStats.get("WakeLock1");
- assertEquals(45 + 56, entry1.mCount);
- assertEquals((789 + 123) * 1000, entry1.mTotalTime); // Microseconds
+ assertEquals(45 + 56, entry1.count);
+ assertEquals((789 + 123) * 1000, entry1.totalTimeUs); // Microseconds
entry2 = staleStats.get("WakeLock4");
- assertEquals(40, entry2.mCount);
- assertEquals(4000 * 1000, entry4.mTotalTime); // Microseconds
+ assertEquals(40, entry2.count);
+ assertEquals(4000 * 1000, entry4.totalTimeUs); // Microseconds
}
}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 2e647c4ef78d..62e56f9eeed1 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -27,7 +27,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.usage.NetworkStatsManager;
-import android.hardware.radio.V1_5.AccessNetwork;
import android.net.NetworkCapabilities;
import android.net.NetworkStats;
import android.os.BatteryConsumer;
@@ -35,6 +34,7 @@ import android.os.BatteryStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Process;
import android.os.UidBatteryConsumer;
+import android.telephony.AccessNetworkConstants;
import android.telephony.ActivityStatsTechSpecificInfo;
import android.telephony.CellSignalStrength;
import android.telephony.DataConnectionRealTimeInfo;
@@ -248,22 +248,24 @@ public class MobileRadioPowerCalculatorTest {
mStatsRule.setNetworkStats(networkStats);
ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ AccessNetworkConstants.AccessNetworkType.CDMA2000,
+ ServiceState.FREQUENCY_RANGE_UNKNOWN,
new int[]{10, 11, 12, 13, 14}, 15);
ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ AccessNetworkConstants.AccessNetworkType.EUTRAN,
+ ServiceState.FREQUENCY_RANGE_UNKNOWN,
new int[]{20, 21, 22, 23, 24}, 25);
ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
+ AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
new int[]{30, 31, 32, 33, 34}, 35);
ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
+ AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
new int[]{40, 41, 42, 43, 44}, 45);
ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
+ AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
new int[]{50, 51, 52, 53, 54}, 55);
ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
+ AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
new int[]{60, 61, 62, 63, 64}, 65);
ActivityStatsTechSpecificInfo[] ratInfos =
@@ -719,22 +721,24 @@ public class MobileRadioPowerCalculatorTest {
mStatsRule.setNetworkStats(networkStats);
ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ AccessNetworkConstants.AccessNetworkType.CDMA2000,
+ ServiceState.FREQUENCY_RANGE_UNKNOWN,
new int[]{10, 11, 12, 13, 14}, 15);
ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ AccessNetworkConstants.AccessNetworkType.EUTRAN,
+ ServiceState.FREQUENCY_RANGE_UNKNOWN,
new int[]{20, 21, 22, 23, 24}, 25);
ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
+ AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
new int[]{30, 31, 32, 33, 34}, 35);
ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
+ AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
new int[]{40, 41, 42, 43, 44}, 45);
ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
+ AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
new int[]{50, 51, 52, 53, 54}, 55);
ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo(
- AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
+ AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
new int[]{60, 61, 62, 63, 64}, 65);
ActivityStatsTechSpecificInfo[] ratInfos =
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index d78ab8677800..b032cbe67485 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -74,6 +74,7 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
mCpuUidFreqTimeReader = mock(KernelCpuUidFreqTimeReader.class);
when(mCpuUidFreqTimeReader.readFreqs(any())).thenReturn(new long[]{100, 200});
+ mKernelWakelockReader = null;
}
public void initMeasuredEnergyStats(String[] customBucketNames) {
@@ -173,6 +174,11 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
return this;
}
+ public MockBatteryStatsImpl setKernelWakelockReader(KernelWakelockReader reader) {
+ mKernelWakelockReader = reader;
+ return this;
+ }
+
public MockBatteryStatsImpl setSystemServerCpuThreadReader(
SystemServerCpuThreadReader systemServerCpuThreadReader) {
mSystemServerCpuThreadReader = systemServerCpuThreadReader;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 27677e153d83..0e627b2f0909 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -88,7 +88,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
private static final Multimap<Class<?>, String> KNOWN_BAD =
ImmutableMultimap.<Class<?>, String>builder()
.put(Notification.Builder.class, "setPublicVersion") // b/276294099
- .putAll(RemoteViews.class, "addView", "addStableView") // b/277740082
.put(RemoteViews.class, "setIcon") // b/281018094
.put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385
.put(Person.Builder.class, "setUri") // TODO: b/281044385
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 2671e771aa59..2b589bf59682 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -944,7 +944,7 @@ public class ActivityStarterTests extends WindowTestsBase {
anyInt(), anyInt()));
doReturn(BackgroundStartPrivileges.allowBackgroundActivityStarts(null)).when(
() -> PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
- anyObject(), anyInt()));
+ anyObject(), anyInt(), anyObject()));
runAndVerifyBackgroundActivityStartsSubtest(
"allowed_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 5ec36048234b..a422c6893de5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -178,6 +178,44 @@ public class DisplayPolicyTests extends WindowTestsBase {
dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
}
+ @Test
+ public void testChooseNavigationBackgroundWindow() {
+ final WindowState drawBarWin = createOpaqueFullscreen(false);
+ final WindowState nonDrawBarWin = createDimmingDialogWindow(true);
+
+ final WindowState visibleIme = createInputMethodWindow(true, true, false);
+ final WindowState invisibleIme = createInputMethodWindow(false, true, false);
+ final WindowState nonDrawBarIme = createInputMethodWindow(true, false, false);
+
+ assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+ drawBarWin, null, NAV_BAR_BOTTOM));
+ assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+ null, null, NAV_BAR_BOTTOM));
+ assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+ nonDrawBarWin, null, NAV_BAR_BOTTOM));
+
+ assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+ drawBarWin, visibleIme, NAV_BAR_BOTTOM));
+ assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+ null, visibleIme, NAV_BAR_BOTTOM));
+ assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+ nonDrawBarWin, visibleIme, NAV_BAR_BOTTOM));
+
+ assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+ drawBarWin, invisibleIme, NAV_BAR_BOTTOM));
+ assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+ null, invisibleIme, NAV_BAR_BOTTOM));
+ assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+ nonDrawBarWin, invisibleIme, NAV_BAR_BOTTOM));
+
+ assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+ drawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM));
+ assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+ null, nonDrawBarIme, NAV_BAR_BOTTOM));
+ assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+ nonDrawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM));
+ }
+
@SetupWindows(addWindows = W_NAVIGATION_BAR)
@Test
public void testUpdateLightNavigationBarLw() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 7d507e9150e8..34a13bfa855c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -38,6 +38,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCA
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -461,7 +462,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
WindowInsets.Type.navigationBars());
- taskbar.setInsetsRoundedCornerFrame(true);
+ taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
final Rect opaqueBounds = new Rect(0, 0, 500, 300);
doReturn(opaqueBounds).when(mActivity).getBounds();
@@ -505,7 +506,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
public void testGetCropBoundsIfNeeded_appliesCrop() {
final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
WindowInsets.Type.navigationBars());
- taskbar.setInsetsRoundedCornerFrame(true);
+ taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
// Apply crop if taskbar is expanded
@@ -528,7 +529,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() {
final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
WindowInsets.Type.navigationBars());
- taskbar.setInsetsRoundedCornerFrame(true);
+ taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
final float scaling = 2.0f;
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 2dd34eb5ac4d..d91be16f2538 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -30,6 +30,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
@@ -3483,7 +3484,7 @@ public class SizeCompatTests extends WindowTestsBase {
final InsetsSource navSource = new InsetsSource(
InsetsSource.createId(null, 0, navigationBars()), navigationBars());
- navSource.setInsetsRoundedCornerFrame(true);
+ navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
@@ -3531,7 +3532,7 @@ public class SizeCompatTests extends WindowTestsBase {
final InsetsSource navSource = new InsetsSource(
InsetsSource.createId(null, 0, navigationBars()), navigationBars());
- navSource.setInsetsRoundedCornerFrame(true);
+ navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
// Immersive activity has transient navbar
navSource.setVisible(!immersive);
navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 31ab3c853c07..ffc7b8eb13d6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -84,6 +84,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.infra.AndroidFuture;
import com.android.server.LocalServices;
+import com.android.server.policy.AppOpsPolicy;
import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
import java.io.Closeable;
@@ -743,18 +744,24 @@ abstract class DetectorSession {
void enforcePermissionsForDataDelivery() {
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
- int result = PermissionChecker.checkPermissionForPreflight(
- mContext, RECORD_AUDIO, /* pid */ -1, mVoiceInteractorIdentity.uid,
- mVoiceInteractorIdentity.packageName);
- if (result != PermissionChecker.PERMISSION_GRANTED) {
- throw new SecurityException(
- "Failed to obtain permission RECORD_AUDIO for identity "
- + mVoiceInteractorIdentity);
+ if (AppOpsPolicy.isHotwordDetectionServiceRequired(mContext.getPackageManager())) {
+ int result = PermissionChecker.checkPermissionForPreflight(
+ mContext, RECORD_AUDIO, /* pid */ -1, mVoiceInteractorIdentity.uid,
+ mVoiceInteractorIdentity.packageName);
+ if (result != PermissionChecker.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Failed to obtain permission RECORD_AUDIO for identity "
+ + mVoiceInteractorIdentity);
+ }
+ int hotwordOp = AppOpsManager.strOpToOp(
+ AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
+ mAppOpsManager.noteOpNoThrow(hotwordOp,
+ mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+ mVoiceInteractorIdentity.attributionTag, HOTWORD_DETECTION_OP_MESSAGE);
+ } else {
+ enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
+ RECORD_AUDIO, HOTWORD_DETECTION_OP_MESSAGE);
}
- int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
- mAppOpsManager.noteOpNoThrow(hotwordOp,
- mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
- mVoiceInteractorIdentity.attributionTag, HOTWORD_DETECTION_OP_MESSAGE);
enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
CAPTURE_AUDIO_HOTWORD, HOTWORD_DETECTION_OP_MESSAGE);
}
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 27ba67667d92..35721f11c27f 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -19,7 +19,7 @@ package android.telephony;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.hardware.radio.V1_5.AccessNetwork;
+import android.hardware.radio.AccessNetwork;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -134,20 +134,22 @@ public final class AccessNetworkConstants {
* http://www.etsi.org/deliver/etsi_ts/145000_145099/145005/14.00.00_60/ts_145005v140000p.pdf
*/
public static final class GeranBand {
- public static final int BAND_T380 = android.hardware.radio.V1_1.GeranBands.BAND_T380;
- public static final int BAND_T410 = android.hardware.radio.V1_1.GeranBands.BAND_T410;
- public static final int BAND_450 = android.hardware.radio.V1_1.GeranBands.BAND_450;
- public static final int BAND_480 = android.hardware.radio.V1_1.GeranBands.BAND_480;
- public static final int BAND_710 = android.hardware.radio.V1_1.GeranBands.BAND_710;
- public static final int BAND_750 = android.hardware.radio.V1_1.GeranBands.BAND_750;
- public static final int BAND_T810 = android.hardware.radio.V1_1.GeranBands.BAND_T810;
- public static final int BAND_850 = android.hardware.radio.V1_1.GeranBands.BAND_850;
- public static final int BAND_P900 = android.hardware.radio.V1_1.GeranBands.BAND_P900;
- public static final int BAND_E900 = android.hardware.radio.V1_1.GeranBands.BAND_E900;
- public static final int BAND_R900 = android.hardware.radio.V1_1.GeranBands.BAND_R900;
- public static final int BAND_DCS1800 = android.hardware.radio.V1_1.GeranBands.BAND_DCS1800;
- public static final int BAND_PCS1900 = android.hardware.radio.V1_1.GeranBands.BAND_PCS1900;
- public static final int BAND_ER900 = android.hardware.radio.V1_1.GeranBands.BAND_ER900;
+ public static final int BAND_T380 = android.hardware.radio.network.GeranBands.BAND_T380;
+ public static final int BAND_T410 = android.hardware.radio.network.GeranBands.BAND_T410;
+ public static final int BAND_450 = android.hardware.radio.network.GeranBands.BAND_450;
+ public static final int BAND_480 = android.hardware.radio.network.GeranBands.BAND_480;
+ public static final int BAND_710 = android.hardware.radio.network.GeranBands.BAND_710;
+ public static final int BAND_750 = android.hardware.radio.network.GeranBands.BAND_750;
+ public static final int BAND_T810 = android.hardware.radio.network.GeranBands.BAND_T810;
+ public static final int BAND_850 = android.hardware.radio.network.GeranBands.BAND_850;
+ public static final int BAND_P900 = android.hardware.radio.network.GeranBands.BAND_P900;
+ public static final int BAND_E900 = android.hardware.radio.network.GeranBands.BAND_E900;
+ public static final int BAND_R900 = android.hardware.radio.network.GeranBands.BAND_R900;
+ public static final int BAND_DCS1800 =
+ android.hardware.radio.network.GeranBands.BAND_DCS1800;
+ public static final int BAND_PCS1900 =
+ android.hardware.radio.network.GeranBands.BAND_PCS1900;
+ public static final int BAND_ER900 = android.hardware.radio.network.GeranBands.BAND_ER900;
/**
* GeranBand
@@ -226,28 +228,28 @@ public final class AccessNetworkConstants {
* http://www.etsi.org/deliver/etsi_ts/125100_125199/125104/13.03.00_60/ts_125104v130p.pdf
*/
public static final class UtranBand {
- public static final int BAND_1 = android.hardware.radio.V1_5.UtranBands.BAND_1;
- public static final int BAND_2 = android.hardware.radio.V1_5.UtranBands.BAND_2;
- public static final int BAND_3 = android.hardware.radio.V1_5.UtranBands.BAND_3;
- public static final int BAND_4 = android.hardware.radio.V1_5.UtranBands.BAND_4;
- public static final int BAND_5 = android.hardware.radio.V1_5.UtranBands.BAND_5;
- public static final int BAND_6 = android.hardware.radio.V1_5.UtranBands.BAND_6;
- public static final int BAND_7 = android.hardware.radio.V1_5.UtranBands.BAND_7;
- public static final int BAND_8 = android.hardware.radio.V1_5.UtranBands.BAND_8;
- public static final int BAND_9 = android.hardware.radio.V1_5.UtranBands.BAND_9;
- public static final int BAND_10 = android.hardware.radio.V1_5.UtranBands.BAND_10;
- public static final int BAND_11 = android.hardware.radio.V1_5.UtranBands.BAND_11;
- public static final int BAND_12 = android.hardware.radio.V1_5.UtranBands.BAND_12;
- public static final int BAND_13 = android.hardware.radio.V1_5.UtranBands.BAND_13;
- public static final int BAND_14 = android.hardware.radio.V1_5.UtranBands.BAND_14;
+ public static final int BAND_1 = android.hardware.radio.network.UtranBands.BAND_1;
+ public static final int BAND_2 = android.hardware.radio.network.UtranBands.BAND_2;
+ public static final int BAND_3 = android.hardware.radio.network.UtranBands.BAND_3;
+ public static final int BAND_4 = android.hardware.radio.network.UtranBands.BAND_4;
+ public static final int BAND_5 = android.hardware.radio.network.UtranBands.BAND_5;
+ public static final int BAND_6 = android.hardware.radio.network.UtranBands.BAND_6;
+ public static final int BAND_7 = android.hardware.radio.network.UtranBands.BAND_7;
+ public static final int BAND_8 = android.hardware.radio.network.UtranBands.BAND_8;
+ public static final int BAND_9 = android.hardware.radio.network.UtranBands.BAND_9;
+ public static final int BAND_10 = android.hardware.radio.network.UtranBands.BAND_10;
+ public static final int BAND_11 = android.hardware.radio.network.UtranBands.BAND_11;
+ public static final int BAND_12 = android.hardware.radio.network.UtranBands.BAND_12;
+ public static final int BAND_13 = android.hardware.radio.network.UtranBands.BAND_13;
+ public static final int BAND_14 = android.hardware.radio.network.UtranBands.BAND_14;
// band 15, 16, 17, 18 are reserved
- public static final int BAND_19 = android.hardware.radio.V1_5.UtranBands.BAND_19;
- public static final int BAND_20 = android.hardware.radio.V1_5.UtranBands.BAND_20;
- public static final int BAND_21 = android.hardware.radio.V1_5.UtranBands.BAND_21;
- public static final int BAND_22 = android.hardware.radio.V1_5.UtranBands.BAND_22;
+ public static final int BAND_19 = android.hardware.radio.network.UtranBands.BAND_19;
+ public static final int BAND_20 = android.hardware.radio.network.UtranBands.BAND_20;
+ public static final int BAND_21 = android.hardware.radio.network.UtranBands.BAND_21;
+ public static final int BAND_22 = android.hardware.radio.network.UtranBands.BAND_22;
// band 23, 24 are reserved
- public static final int BAND_25 = android.hardware.radio.V1_5.UtranBands.BAND_25;
- public static final int BAND_26 = android.hardware.radio.V1_5.UtranBands.BAND_26;
+ public static final int BAND_25 = android.hardware.radio.network.UtranBands.BAND_25;
+ public static final int BAND_26 = android.hardware.radio.network.UtranBands.BAND_26;
// Frequency bands for TD-SCDMA. Defined in 3GPP TS 25.102, Table 5.2.
@@ -256,38 +258,38 @@ public final class AccessNetworkConstants {
* 1900 - 1920 MHz: Uplink and downlink transmission
* 2010 - 2025 MHz: Uplink and downlink transmission
*/
- public static final int BAND_A = android.hardware.radio.V1_5.UtranBands.BAND_A;
+ public static final int BAND_A = android.hardware.radio.network.UtranBands.BAND_A;
/**
* Band B
* 1850 - 1910 MHz: Uplink and downlink transmission
* 1930 - 1990 MHz: Uplink and downlink transmission
*/
- public static final int BAND_B = android.hardware.radio.V1_5.UtranBands.BAND_B;
+ public static final int BAND_B = android.hardware.radio.network.UtranBands.BAND_B;
/**
* Band C
* 1910 - 1930 MHz: Uplink and downlink transmission
*/
- public static final int BAND_C = android.hardware.radio.V1_5.UtranBands.BAND_C;
+ public static final int BAND_C = android.hardware.radio.network.UtranBands.BAND_C;
/**
* Band D
* 2570 - 2620 MHz: Uplink and downlink transmission
*/
- public static final int BAND_D = android.hardware.radio.V1_5.UtranBands.BAND_D;
+ public static final int BAND_D = android.hardware.radio.network.UtranBands.BAND_D;
/**
* Band E
* 2300—2400 MHz: Uplink and downlink transmission
*/
- public static final int BAND_E = android.hardware.radio.V1_5.UtranBands.BAND_E;
+ public static final int BAND_E = android.hardware.radio.network.UtranBands.BAND_E;
/**
* Band F
* 1880 - 1920 MHz: Uplink and downlink transmission
*/
- public static final int BAND_F = android.hardware.radio.V1_5.UtranBands.BAND_F;
+ public static final int BAND_F = android.hardware.radio.network.UtranBands.BAND_F;
/**
* UtranBand
@@ -389,66 +391,66 @@ public final class AccessNetworkConstants {
* https://www.etsi.org/deliver/etsi_ts/136100_136199/136101/15.09.00_60/ts_136101v150900p.pdf
*/
public static final class EutranBand {
- public static final int BAND_1 = android.hardware.radio.V1_5.EutranBands.BAND_1;
- public static final int BAND_2 = android.hardware.radio.V1_5.EutranBands.BAND_2;
- public static final int BAND_3 = android.hardware.radio.V1_5.EutranBands.BAND_3;
- public static final int BAND_4 = android.hardware.radio.V1_5.EutranBands.BAND_4;
- public static final int BAND_5 = android.hardware.radio.V1_5.EutranBands.BAND_5;
- public static final int BAND_6 = android.hardware.radio.V1_5.EutranBands.BAND_6;
- public static final int BAND_7 = android.hardware.radio.V1_5.EutranBands.BAND_7;
- public static final int BAND_8 = android.hardware.radio.V1_5.EutranBands.BAND_8;
- public static final int BAND_9 = android.hardware.radio.V1_5.EutranBands.BAND_9;
- public static final int BAND_10 = android.hardware.radio.V1_5.EutranBands.BAND_10;
- public static final int BAND_11 = android.hardware.radio.V1_5.EutranBands.BAND_11;
- public static final int BAND_12 = android.hardware.radio.V1_5.EutranBands.BAND_12;
- public static final int BAND_13 = android.hardware.radio.V1_5.EutranBands.BAND_13;
- public static final int BAND_14 = android.hardware.radio.V1_5.EutranBands.BAND_14;
- public static final int BAND_17 = android.hardware.radio.V1_5.EutranBands.BAND_17;
- public static final int BAND_18 = android.hardware.radio.V1_5.EutranBands.BAND_18;
- public static final int BAND_19 = android.hardware.radio.V1_5.EutranBands.BAND_19;
- public static final int BAND_20 = android.hardware.radio.V1_5.EutranBands.BAND_20;
- public static final int BAND_21 = android.hardware.radio.V1_5.EutranBands.BAND_21;
- public static final int BAND_22 = android.hardware.radio.V1_5.EutranBands.BAND_22;
- public static final int BAND_23 = android.hardware.radio.V1_5.EutranBands.BAND_23;
- public static final int BAND_24 = android.hardware.radio.V1_5.EutranBands.BAND_24;
- public static final int BAND_25 = android.hardware.radio.V1_5.EutranBands.BAND_25;
- public static final int BAND_26 = android.hardware.radio.V1_5.EutranBands.BAND_26;
- public static final int BAND_27 = android.hardware.radio.V1_5.EutranBands.BAND_27;
- public static final int BAND_28 = android.hardware.radio.V1_5.EutranBands.BAND_28;
- public static final int BAND_30 = android.hardware.radio.V1_5.EutranBands.BAND_30;
- public static final int BAND_31 = android.hardware.radio.V1_5.EutranBands.BAND_31;
- public static final int BAND_33 = android.hardware.radio.V1_5.EutranBands.BAND_33;
- public static final int BAND_34 = android.hardware.radio.V1_5.EutranBands.BAND_34;
- public static final int BAND_35 = android.hardware.radio.V1_5.EutranBands.BAND_35;
- public static final int BAND_36 = android.hardware.radio.V1_5.EutranBands.BAND_36;
- public static final int BAND_37 = android.hardware.radio.V1_5.EutranBands.BAND_37;
- public static final int BAND_38 = android.hardware.radio.V1_5.EutranBands.BAND_38;
- public static final int BAND_39 = android.hardware.radio.V1_5.EutranBands.BAND_39;
- public static final int BAND_40 = android.hardware.radio.V1_5.EutranBands.BAND_40;
- public static final int BAND_41 = android.hardware.radio.V1_5.EutranBands.BAND_41;
- public static final int BAND_42 = android.hardware.radio.V1_5.EutranBands.BAND_42;
- public static final int BAND_43 = android.hardware.radio.V1_5.EutranBands.BAND_43;
- public static final int BAND_44 = android.hardware.radio.V1_5.EutranBands.BAND_44;
- public static final int BAND_45 = android.hardware.radio.V1_5.EutranBands.BAND_45;
- public static final int BAND_46 = android.hardware.radio.V1_5.EutranBands.BAND_46;
- public static final int BAND_47 = android.hardware.radio.V1_5.EutranBands.BAND_47;
- public static final int BAND_48 = android.hardware.radio.V1_5.EutranBands.BAND_48;
- public static final int BAND_49 = android.hardware.radio.V1_5.EutranBands.BAND_49;
- public static final int BAND_50 = android.hardware.radio.V1_5.EutranBands.BAND_50;
- public static final int BAND_51 = android.hardware.radio.V1_5.EutranBands.BAND_51;
- public static final int BAND_52 = android.hardware.radio.V1_5.EutranBands.BAND_52;
- public static final int BAND_53 = android.hardware.radio.V1_5.EutranBands.BAND_53;
- public static final int BAND_65 = android.hardware.radio.V1_5.EutranBands.BAND_65;
- public static final int BAND_66 = android.hardware.radio.V1_5.EutranBands.BAND_66;
- public static final int BAND_68 = android.hardware.radio.V1_5.EutranBands.BAND_68;
- public static final int BAND_70 = android.hardware.radio.V1_5.EutranBands.BAND_70;
- public static final int BAND_71 = android.hardware.radio.V1_5.EutranBands.BAND_71;
- public static final int BAND_72 = android.hardware.radio.V1_5.EutranBands.BAND_72;
- public static final int BAND_73 = android.hardware.radio.V1_5.EutranBands.BAND_73;
- public static final int BAND_74 = android.hardware.radio.V1_5.EutranBands.BAND_74;
- public static final int BAND_85 = android.hardware.radio.V1_5.EutranBands.BAND_85;
- public static final int BAND_87 = android.hardware.radio.V1_5.EutranBands.BAND_87;
- public static final int BAND_88 = android.hardware.radio.V1_5.EutranBands.BAND_88;
+ public static final int BAND_1 = android.hardware.radio.network.EutranBands.BAND_1;
+ public static final int BAND_2 = android.hardware.radio.network.EutranBands.BAND_2;
+ public static final int BAND_3 = android.hardware.radio.network.EutranBands.BAND_3;
+ public static final int BAND_4 = android.hardware.radio.network.EutranBands.BAND_4;
+ public static final int BAND_5 = android.hardware.radio.network.EutranBands.BAND_5;
+ public static final int BAND_6 = android.hardware.radio.network.EutranBands.BAND_6;
+ public static final int BAND_7 = android.hardware.radio.network.EutranBands.BAND_7;
+ public static final int BAND_8 = android.hardware.radio.network.EutranBands.BAND_8;
+ public static final int BAND_9 = android.hardware.radio.network.EutranBands.BAND_9;
+ public static final int BAND_10 = android.hardware.radio.network.EutranBands.BAND_10;
+ public static final int BAND_11 = android.hardware.radio.network.EutranBands.BAND_11;
+ public static final int BAND_12 = android.hardware.radio.network.EutranBands.BAND_12;
+ public static final int BAND_13 = android.hardware.radio.network.EutranBands.BAND_13;
+ public static final int BAND_14 = android.hardware.radio.network.EutranBands.BAND_14;
+ public static final int BAND_17 = android.hardware.radio.network.EutranBands.BAND_17;
+ public static final int BAND_18 = android.hardware.radio.network.EutranBands.BAND_18;
+ public static final int BAND_19 = android.hardware.radio.network.EutranBands.BAND_19;
+ public static final int BAND_20 = android.hardware.radio.network.EutranBands.BAND_20;
+ public static final int BAND_21 = android.hardware.radio.network.EutranBands.BAND_21;
+ public static final int BAND_22 = android.hardware.radio.network.EutranBands.BAND_22;
+ public static final int BAND_23 = android.hardware.radio.network.EutranBands.BAND_23;
+ public static final int BAND_24 = android.hardware.radio.network.EutranBands.BAND_24;
+ public static final int BAND_25 = android.hardware.radio.network.EutranBands.BAND_25;
+ public static final int BAND_26 = android.hardware.radio.network.EutranBands.BAND_26;
+ public static final int BAND_27 = android.hardware.radio.network.EutranBands.BAND_27;
+ public static final int BAND_28 = android.hardware.radio.network.EutranBands.BAND_28;
+ public static final int BAND_30 = android.hardware.radio.network.EutranBands.BAND_30;
+ public static final int BAND_31 = android.hardware.radio.network.EutranBands.BAND_31;
+ public static final int BAND_33 = android.hardware.radio.network.EutranBands.BAND_33;
+ public static final int BAND_34 = android.hardware.radio.network.EutranBands.BAND_34;
+ public static final int BAND_35 = android.hardware.radio.network.EutranBands.BAND_35;
+ public static final int BAND_36 = android.hardware.radio.network.EutranBands.BAND_36;
+ public static final int BAND_37 = android.hardware.radio.network.EutranBands.BAND_37;
+ public static final int BAND_38 = android.hardware.radio.network.EutranBands.BAND_38;
+ public static final int BAND_39 = android.hardware.radio.network.EutranBands.BAND_39;
+ public static final int BAND_40 = android.hardware.radio.network.EutranBands.BAND_40;
+ public static final int BAND_41 = android.hardware.radio.network.EutranBands.BAND_41;
+ public static final int BAND_42 = android.hardware.radio.network.EutranBands.BAND_42;
+ public static final int BAND_43 = android.hardware.radio.network.EutranBands.BAND_43;
+ public static final int BAND_44 = android.hardware.radio.network.EutranBands.BAND_44;
+ public static final int BAND_45 = android.hardware.radio.network.EutranBands.BAND_45;
+ public static final int BAND_46 = android.hardware.radio.network.EutranBands.BAND_46;
+ public static final int BAND_47 = android.hardware.radio.network.EutranBands.BAND_47;
+ public static final int BAND_48 = android.hardware.radio.network.EutranBands.BAND_48;
+ public static final int BAND_49 = android.hardware.radio.network.EutranBands.BAND_49;
+ public static final int BAND_50 = android.hardware.radio.network.EutranBands.BAND_50;
+ public static final int BAND_51 = android.hardware.radio.network.EutranBands.BAND_51;
+ public static final int BAND_52 = android.hardware.radio.network.EutranBands.BAND_52;
+ public static final int BAND_53 = android.hardware.radio.network.EutranBands.BAND_53;
+ public static final int BAND_65 = android.hardware.radio.network.EutranBands.BAND_65;
+ public static final int BAND_66 = android.hardware.radio.network.EutranBands.BAND_66;
+ public static final int BAND_68 = android.hardware.radio.network.EutranBands.BAND_68;
+ public static final int BAND_70 = android.hardware.radio.network.EutranBands.BAND_70;
+ public static final int BAND_71 = android.hardware.radio.network.EutranBands.BAND_71;
+ public static final int BAND_72 = android.hardware.radio.network.EutranBands.BAND_72;
+ public static final int BAND_73 = android.hardware.radio.network.EutranBands.BAND_73;
+ public static final int BAND_74 = android.hardware.radio.network.EutranBands.BAND_74;
+ public static final int BAND_85 = android.hardware.radio.network.EutranBands.BAND_85;
+ public static final int BAND_87 = android.hardware.radio.network.EutranBands.BAND_87;
+ public static final int BAND_88 = android.hardware.radio.network.EutranBands.BAND_88;
/**
* EutranBands
@@ -714,61 +716,61 @@ public final class AccessNetworkConstants {
*/
public static final class NgranBands {
/** 3GPP TS 38.101-1, Version 16.5.0, Table 5.2-1: FR1 bands */
- public static final int BAND_1 = android.hardware.radio.V1_5.NgranBands.BAND_1;
- public static final int BAND_2 = android.hardware.radio.V1_5.NgranBands.BAND_2;
- public static final int BAND_3 = android.hardware.radio.V1_5.NgranBands.BAND_3;
- public static final int BAND_5 = android.hardware.radio.V1_5.NgranBands.BAND_5;
- public static final int BAND_7 = android.hardware.radio.V1_5.NgranBands.BAND_7;
- public static final int BAND_8 = android.hardware.radio.V1_5.NgranBands.BAND_8;
- public static final int BAND_12 = android.hardware.radio.V1_5.NgranBands.BAND_12;
- public static final int BAND_14 = android.hardware.radio.V1_5.NgranBands.BAND_14;
- public static final int BAND_18 = android.hardware.radio.V1_5.NgranBands.BAND_18;
- public static final int BAND_20 = android.hardware.radio.V1_5.NgranBands.BAND_20;
- public static final int BAND_25 = android.hardware.radio.V1_5.NgranBands.BAND_25;
- public static final int BAND_26 = android.hardware.radio.V1_6.NgranBands.BAND_26;
- public static final int BAND_28 = android.hardware.radio.V1_5.NgranBands.BAND_28;
- public static final int BAND_29 = android.hardware.radio.V1_5.NgranBands.BAND_29;
- public static final int BAND_30 = android.hardware.radio.V1_5.NgranBands.BAND_30;
- public static final int BAND_34 = android.hardware.radio.V1_5.NgranBands.BAND_34;
- public static final int BAND_38 = android.hardware.radio.V1_5.NgranBands.BAND_38;
- public static final int BAND_39 = android.hardware.radio.V1_5.NgranBands.BAND_39;
- public static final int BAND_40 = android.hardware.radio.V1_5.NgranBands.BAND_40;
- public static final int BAND_41 = android.hardware.radio.V1_5.NgranBands.BAND_41;
- public static final int BAND_46 = android.hardware.radio.V1_6.NgranBands.BAND_46;
- public static final int BAND_48 = android.hardware.radio.V1_5.NgranBands.BAND_48;
- public static final int BAND_50 = android.hardware.radio.V1_5.NgranBands.BAND_50;
- public static final int BAND_51 = android.hardware.radio.V1_5.NgranBands.BAND_51;
- public static final int BAND_53 = android.hardware.radio.V1_6.NgranBands.BAND_53;
- public static final int BAND_65 = android.hardware.radio.V1_5.NgranBands.BAND_65;
- public static final int BAND_66 = android.hardware.radio.V1_5.NgranBands.BAND_66;
- public static final int BAND_70 = android.hardware.radio.V1_5.NgranBands.BAND_70;
- public static final int BAND_71 = android.hardware.radio.V1_5.NgranBands.BAND_71;
- public static final int BAND_74 = android.hardware.radio.V1_5.NgranBands.BAND_74;
- public static final int BAND_75 = android.hardware.radio.V1_5.NgranBands.BAND_75;
- public static final int BAND_76 = android.hardware.radio.V1_5.NgranBands.BAND_76;
- public static final int BAND_77 = android.hardware.radio.V1_5.NgranBands.BAND_77;
- public static final int BAND_78 = android.hardware.radio.V1_5.NgranBands.BAND_78;
- public static final int BAND_79 = android.hardware.radio.V1_5.NgranBands.BAND_79;
- public static final int BAND_80 = android.hardware.radio.V1_5.NgranBands.BAND_80;
- public static final int BAND_81 = android.hardware.radio.V1_5.NgranBands.BAND_81;
- public static final int BAND_82 = android.hardware.radio.V1_5.NgranBands.BAND_82;
- public static final int BAND_83 = android.hardware.radio.V1_5.NgranBands.BAND_83;
- public static final int BAND_84 = android.hardware.radio.V1_5.NgranBands.BAND_84;
- public static final int BAND_86 = android.hardware.radio.V1_5.NgranBands.BAND_86;
- public static final int BAND_89 = android.hardware.radio.V1_5.NgranBands.BAND_89;
- public static final int BAND_90 = android.hardware.radio.V1_5.NgranBands.BAND_90;
- public static final int BAND_91 = android.hardware.radio.V1_5.NgranBands.BAND_91;
- public static final int BAND_92 = android.hardware.radio.V1_5.NgranBands.BAND_92;
- public static final int BAND_93 = android.hardware.radio.V1_5.NgranBands.BAND_93;
- public static final int BAND_94 = android.hardware.radio.V1_5.NgranBands.BAND_94;
- public static final int BAND_95 = android.hardware.radio.V1_5.NgranBands.BAND_95;
- public static final int BAND_96 = android.hardware.radio.V1_6.NgranBands.BAND_96;
+ public static final int BAND_1 = android.hardware.radio.network.NgranBands.BAND_1;
+ public static final int BAND_2 = android.hardware.radio.network.NgranBands.BAND_2;
+ public static final int BAND_3 = android.hardware.radio.network.NgranBands.BAND_3;
+ public static final int BAND_5 = android.hardware.radio.network.NgranBands.BAND_5;
+ public static final int BAND_7 = android.hardware.radio.network.NgranBands.BAND_7;
+ public static final int BAND_8 = android.hardware.radio.network.NgranBands.BAND_8;
+ public static final int BAND_12 = android.hardware.radio.network.NgranBands.BAND_12;
+ public static final int BAND_14 = android.hardware.radio.network.NgranBands.BAND_14;
+ public static final int BAND_18 = android.hardware.radio.network.NgranBands.BAND_18;
+ public static final int BAND_20 = android.hardware.radio.network.NgranBands.BAND_20;
+ public static final int BAND_25 = android.hardware.radio.network.NgranBands.BAND_25;
+ public static final int BAND_26 = android.hardware.radio.network.NgranBands.BAND_26;
+ public static final int BAND_28 = android.hardware.radio.network.NgranBands.BAND_28;
+ public static final int BAND_29 = android.hardware.radio.network.NgranBands.BAND_29;
+ public static final int BAND_30 = android.hardware.radio.network.NgranBands.BAND_30;
+ public static final int BAND_34 = android.hardware.radio.network.NgranBands.BAND_34;
+ public static final int BAND_38 = android.hardware.radio.network.NgranBands.BAND_38;
+ public static final int BAND_39 = android.hardware.radio.network.NgranBands.BAND_39;
+ public static final int BAND_40 = android.hardware.radio.network.NgranBands.BAND_40;
+ public static final int BAND_41 = android.hardware.radio.network.NgranBands.BAND_41;
+ public static final int BAND_46 = android.hardware.radio.network.NgranBands.BAND_46;
+ public static final int BAND_48 = android.hardware.radio.network.NgranBands.BAND_48;
+ public static final int BAND_50 = android.hardware.radio.network.NgranBands.BAND_50;
+ public static final int BAND_51 = android.hardware.radio.network.NgranBands.BAND_51;
+ public static final int BAND_53 = android.hardware.radio.network.NgranBands.BAND_53;
+ public static final int BAND_65 = android.hardware.radio.network.NgranBands.BAND_65;
+ public static final int BAND_66 = android.hardware.radio.network.NgranBands.BAND_66;
+ public static final int BAND_70 = android.hardware.radio.network.NgranBands.BAND_70;
+ public static final int BAND_71 = android.hardware.radio.network.NgranBands.BAND_71;
+ public static final int BAND_74 = android.hardware.radio.network.NgranBands.BAND_74;
+ public static final int BAND_75 = android.hardware.radio.network.NgranBands.BAND_75;
+ public static final int BAND_76 = android.hardware.radio.network.NgranBands.BAND_76;
+ public static final int BAND_77 = android.hardware.radio.network.NgranBands.BAND_77;
+ public static final int BAND_78 = android.hardware.radio.network.NgranBands.BAND_78;
+ public static final int BAND_79 = android.hardware.radio.network.NgranBands.BAND_79;
+ public static final int BAND_80 = android.hardware.radio.network.NgranBands.BAND_80;
+ public static final int BAND_81 = android.hardware.radio.network.NgranBands.BAND_81;
+ public static final int BAND_82 = android.hardware.radio.network.NgranBands.BAND_82;
+ public static final int BAND_83 = android.hardware.radio.network.NgranBands.BAND_83;
+ public static final int BAND_84 = android.hardware.radio.network.NgranBands.BAND_84;
+ public static final int BAND_86 = android.hardware.radio.network.NgranBands.BAND_86;
+ public static final int BAND_89 = android.hardware.radio.network.NgranBands.BAND_89;
+ public static final int BAND_90 = android.hardware.radio.network.NgranBands.BAND_90;
+ public static final int BAND_91 = android.hardware.radio.network.NgranBands.BAND_91;
+ public static final int BAND_92 = android.hardware.radio.network.NgranBands.BAND_92;
+ public static final int BAND_93 = android.hardware.radio.network.NgranBands.BAND_93;
+ public static final int BAND_94 = android.hardware.radio.network.NgranBands.BAND_94;
+ public static final int BAND_95 = android.hardware.radio.network.NgranBands.BAND_95;
+ public static final int BAND_96 = android.hardware.radio.network.NgranBands.BAND_96;
/** 3GPP TS 38.101-2, Version 16.2.0, Table 5.2-1: FR2 bands */
- public static final int BAND_257 = android.hardware.radio.V1_5.NgranBands.BAND_257;
- public static final int BAND_258 = android.hardware.radio.V1_5.NgranBands.BAND_258;
- public static final int BAND_260 = android.hardware.radio.V1_5.NgranBands.BAND_260;
- public static final int BAND_261 = android.hardware.radio.V1_5.NgranBands.BAND_261;
+ public static final int BAND_257 = android.hardware.radio.network.NgranBands.BAND_257;
+ public static final int BAND_258 = android.hardware.radio.network.NgranBands.BAND_258;
+ public static final int BAND_260 = android.hardware.radio.network.NgranBands.BAND_260;
+ public static final int BAND_261 = android.hardware.radio.network.NgranBands.BAND_261;
/**
* NR Bands
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
index 29152f19d17d..971fc781a719 100644
--- a/telephony/java/android/telephony/BarringInfo.java
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -58,41 +58,41 @@ public final class BarringInfo implements Parcelable {
BARRING_SERVICE_TYPE_SMS})
public @interface BarringServiceType {}
- /* Applicabe to UTRAN */
+ /* Applicable to UTRAN */
/** Barring indicator for circuit-switched service; applicable to UTRAN */
public static final int BARRING_SERVICE_TYPE_CS_SERVICE =
- android.hardware.radio.V1_5.BarringInfo.ServiceType.CS_SERVICE;
+ android.hardware.radio.network.BarringInfo.SERVICE_TYPE_CS_SERVICE;
/** Barring indicator for packet-switched service; applicable to UTRAN */
public static final int BARRING_SERVICE_TYPE_PS_SERVICE =
- android.hardware.radio.V1_5.BarringInfo.ServiceType.PS_SERVICE;
+ android.hardware.radio.network.BarringInfo.SERVICE_TYPE_PS_SERVICE;
/** Barring indicator for circuit-switched voice service; applicable to UTRAN */
public static final int BARRING_SERVICE_TYPE_CS_VOICE =
- android.hardware.radio.V1_5.BarringInfo.ServiceType.CS_VOICE;
+ android.hardware.radio.network.BarringInfo.SERVICE_TYPE_CS_VOICE;
/* Applicable to EUTRAN, NGRAN */
/** Barring indicator for mobile-originated signalling; applicable to EUTRAN and NGRAN */
public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING =
- android.hardware.radio.V1_5.BarringInfo.ServiceType.MO_SIGNALLING;
+ android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MO_SIGNALLING;
/** Barring indicator for mobile-originated data traffic; applicable to EUTRAN and NGRAN */
public static final int BARRING_SERVICE_TYPE_MO_DATA =
- android.hardware.radio.V1_5.BarringInfo.ServiceType.MO_DATA;
+ android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MO_DATA;
/** Barring indicator for circuit-switched fallback for voice; applicable to EUTRAN and NGRAN */
public static final int BARRING_SERVICE_TYPE_CS_FALLBACK =
- android.hardware.radio.V1_5.BarringInfo.ServiceType.CS_FALLBACK;
+ android.hardware.radio.network.BarringInfo.SERVICE_TYPE_CS_FALLBACK;
/** Barring indicator for MMTEL (IMS) voice; applicable to EUTRAN and NGRAN */
public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE =
- android.hardware.radio.V1_5.BarringInfo.ServiceType.MMTEL_VOICE;
+ android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MMTEL_VOICE;
/** Barring indicator for MMTEL (IMS) video; applicable to EUTRAN and NGRAN */
public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO =
- android.hardware.radio.V1_5.BarringInfo.ServiceType.MMTEL_VIDEO;
+ android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MMTEL_VIDEO;
/* Applicable to UTRAN, EUTRAN, NGRAN */
/** Barring indicator for emergency services; applicable to UTRAN, EUTRAN, and NGRAN */
public static final int BARRING_SERVICE_TYPE_EMERGENCY =
- android.hardware.radio.V1_5.BarringInfo.ServiceType.EMERGENCY;
+ android.hardware.radio.network.BarringInfo.SERVICE_TYPE_EMERGENCY;
/** Barring indicator for SMS sending; applicable to UTRAN, EUTRAN, and NGRAN */
public static final int BARRING_SERVICE_TYPE_SMS =
- android.hardware.radio.V1_5.BarringInfo.ServiceType.SMS;
+ android.hardware.radio.network.BarringInfo.SERVICE_TYPE_SMS;
//TODO: add barring constants for Operator-Specific barring codes
@@ -112,13 +112,13 @@ public final class BarringInfo implements Parcelable {
/** Barring is inactive */
public static final int BARRING_TYPE_NONE =
- android.hardware.radio.V1_5.BarringInfo.BarringType.NONE;
+ android.hardware.radio.network.BarringInfo.BARRING_TYPE_NONE;
/** The service is barred */
public static final int BARRING_TYPE_UNCONDITIONAL =
- android.hardware.radio.V1_5.BarringInfo.BarringType.UNCONDITIONAL;
+ android.hardware.radio.network.BarringInfo.BARRING_TYPE_UNCONDITIONAL;
/** The service may be barred based on additional factors */
public static final int BARRING_TYPE_CONDITIONAL =
- android.hardware.radio.V1_5.BarringInfo.BarringType.CONDITIONAL;
+ android.hardware.radio.network.BarringInfo.BARRING_TYPE_CONDITIONAL;
/** If a modem does not report barring info, then the barring type will be UNKNOWN */
public static final int BARRING_TYPE_UNKNOWN = -1;
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index b0552b4a18a3..d8f63df9a71f 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -63,9 +63,9 @@ public final class NetworkRegistrationInfo implements Parcelable {
/** Unknown / Unspecified domain */
public static final int DOMAIN_UNKNOWN = 0;
/** Circuit switched domain */
- public static final int DOMAIN_CS = android.hardware.radio.V1_5.Domain.CS;
+ public static final int DOMAIN_CS = android.hardware.radio.network.Domain.CS;
/** Packet switched domain */
- public static final int DOMAIN_PS = android.hardware.radio.V1_5.Domain.PS;
+ public static final int DOMAIN_PS = android.hardware.radio.network.Domain.PS;
/** Applicable to both CS and PS Domain */
public static final int DOMAIN_CS_PS = DOMAIN_CS | DOMAIN_PS;
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 28ea5a681730..1b5c53749b9b 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -22,7 +22,7 @@ import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.content.ContentValues;
import android.database.Cursor;
-import android.hardware.radio.V1_5.ApnTypes;
+import android.hardware.radio.data.ApnTypes;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -116,12 +116,11 @@ public class ApnSetting implements Parcelable {
/** APN type for XCAP. */
public static final int TYPE_XCAP = ApnTypes.XCAP;
/** APN type for VSIM. */
- public static final int TYPE_VSIM = 1 << 12; // TODO: Refer to ApnTypes.VSIM
+ public static final int TYPE_VSIM = ApnTypes.VSIM;
/** APN type for BIP. */
- public static final int TYPE_BIP = 1 << 13; // TODO: Refer to ApnTypes.BIP
+ public static final int TYPE_BIP = ApnTypes.BIP;
/** APN type for ENTERPRISE. */
- public static final int TYPE_ENTERPRISE = 1 << 14; //TODO: In future should be referenced from
- // hardware.interfaces.radio.data.ApnTypes
+ public static final int TYPE_ENTERPRISE = ApnTypes.ENTERPRISE;
/** @hide */
@IntDef(flag = true, prefix = {"TYPE_"}, value = {
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index a0d9c1bdf1ed..baa160e50cad 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -49,17 +49,13 @@ public final class QosBearerFilter implements Parcelable {
public @interface QosProtocol {}
public static final int QOS_PROTOCOL_UNSPECIFIED =
- android.hardware.radio.V1_6.QosProtocol.UNSPECIFIED;
- public static final int QOS_PROTOCOL_TCP = android.hardware.radio.V1_6.QosProtocol.TCP;
- public static final int QOS_PROTOCOL_UDP = android.hardware.radio.V1_6.QosProtocol.UDP;
- public static final int QOS_PROTOCOL_ESP = android.hardware.radio.V1_6.QosProtocol.ESP;
- public static final int QOS_PROTOCOL_AH = android.hardware.radio.V1_6.QosProtocol.AH;
- public static final int QOS_MIN_PORT = android.hardware.radio.V1_6.QosPortRange.MIN;
- /**
- * Hardcoded in place of android.hardware.radio.V1_6.QosPortRange.MAX as it
- * returns -1 due to uint16_t to int conversion in java. (TODO: Fix the HAL)
- */
- public static final int QOS_MAX_PORT = 65535; // android.hardware.radio.V1_6.QosPortRange.MIN;
+ android.hardware.radio.data.QosFilter.PROTOCOL_UNSPECIFIED;
+ public static final int QOS_PROTOCOL_TCP = android.hardware.radio.data.QosFilter.PROTOCOL_TCP;
+ public static final int QOS_PROTOCOL_UDP = android.hardware.radio.data.QosFilter.PROTOCOL_UDP;
+ public static final int QOS_PROTOCOL_ESP = android.hardware.radio.data.QosFilter.PROTOCOL_ESP;
+ public static final int QOS_PROTOCOL_AH = android.hardware.radio.data.QosFilter.PROTOCOL_AH;
+ public static final int QOS_MIN_PORT = android.hardware.radio.data.PortRange.PORT_RANGE_MIN;
+ public static final int QOS_MAX_PORT = android.hardware.radio.data.PortRange.PORT_RANGE_MAX;
private @QosProtocol int protocol;
@@ -78,11 +74,11 @@ public final class QosBearerFilter implements Parcelable {
public @interface QosBearerFilterDirection {}
public static final int QOS_FILTER_DIRECTION_DOWNLINK =
- android.hardware.radio.V1_6.QosFilterDirection.DOWNLINK;
+ android.hardware.radio.data.QosFilter.DIRECTION_DOWNLINK;
public static final int QOS_FILTER_DIRECTION_UPLINK =
- android.hardware.radio.V1_6.QosFilterDirection.UPLINK;
+ android.hardware.radio.data.QosFilter.DIRECTION_UPLINK;
public static final int QOS_FILTER_DIRECTION_BIDIRECTIONAL =
- android.hardware.radio.V1_6.QosFilterDirection.BIDIRECTIONAL;
+ android.hardware.radio.data.QosFilter.DIRECTION_BIDIRECTIONAL;
private @QosBearerFilterDirection int filterDirection;
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index 64bcf7171ade..b429407d8f31 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -19,8 +19,7 @@ package android.telephony.emergency;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
-import android.hardware.radio.V1_4.EmergencyNumberSource;
-import android.hardware.radio.V1_4.EmergencyServiceCategory;
+import android.hardware.radio.voice.EmergencyServiceCategory;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.CarrierConfigManager;
@@ -172,13 +171,14 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu
* Reference: 3gpp 22.101, Section 10 - Emergency Calls
*/
public static final int EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING =
- EmergencyNumberSource.NETWORK_SIGNALING;
+ android.hardware.radio.voice.EmergencyNumber.SOURCE_NETWORK_SIGNALING;
/**
* Bit-field which indicates the number is from the sim.
*
* Reference: 3gpp 22.101, Section 10 - Emergency Calls
*/
- public static final int EMERGENCY_NUMBER_SOURCE_SIM = EmergencyNumberSource.SIM;
+ public static final int EMERGENCY_NUMBER_SOURCE_SIM =
+ android.hardware.radio.voice.EmergencyNumber.SOURCE_SIM;
/**
* Bit-field which indicates the number is from the platform-maintained database.
*/
@@ -192,7 +192,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu
public static final int EMERGENCY_NUMBER_SOURCE_TEST = 1 << 5;
/** Bit-field which indicates the number is from the modem config. */
public static final int EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG =
- EmergencyNumberSource.MODEM_CONFIG;
+ android.hardware.radio.voice.EmergencyNumber.SOURCE_MODEM_CONFIG;
/**
* Bit-field which indicates the number is available as default.
*
@@ -201,7 +201,8 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu
*
* Reference: 3gpp 22.101, Section 10 - Emergency Calls
*/
- public static final int EMERGENCY_NUMBER_SOURCE_DEFAULT = EmergencyNumberSource.DEFAULT;
+ public static final int EMERGENCY_NUMBER_SOURCE_DEFAULT =
+ android.hardware.radio.voice.EmergencyNumber.SOURCE_DEFAULT;
private static final Set<Integer> EMERGENCY_NUMBER_SOURCE_SET;
static {
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 4ba538ed9d45..2f96eecb27e3 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -23,14 +23,49 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-android_test {
- name: "FlickerTests",
+filegroup {
+ name: "FlickerTestsBase-src",
+ srcs: ["src/com/android/server/wm/flicker/*.kt"],
+}
+
+filegroup {
+ name: "FlickerTestsAppClose-src",
+ srcs: ["src/**/close/*.kt"],
+}
+
+filegroup {
+ name: "FlickerTestsActivityEmbedding-src",
srcs: [
- "src/**/*.java",
- "src/**/*.kt",
+ "src/**/activityembedding/*.kt",
+ "src/**/activityembedding/close/*.kt",
+ "src/**/activityembedding/rotation/*.kt",
],
- manifest: "AndroidManifest.xml",
- test_config: "AndroidTest.xml",
+}
+
+filegroup {
+ name: "FlickerTestsIme-src",
+ srcs: ["src/**/ime/*.kt"],
+}
+
+filegroup {
+ name: "FlickerTestsAppLaunch-src",
+ srcs: ["src/**/launch/*.kt"],
+}
+
+filegroup {
+ name: "FlickerTestsQuickswitch-src",
+ srcs: ["src/**/quickswitch/*.kt"],
+}
+
+filegroup {
+ name: "FlickerTestsRotation-src",
+ srcs: ["src/**/rotation/*.kt"],
+}
+
+java_defaults {
+ name: "FlickerTestsDefault",
+ manifest: "manifests/AndroidManifest.xml",
+ test_config_template: "AndroidTestTemplate.xml",
platform_apis: true,
certificate: "platform",
optimize: {
@@ -42,19 +77,100 @@ android_test {
"androidx.test.ext.junit",
"flickertestapplib",
"flickerlib",
- "flickerlib-apphelpers",
"flickerlib-helpers",
- "truth-prebuilt",
- "launcher-helper-lib",
- "launcher-aosp-tapl",
"platform-test-annotations",
- "wm-flicker-window-extensions",
+ "wm-flicker-common-app-helpers",
],
data: [
":FlickerTestApp",
],
}
+android_test {
+ name: "FlickerTestsOther",
+ defaults: ["FlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestOther.xml"],
+ package_name: "com.android.server.wm.flicker",
+ instrumentation_target_package: "com.android.server.wm.flicker",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ exclude_srcs: [
+ ":FlickerTestsAppClose-src",
+ ":FlickerTestsIme-src",
+ ":FlickerTestsAppLaunch-src",
+ ":FlickerTestsQuickswitch-src",
+ ":FlickerTestsRotation-src",
+ ],
+}
+
+android_test {
+ name: "FlickerTestsAppClose",
+ defaults: ["FlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestAppClose.xml"],
+ package_name: "com.android.server.wm.flicker.close",
+ instrumentation_target_package: "com.android.server.wm.flicker.close",
+ srcs: [
+ ":FlickerTestsBase-src",
+ ":FlickerTestsAppClose-src",
+ ],
+ exclude_srcs: [
+ ":FlickerTestsActivityEmbedding-src",
+ ],
+}
+
+android_test {
+ name: "FlickerTestsIme",
+ defaults: ["FlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestIme.xml"],
+ package_name: "com.android.server.wm.flicker.ime",
+ instrumentation_target_package: "com.android.server.wm.flicker.ime",
+ srcs: [
+ ":FlickerTestsBase-src",
+ ":FlickerTestsIme-src",
+ ],
+}
+
+android_test {
+ name: "FlickerTestsAppLaunch",
+ defaults: ["FlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+ package_name: "com.android.server.wm.flicker.launch",
+ instrumentation_target_package: "com.android.server.wm.flicker.launch",
+ srcs: [
+ ":FlickerTestsBase-src",
+ ":FlickerTestsAppLaunch-src",
+ ],
+}
+
+android_test {
+ name: "FlickerTestsQuickswitch",
+ defaults: ["FlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestQuickswitch.xml"],
+ package_name: "com.android.server.wm.flicker.rotation",
+ instrumentation_target_package: "com.android.server.wm.flicker.rotation",
+ srcs: [
+ ":FlickerTestsBase-src",
+ ":FlickerTestsQuickswitch-src",
+ ],
+}
+
+android_test {
+ name: "FlickerTestsRotation",
+ defaults: ["FlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestRotation.xml"],
+ package_name: "com.android.server.wm.flicker.rotation",
+ instrumentation_target_package: "com.android.server.wm.flicker.rotation",
+ srcs: [
+ ":FlickerTestsBase-src",
+ ":FlickerTestsRotation-src",
+ ],
+ exclude_srcs: [
+ ":FlickerTestsActivityEmbedding-src",
+ ],
+}
+
java_library {
name: "wm-flicker-common-assertions",
platform_apis: true,
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index 32ff243921ec..1176828d47c3 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -2,7 +2,7 @@
<!--
* Copyright 2018 Google Inc. All Rights Reserved.
-->
-<configuration description="Runs WindowManager Flicker Tests">
+<configuration description="Runs WindowManager {MODULE}">
<option name="test-tag" value="FlickerTests" />
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<!-- keeps the screen on during tests -->
@@ -36,11 +36,11 @@
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="FlickerTests.apk"/>
+ <option name="test-file-name" value="{MODULE}.apk"/>
<option name="test-file-name" value="FlickerTestApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.server.wm.flicker"/>
+ <option name="package" value="{PACKAGE}"/>
<option name="shell-timeout" value="6600s" />
<option name="test-timeout" value="6600s" />
<option name="hidden-api-checks" value="false" />
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/manifests/AndroidManifest.xml
index 462f91bc081c..5b00310cd874 100644
--- a/tests/FlickerTests/AndroidManifest.xml
+++ b/tests/FlickerTests/manifests/AndroidManifest.xml
@@ -1,18 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.server.wm.flicker">
@@ -48,8 +49,8 @@
<uses-library android:name="androidx.window.extensions" android:required="false"/>
</application>
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.server.wm.flicker"
- android:label="WindowManager Flicker Tests">
- </instrumentation>
+ <!--<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="{{package}}"
+ android:label="WindowManager Flicker Tests {MODULE}">
+ </instrumentation>-->
</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestAppClose.xml b/tests/FlickerTests/manifests/AndroidManifestAppClose.xml
new file mode 100644
index 000000000000..4cdcb903b498
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestAppClose.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.wm.flicker.close">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.wm.flicker.close"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml b/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml
new file mode 100644
index 000000000000..659a745ba480
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.wm.flicker.launch">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.wm.flicker.launch"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestIme.xml b/tests/FlickerTests/manifests/AndroidManifestIme.xml
new file mode 100644
index 000000000000..abd03af4888a
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestIme.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.wm.flicker.ime">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.wm.flicker.ime"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestOther.xml b/tests/FlickerTests/manifests/AndroidManifestOther.xml
new file mode 100644
index 000000000000..47749b8133b1
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestOther.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.wm.flicker">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.wm.flicker"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml b/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml
new file mode 100644
index 000000000000..203035d30584
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.wm.flicker.quickswitch">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.wm.flicker.quickswitch"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestRotation.xml b/tests/FlickerTests/manifests/AndroidManifestRotation.xml
new file mode 100644
index 000000000000..2852cf23a35b
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestRotation.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.wm.flicker.rotation">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.wm.flicker.rotation"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>