summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Activity.java2
-rw-r--r--core/java/android/app/ActivityOptions.java5
-rw-r--r--core/java/android/content/integrity/AppInstallMetadata.java15
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java6
-rw-r--r--core/java/android/view/RemoteAnimationTarget.java18
-rw-r--r--core/java/android/view/ViewRootImpl.java4
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java7
-rw-r--r--core/java/android/window/ProxyOnBackInvokedDispatcher.java2
-rw-r--r--core/java/android/window/TransitionInfo.java6
-rw-r--r--core/java/com/android/internal/app/BlockedAppStreamingActivity.java17
-rw-r--r--core/java/com/android/internal/util/LatencyTracker.java10
-rw-r--r--core/java/com/android/internal/util/OWNERS1
-rw-r--r--core/java/com/android/internal/util/ScreenshotHelper.java200
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java2
-rw-r--r--core/res/res/values/strings.xml20
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--core/tests/screenshothelpertests/OWNERS2
-rw-r--r--core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java82
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTrigger.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTriggerOverride.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java160
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java106
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java87
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java137
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/SplitscreenPipMixedHandler.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java67
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java64
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java65
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java80
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java15
-rw-r--r--packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml2
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml2
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java1
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java1
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java2
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java2
-rw-r--r--packages/SettingsLib/SettingsTransition/AndroidManifest.xml2
-rw-r--r--packages/SettingsLib/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java7
-rw-r--r--packages/SystemUI/Android.bp26
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt8
-rw-r--r--packages/SystemUI/docs/device-entry/keyguard.md4
-rw-r--r--packages/SystemUI/docs/device-entry/quickaffordance.md25
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt21
-rw-r--r--packages/SystemUI/proguard.flags5
-rw-r--r--packages/SystemUI/res-keyguard/font/clock.xml28
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml32
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml2
-rw-r--r--packages/SystemUI/res/layout/keyguard_bottom_area.xml24
-rw-r--r--packages/SystemUI/res/values-sw600dp/config.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml13
-rw-r--r--packages/SystemUI/shared/res/layout/clock_default_large.xml1
-rw-r--r--packages/SystemUI/shared/res/layout/clock_default_small.xml2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt18
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt68
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt40
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java9
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt153
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java182
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java216
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java47
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java349
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt326
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java111
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java (renamed from packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java)51
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt121
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt181
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt307
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt176
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogMessage.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt117
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java631
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt259
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java68
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java218
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java128
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java)12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt148
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt179
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt74
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt133
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt193
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt162
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt47
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt178
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt161
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt503
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt167
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt125
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java375
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java467
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt100
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt252
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt166
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java67
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java3
-rw-r--r--packages/SystemUI/tests/utils/AndroidManifest.xml23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java (renamed from packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java (renamed from packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/TestableDependency.java (renamed from packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerFake.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java (renamed from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java (renamed from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSession.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SbnBuilder.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeThreadFactory.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/io/FakeBasicFileAttributes.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeProximitySensor.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeThresholdSensor.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/wakelock/WakeLockFake.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/BaseLeakChecker.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeExtensionController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeHotspotController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeTunerService.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeUserInfoController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeZenModeController.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/os/FakeHandler.java (renamed from packages/SystemUI/tests/src/com/android/systemui/utils/os/FakeHandler.java)0
-rw-r--r--services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java7
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java1
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java3
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java42
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java7
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java6
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java29
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java42
-rw-r--r--services/core/java/com/android/server/wm/Letterbox.java5
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java32
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java59
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java4
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java14
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java21
-rw-r--r--services/core/java/com/android/server/wm/Transition.java8
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java6
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java68
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java104
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java20
320 files changed, 9337 insertions, 4356 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 90c37d1999e1..4ea0c32d3b36 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -308,7 +308,7 @@ import java.util.function.Consumer;
* <li>The <b>foreground lifetime</b> of an activity happens between a call to
* {@link android.app.Activity#onResume} until a corresponding call to
* {@link android.app.Activity#onPause}. During this time the activity is
- * in visible, active and interacting with the user. An activity
+ * visible, active and interacting with the user. An activity
* can frequently go between the resumed and paused states -- for example when
* the device goes to sleep, when an activity result is delivered, when a new
* intent is delivered -- so the code in these methods should be fairly
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 4fc3254ed1a3..61cca00f76ac 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1441,6 +1441,11 @@ public class ActivityOptions extends ComponentOptions {
}
/** @hide */
+ public void setRemoteTransition(@Nullable RemoteTransition remoteTransition) {
+ mRemoteTransition = remoteTransition;
+ }
+
+ /** @hide */
public static ActivityOptions fromBundle(Bundle bOptions) {
return bOptions != null ? new ActivityOptions(bOptions) : null;
}
diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java
index 9874890ed09e..91b000774457 100644
--- a/core/java/android/content/integrity/AppInstallMetadata.java
+++ b/core/java/android/content/integrity/AppInstallMetadata.java
@@ -129,9 +129,18 @@ public final class AppInstallMetadata {
@Override
public String toString() {
return String.format(
- "AppInstallMetadata { PackageName = %s, AppCerts = %s, InstallerName = %s,"
- + " InstallerCerts = %s, VersionCode = %d, PreInstalled = %b, StampPresent ="
- + " %b, StampVerified = %b, StampTrusted = %b, StampCert = %s }",
+ "AppInstallMetadata {"
+ + " PackageName = %s,"
+ + " AppCerts = %s,"
+ + " AppCertsLineage = %s,"
+ + " InstallerName = %s,"
+ + " InstallerCerts = %s,"
+ + " VersionCode = %d,"
+ + " PreInstalled = %b,"
+ + " StampPresent = %b,"
+ + " StampVerified = %b,"
+ + " StampTrusted = %b,"
+ + " StampCert = %s }",
mPackageName,
mAppCertificates,
mAppCertificateLineage,
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 336ef7ac78db..41822e77f953 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1693,8 +1693,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
((i != idx) || notifyCurrentIndex)) {
TotalCaptureResult result = previewMap.valueAt(i).second;
Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
- mCaptureResultHandler.onCaptureCompleted(timestamp,
- initializeFilteredResults(result));
+ if (mCaptureResultHandler != null) {
+ mCaptureResultHandler.onCaptureCompleted(timestamp,
+ initializeFilteredResults(result));
+ }
Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
final long ident = Binder.clearCallingIdentity();
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index a468951c5020..44f419a8097d 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -45,6 +45,7 @@ import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.proto.ProtoOutputStream;
+import android.window.TaskSnapshot;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -234,6 +235,12 @@ public class RemoteAnimationTarget implements Parcelable {
*/
public @ColorInt int backgroundColor;
+ /**
+ * Whether the activity is going to show IME on the target window after the app transition.
+ * @see TaskSnapshot#hasImeSurface() that used the task snapshot during animating task.
+ */
+ public boolean willShowImeOnTarget;
+
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
@@ -294,12 +301,21 @@ public class RemoteAnimationTarget implements Parcelable {
hasAnimatingParent = in.readBoolean();
backgroundColor = in.readInt();
showBackdrop = in.readBoolean();
+ willShowImeOnTarget = in.readBoolean();
}
public void setShowBackdrop(boolean shouldShowBackdrop) {
showBackdrop = shouldShowBackdrop;
}
+ public void setWillShowImeOnTarget(boolean showImeOnTarget) {
+ willShowImeOnTarget = showImeOnTarget;
+ }
+
+ public boolean willShowImeOnTarget() {
+ return willShowImeOnTarget;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -328,6 +344,7 @@ public class RemoteAnimationTarget implements Parcelable {
dest.writeBoolean(hasAnimatingParent);
dest.writeInt(backgroundColor);
dest.writeBoolean(showBackdrop);
+ dest.writeBoolean(willShowImeOnTarget);
}
public void dump(PrintWriter pw, String prefix) {
@@ -350,6 +367,7 @@ public class RemoteAnimationTarget implements Parcelable {
pw.print(prefix); pw.print("hasAnimatingParent="); pw.print(hasAnimatingParent);
pw.print(prefix); pw.print("backgroundColor="); pw.print(backgroundColor);
pw.print(prefix); pw.print("showBackdrop="); pw.print(showBackdrop);
+ pw.print(prefix); pw.print("willShowImeOnTarget="); pw.print(willShowImeOnTarget);
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 427aa0f3914e..7690af693148 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -10879,8 +10879,8 @@ public final class ViewRootImpl implements ViewParent,
private void registerCompatOnBackInvokedCallback() {
mCompatOnBackInvokedCallback = () -> {
- sendBackKeyEvent(KeyEvent.ACTION_DOWN);
- sendBackKeyEvent(KeyEvent.ACTION_UP);
+ sendBackKeyEvent(KeyEvent.ACTION_DOWN);
+ sendBackKeyEvent(KeyEvent.ACTION_UP);
};
mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatOnBackInvokedCallback);
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 98ef4e7ae6fa..90384b520315 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -64,6 +64,7 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -630,7 +631,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
mComponentName = null;
mEvents = null;
if (mDirectServiceInterface != null) {
- mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
+ try {
+ mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "IContentCaptureDirectManager does not exist");
+ }
}
mDirectServiceInterface = null;
mHandler.removeMessages(MSG_FLUSH);
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 8ad109317f4b..49acde9dc295 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -122,7 +122,7 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
int priority = callbackPair.second;
- if (priority >= 0) {
+ if (priority >= PRIORITY_DEFAULT) {
mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
} else {
mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 1f3841ad8b58..b263b08d6abc 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -116,6 +116,9 @@ public final class TransitionInfo implements Parcelable {
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
public static final int FLAG_FIRST_CUSTOM = 1 << 10;
+ /** The container is going to show IME on its task after the transition. */
+ public static final int FLAG_WILL_IME_SHOWN = 1 << 11;
+
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
@@ -129,7 +132,8 @@ public final class TransitionInfo implements Parcelable {
FLAG_DISPLAY_HAS_ALERT_WINDOWS,
FLAG_IS_INPUT_METHOD,
FLAG_IS_EMBEDDED,
- FLAG_FIRST_CUSTOM
+ FLAG_FIRST_CUSTOM,
+ FLAG_WILL_IME_SHOWN
})
public @interface ChangeFlags {}
diff --git a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
index ca111a49235e..7c248927be80 100644
--- a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
+++ b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
@@ -36,6 +36,7 @@ public class BlockedAppStreamingActivity extends AlertActivity {
private static final String EXTRA_BLOCKED_ACTIVITY_INFO =
PACKAGE_NAME + ".extra.BLOCKED_ACTIVITY_INFO";
private static final String EXTRA_STREAMED_DEVICE = PACKAGE_NAME + ".extra.STREAMED_DEVICE";
+ private static final String BLOCKED_COMPONENT_PLAYSTORE = "com.android.vending";
private static final String BLOCKED_COMPONENT_SETTINGS = "com.android.settings";
@Override
@@ -62,21 +63,25 @@ public class BlockedAppStreamingActivity extends AlertActivity {
mAlertParams.mTitle =
getString(R.string.app_streaming_blocked_title_for_permission_dialog);
mAlertParams.mMessage =
- getString(R.string.app_streaming_blocked_message_for_permission_dialog,
- streamedDeviceName);
+ getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+ } else if (TextUtils.equals(activityInfo.packageName, BLOCKED_COMPONENT_PLAYSTORE)) {
+ mAlertParams.mTitle =
+ getString(R.string.app_streaming_blocked_title_for_playstore_dialog);
+ mAlertParams.mMessage =
+ getString(R.string.app_streaming_blocked_message, streamedDeviceName);
} else if (TextUtils.equals(activityInfo.packageName, BLOCKED_COMPONENT_SETTINGS)) {
mAlertParams.mTitle =
getString(R.string.app_streaming_blocked_title_for_settings_dialog);
mAlertParams.mMessage =
- getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+ getString(R.string.app_streaming_blocked_message_for_settings_dialog,
+ streamedDeviceName);
} else {
- mAlertParams.mTitle =
- getString(R.string.app_streaming_blocked_title, appLabel);
+ // No title required
mAlertParams.mMessage =
getString(R.string.app_streaming_blocked_message, streamedDeviceName);
}
} else {
- mAlertParams.mTitle = getString(R.string.app_blocked_title);
+ // No title required
mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
}
mAlertParams.mPositiveButtonText = getString(android.R.string.ok);
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index ca40a4052ca1..14a6d5e4d08c 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -142,6 +142,11 @@ public class LatencyTracker {
*/
public static final int ACTION_LOAD_SHARE_SHEET = 16;
+ /**
+ * Time it takes to show AOD display after folding the device.
+ */
+ public static final int ACTION_FOLD_TO_AOD = 17;
+
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -160,6 +165,7 @@ public class LatencyTracker {
ACTION_UDFPS_ILLUMINATE,
ACTION_SHOW_BACK_ARROW,
ACTION_LOAD_SHARE_SHEET,
+ ACTION_FOLD_TO_AOD,
};
/** @hide */
@@ -181,6 +187,7 @@ public class LatencyTracker {
ACTION_UDFPS_ILLUMINATE,
ACTION_SHOW_BACK_ARROW,
ACTION_LOAD_SHARE_SHEET,
+ ACTION_FOLD_TO_AOD
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -204,6 +211,7 @@ public class LatencyTracker {
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
+ FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD
};
private static LatencyTracker sLatencyTracker;
@@ -297,6 +305,8 @@ public class LatencyTracker {
return "ACTION_SHOW_BACK_ARROW";
case 17:
return "ACTION_LOAD_SHARE_SHEET";
+ case 19:
+ return "ACTION_FOLD_TO_AOD";
default:
throw new IllegalArgumentException("Invalid action");
}
diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS
index 354dd9a41586..1808bd56b562 100644
--- a/core/java/com/android/internal/util/OWNERS
+++ b/core/java/com/android/internal/util/OWNERS
@@ -5,3 +5,4 @@ per-file *ContrastColor* = file:/services/core/java/com/android/server/notificat
per-file Protocol* = etancohen@google.com, lorenzo@google.com
per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com
per-file *Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
+per-file *Screenshot* = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index f4f438b1f601..8af2450f29ef 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,7 +1,6 @@
package com.android.internal.util;
import static android.content.Intent.ACTION_USER_SWITCHED;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,6 +28,10 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.WindowManager;
+import android.view.WindowManager.ScreenshotSource;
+import android.view.WindowManager.ScreenshotType;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
import java.util.function.Consumer;
@@ -42,24 +45,28 @@ public class ScreenshotHelper {
* Describes a screenshot request (to make it easier to pass data through to the handler).
*/
public static class ScreenshotRequest implements Parcelable {
- private int mSource;
- private boolean mHasStatusBar;
- private boolean mHasNavBar;
- private Bundle mBitmapBundle;
- private Rect mBoundsInScreen;
- private Insets mInsets;
- private int mTaskId;
- private int mUserId;
- private ComponentName mTopComponent;
-
- ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) {
+ private final int mSource;
+ private final Bundle mBitmapBundle;
+ private final Rect mBoundsInScreen;
+ private final Insets mInsets;
+ private final int mTaskId;
+ private final int mUserId;
+ private final ComponentName mTopComponent;
+
+ @VisibleForTesting
+ public ScreenshotRequest(int source) {
mSource = source;
- mHasStatusBar = hasStatus;
- mHasNavBar = hasNav;
+ mBitmapBundle = null;
+ mBoundsInScreen = null;
+ mInsets = null;
+ mTaskId = -1;
+ mUserId = -1;
+ mTopComponent = null;
}
- ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen, Insets insets,
- int taskId, int userId, ComponentName topComponent) {
+ @VisibleForTesting
+ public ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen,
+ Insets insets, int taskId, int userId, ComponentName topComponent) {
mSource = source;
mBitmapBundle = bitmapBundle;
mBoundsInScreen = boundsInScreen;
@@ -71,16 +78,21 @@ public class ScreenshotHelper {
ScreenshotRequest(Parcel in) {
mSource = in.readInt();
- mHasStatusBar = in.readBoolean();
- mHasNavBar = in.readBoolean();
-
if (in.readInt() == 1) {
mBitmapBundle = in.readBundle(getClass().getClassLoader());
- mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), android.graphics.Rect.class);
- mInsets = in.readParcelable(Insets.class.getClassLoader(), android.graphics.Insets.class);
+ mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
+ mInsets = in.readParcelable(Insets.class.getClassLoader(), Insets.class);
mTaskId = in.readInt();
mUserId = in.readInt();
- mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
+ mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(),
+ ComponentName.class);
+ } else {
+ mBitmapBundle = null;
+ mBoundsInScreen = null;
+ mInsets = null;
+ mTaskId = -1;
+ mUserId = -1;
+ mTopComponent = null;
}
}
@@ -88,14 +100,6 @@ public class ScreenshotHelper {
return mSource;
}
- public boolean getHasStatusBar() {
- return mHasStatusBar;
- }
-
- public boolean getHasNavBar() {
- return mHasNavBar;
- }
-
public Bundle getBitmapBundle() {
return mBitmapBundle;
}
@@ -112,7 +116,6 @@ public class ScreenshotHelper {
return mTaskId;
}
-
public int getUserId() {
return mUserId;
}
@@ -129,8 +132,6 @@ public class ScreenshotHelper {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mSource);
- dest.writeBoolean(mHasStatusBar);
- dest.writeBoolean(mHasNavBar);
if (mBitmapBundle == null) {
dest.writeInt(0);
} else {
@@ -144,7 +145,8 @@ public class ScreenshotHelper {
}
}
- public static final @NonNull Parcelable.Creator<ScreenshotRequest> CREATOR =
+ @NonNull
+ public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
new Parcelable.Creator<ScreenshotRequest>() {
@Override
@@ -254,113 +256,71 @@ public class ScreenshotHelper {
/**
* Request a screenshot be taken.
- *
+ * <p>
* Added to support reducing unit test duration; the method variant without a timeout argument
* is recommended for general use.
*
- * @param screenshotType The type of screenshot, for example either
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
- * or
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
- * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
- * if not.
- * @param hasNav {@code true} if the navigation bar is currently showing. {@code
- * false} if not.
- * @param source The source of the screenshot request. One of
- * {SCREENSHOT_GLOBAL_ACTIONS, SCREENSHOT_KEY_CHORD,
- * SCREENSHOT_OVERVIEW, SCREENSHOT_OTHER}
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
+ * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+ * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+ * @param handler used to process messages received from the screenshot service
+ * @param completionConsumer receives the URI of the captured screenshot, once saved or
+ * null if no screenshot was saved
*/
- public void takeScreenshot(final int screenshotType, final boolean hasStatus,
- final boolean hasNav, int source, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav);
- takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest,
- completionConsumer);
- }
-
- /**
- * Request a screenshot be taken, with provided reason.
- *
- * @param screenshotType The type of screenshot, for example either
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
- * or
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
- * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
- * if
- * not.
- * @param hasNav {@code true} if the navigation bar is currently showing. {@code
- * false}
- * if not.
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
- */
- public void takeScreenshot(final int screenshotType, final boolean hasStatus,
- final boolean hasNav, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler,
+ public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+ @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
+ takeScreenshot(screenshotType, handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
completionConsumer);
}
/**
- * Request a screenshot be taken with a specific timeout.
- *
+ * Request a screenshot be taken.
+ * <p>
* Added to support reducing unit test duration; the method variant without a timeout argument
* is recommended for general use.
*
- * @param screenshotType The type of screenshot, for example either
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
- * or
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
- * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
- * if
- * not.
- * @param hasNav {@code true} if the navigation bar is currently showing. {@code
- * false}
- * if not.
- * @param timeoutMs If the screenshot hasn't been completed within this time period,
- * the screenshot attempt will be cancelled and `completionConsumer`
- * will be run.
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
+ * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+ * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+ * @param handler used to process messages received from the screenshot service
+ * @param timeoutMs time limit for processing, intended only for testing
+ * @param completionConsumer receives the URI of the captured screenshot, once saved or
+ * null if no screenshot was saved
*/
- public void takeScreenshot(final int screenshotType, final boolean hasStatus,
- final boolean hasNav, long timeoutMs, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(SCREENSHOT_OTHER, hasStatus,
- hasNav);
- takeScreenshot(screenshotType, timeoutMs, handler, screenshotRequest, completionConsumer);
+ @VisibleForTesting
+ public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+ @NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
+ takeScreenshot(screenshotType, handler, screenshotRequest, timeoutMs, completionConsumer);
}
/**
* Request that provided image be handled as if it was a screenshot.
*
- * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
- * @param boundsInScreen The bounds in screen coordinates that the bitmap orginated from.
- * @param insets The insets that the image was shown with, inside the screenbounds.
- * @param taskId The taskId of the task that the screen shot was taken of.
- * @param userId The userId of user running the task provided in taskId.
- * @param topComponent The component name of the top component running in the task.
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
+ * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
+ * @param boundsInScreen The bounds in screen coordinates that the bitmap originated from.
+ * @param insets The insets that the image was shown with, inside the screen bounds.
+ * @param taskId The taskId of the task that the screen shot was taken of.
+ * @param userId The userId of user running the task provided in taskId.
+ * @param topComponent The component name of the top component running in the task.
+ * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+ * @param handler A handler used in case the screenshot times out
+ * @param completionConsumer receives the URI of the captured screenshot, once saved or
+ * null if no screenshot was saved
*/
public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
- @NonNull Insets insets, int taskId, int userId, ComponentName topComponent, int source,
- @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest =
- new ScreenshotRequest(source, screenshotBundle, boundsInScreen, insets, taskId,
- userId, topComponent);
- takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS,
- handler, screenshotRequest, completionConsumer);
+ @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
+ @ScreenshotSource int source, @NonNull Handler handler,
+ @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, screenshotBundle,
+ boundsInScreen, insets, taskId, userId, topComponent);
+ takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, handler, screenshotRequest,
+ SCREENSHOT_TIMEOUT_MS,
+ completionConsumer);
}
- private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,
- ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) {
+ private void takeScreenshot(@ScreenshotType int screenshotType, @NonNull Handler handler,
+ ScreenshotRequest screenshotRequest, long timeoutMs,
+ @Nullable Consumer<Uri> completionConsumer) {
synchronized (mScreenshotLock) {
final Runnable mScreenshotTimeout = () -> {
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 09ff4e0aa076..9ee9b8249493 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -828,6 +828,8 @@ public class PointerLocationView extends View implements InputDeviceListener,
mSystemGestureExclusionListener, mContext.getDisplayId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to unregister window manager callbacks", e);
}
}
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d5283852c295..a3b05b57c394 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5479,6 +5479,8 @@
<string name="app_streaming_blocked_title_for_fingerprint_dialog">Continue on phone</string>
<!-- Title of the dialog shown when the microphone permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_title_for_microphone_dialog">Microphone unavailable</string>
+ <!-- Title of the dialog shown when the play store is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_title_for_playstore_dialog">Play Store unavailable</string>
<!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_title_for_settings_dialog" product="tv">Android TV settings unavailable</string>
<!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
@@ -5486,23 +5488,23 @@
<!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_title_for_settings_dialog" product="default">Phone settings unavailable</string>
<!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your Android TV device instead.</string>
+ <string name="app_streaming_blocked_message" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your Android TV device instead.</string>
<!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your tablet instead.</string>
+ <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string>
<!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your phone instead.</string>
- <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message_for_permission_dialog" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your Android TV device instead.</string>
- <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message_for_permission_dialog" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string>
- <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message_for_permission_dialog" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string>
+ <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string>
<!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv">This app is requesting additional security. Try on your Android TV device instead.</string>
<!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet">This app is requesting additional security. Try on your tablet instead.</string>
<!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default">This app is requesting additional security. Try on your phone instead.</string>
+ <!-- Message shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your Android TV device instead.</string>
+ <!-- Message shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your tablet instead.</string>
+ <!-- Message shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your phone instead.</string>
<!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
<string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index adff14a14871..7d4188377be6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3310,10 +3310,11 @@
<java-symbol type="string" name="app_streaming_blocked_title_for_camera_dialog" />
<java-symbol type="string" name="app_streaming_blocked_title_for_fingerprint_dialog" />
<java-symbol type="string" name="app_streaming_blocked_title_for_microphone_dialog" />
+ <java-symbol type="string" name="app_streaming_blocked_title_for_playstore_dialog" />
<java-symbol type="string" name="app_streaming_blocked_title_for_settings_dialog" />
<java-symbol type="string" name="app_streaming_blocked_message" />
- <java-symbol type="string" name="app_streaming_blocked_message_for_permission_dialog" />
<java-symbol type="string" name="app_streaming_blocked_message_for_fingerprint_dialog" />
+ <java-symbol type="string" name="app_streaming_blocked_message_for_settings_dialog" />
<!-- Used internally for assistant to launch activity transitions -->
<java-symbol type="id" name="cross_task_transition" />
diff --git a/core/tests/screenshothelpertests/OWNERS b/core/tests/screenshothelpertests/OWNERS
new file mode 100644
index 000000000000..23dc8fb140ad
--- /dev/null
+++ b/core/tests/screenshothelpertests/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 801321
+file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index 4b8173732b4d..fd4fb133ef12 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -80,13 +80,14 @@ public final class ScreenshotHelperTest {
@Test
public void testFullscreenScreenshot() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, mHandler, null);
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+ WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
}
@Test
public void testSelectedRegionScreenshot() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION, false, false, mHandler,
- null);
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION,
+ WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
}
@Test
@@ -101,8 +102,10 @@ public final class ScreenshotHelperTest {
long timeoutMs = 10;
CountDownLatch lock = new CountDownLatch(1);
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, timeoutMs,
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+ WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
mHandler,
+ timeoutMs,
uri -> {
assertNull(uri);
lock.countDown();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 4080b99d30cf..5cba3b4ec130 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -31,7 +31,6 @@ import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
-import android.content.Context;
import android.content.LocusId;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
@@ -56,6 +55,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldAnimationController;
import java.io.PrintWriter;
@@ -186,39 +186,43 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
@Nullable
private RunningTaskInfo mLastFocusedTaskInfo;
- public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
- this(null /* taskOrganizerController */, mainExecutor, context, null /* compatUI */,
+ public ShellTaskOrganizer(ShellExecutor mainExecutor) {
+ this(null /* shellInit */, null /* taskOrganizerController */, null /* compatUI */,
Optional.empty() /* unfoldAnimationController */,
- Optional.empty() /* recentTasksController */);
+ Optional.empty() /* recentTasksController */,
+ mainExecutor);
}
- public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
- CompatUIController compatUI) {
- this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
- Optional.empty() /* unfoldAnimationController */,
- Optional.empty() /* recentTasksController */);
- }
-
- public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
- CompatUIController compatUI,
+ public ShellTaskOrganizer(ShellInit shellInit,
+ @Nullable CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasks) {
- this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
- unfoldAnimationController, recentTasks);
+ Optional<RecentTasksController> recentTasks,
+ ShellExecutor mainExecutor) {
+ this(shellInit, null /* taskOrganizerController */, compatUI,
+ unfoldAnimationController, recentTasks, mainExecutor);
}
@VisibleForTesting
- protected ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
- ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI,
+ protected ShellTaskOrganizer(ShellInit shellInit,
+ ITaskOrganizerController taskOrganizerController,
+ @Nullable CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasks) {
+ Optional<RecentTasksController> recentTasks,
+ ShellExecutor mainExecutor) {
super(taskOrganizerController, mainExecutor);
mCompatUI = compatUI;
mRecentTasks = recentTasks;
mUnfoldAnimationController = unfoldAnimationController.orElse(null);
- if (compatUI != null) {
- compatUI.setCompatUICallback(this);
+ if (shellInit != null) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ if (mCompatUI != null) {
+ mCompatUI.setCompatUICallback(this);
}
+ registerOrganizer();
}
@Override
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 82b0270698e4..b305897b77ae 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
@@ -28,6 +28,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
/**
@@ -38,13 +39,17 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
private final Context mContext;
private final Transitions mTransitions;
- public ActivityEmbeddingController(Context context, Transitions transitions) {
+ public ActivityEmbeddingController(Context context, ShellInit shellInit,
+ Transitions transitions) {
mContext = context;
mTransitions = transitions;
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
/** Registers to handle transitions. */
- public void init() {
+ public void onInit() {
mTransitions.addHandler(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 02211220822a..05fafc54c273 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -30,15 +30,20 @@ import android.database.ContentObserver;
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.HardwareBuffer;
+import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.util.Log;
import android.view.IWindowFocusObserver;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -292,6 +297,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
} else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
"Finishing gesture with event action: %d", keyAction);
+ if (keyAction == MotionEvent.ACTION_CANCEL) {
+ mTriggerBack = false;
+ }
onGestureFinished(true);
}
}
@@ -321,7 +329,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo);
if (backNavigationInfo == null) {
Log.e(TAG, "Received BackNavigationInfo is null.");
- finishAnimation();
return;
}
int backType = backNavigationInfo.getType();
@@ -397,6 +404,25 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
dispatchOnBackProgressed(targetCallback, backEvent);
}
+ private void injectBackKey() {
+ sendBackEvent(KeyEvent.ACTION_DOWN);
+ sendBackEvent(KeyEvent.ACTION_UP);
+ }
+
+ private void sendBackEvent(int action) {
+ final long when = SystemClock.uptimeMillis();
+ final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */,
+ 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+ KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+ InputDevice.SOURCE_KEYBOARD);
+
+ ev.setDisplayId(mContext.getDisplay().getDisplayId());
+ if (!InputManager.getInstance()
+ .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
+ Log.e(TAG, "Inject input event fail");
+ }
+ }
+
private void onGestureFinished(boolean fromTouch) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
if (fromTouch) {
@@ -405,7 +431,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mBackGestureStarted = false;
}
- if (mTransitionInProgress || mBackNavigationInfo == null) {
+ if (mTransitionInProgress) {
+ return;
+ }
+
+ if (mBackNavigationInfo == null) {
+ // No focus window found or core are running recents animation, inject back key as
+ // legacy behavior.
+ if (mTriggerBack) {
+ injectBackKey();
+ }
+ finishAnimation();
return;
}
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 d7f1292cb717..398261600dc7 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
@@ -70,12 +70,10 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseSetArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -103,14 +101,17 @@ import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -177,8 +178,8 @@ public class BubbleController implements ConfigurationChangeListener {
private int mCurrentUserId;
// Current profiles of the user (e.g. user with a workprofile)
private SparseArray<UserInfo> mCurrentProfiles;
- // Saves notification keys of active bubbles when users are switched.
- private final SparseSetArray<String> mSavedBubbleKeysPerUser;
+ // Saves data about active bubbles when users are switched.
+ private final SparseArray<UserBubbleData> mSavedUserBubbleData;
// Used when ranking updates occur and we check if things should bubble / unbubble
private NotificationListenerService.Ranking mTmpRanking;
@@ -227,6 +228,7 @@ public class BubbleController implements ConfigurationChangeListener {
public BubbleController(Context context,
+ ShellInit shellInit,
ShellController shellController,
BubbleData data,
@Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
@@ -271,7 +273,7 @@ public class BubbleController implements ConfigurationChangeListener {
mCurrentUserId = ActivityManager.getCurrentUser();
mBubblePositioner = positioner;
mBubbleData = data;
- mSavedBubbleKeysPerUser = new SparseSetArray<>();
+ mSavedUserBubbleData = new SparseArray<>();
mBubbleIconFactory = new BubbleIconFactory(context);
mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context);
mDisplayController = displayController;
@@ -279,6 +281,7 @@ public class BubbleController implements ConfigurationChangeListener {
mOneHandedOptional = oneHandedOptional;
mDragAndDropController = dragAndDropController;
mSyncQueue = syncQueue;
+ shellInit.addInitCallback(this::onInit, this);
}
private void registerOneHandedState(OneHandedController oneHanded) {
@@ -300,7 +303,7 @@ public class BubbleController implements ConfigurationChangeListener {
});
}
- public void initialize() {
+ protected void onInit() {
mBubbleData.setListener(mBubbleDataListener);
mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
@@ -420,6 +423,13 @@ public class BubbleController implements ConfigurationChangeListener {
List<UserInfo> users = mUserManager.getAliveUsers();
mDataRepository.sanitizeBubbles(users);
+ // Init profiles
+ SparseArray<UserInfo> userProfiles = new SparseArray<>();
+ for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
+ userProfiles.put(user.id, user);
+ }
+ mCurrentProfiles = userProfiles;
+
mShellController.addConfigurationChangeListener(this);
}
@@ -774,11 +784,13 @@ public class BubbleController implements ConfigurationChangeListener {
*/
private void saveBubbles(@UserIdInt int userId) {
// First clear any existing keys that might be stored.
- mSavedBubbleKeysPerUser.remove(userId);
+ mSavedUserBubbleData.remove(userId);
+ UserBubbleData userBubbleData = new UserBubbleData();
// Add in all active bubbles for the current user.
for (Bubble bubble : mBubbleData.getBubbles()) {
- mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
+ userBubbleData.add(bubble.getKey(), bubble.showInShade());
}
+ mSavedUserBubbleData.put(userId, userBubbleData);
}
/**
@@ -787,22 +799,23 @@ public class BubbleController implements ConfigurationChangeListener {
* @param userId the id of the user
*/
private void restoreBubbles(@UserIdInt int userId) {
- ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
- if (savedBubbleKeys == null) {
+ UserBubbleData savedBubbleData = mSavedUserBubbleData.get(userId);
+ if (savedBubbleData == null) {
// There were no bubbles saved for this used.
return;
}
- mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> {
+ mSysuiProxy.getShouldRestoredEntries(savedBubbleData.getKeys(), (entries) -> {
mMainExecutor.execute(() -> {
for (BubbleEntry e : entries) {
if (canLaunchInTaskView(mContext, e)) {
- updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
+ boolean showInShade = savedBubbleData.isShownInShade(e.getKey());
+ updateBubble(e, true /* suppressFlyout */, showInShade);
}
}
});
});
// Finally, remove the entries for this user now that bubbles are restored.
- mSavedBubbleKeysPerUser.remove(userId);
+ mSavedUserBubbleData.remove(userId);
}
@Override
@@ -993,7 +1006,19 @@ public class BubbleController implements ConfigurationChangeListener {
*/
@VisibleForTesting
public void updateBubble(BubbleEntry notif) {
- updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
+ int bubbleUserId = notif.getStatusBarNotification().getUserId();
+ if (isCurrentProfile(bubbleUserId)) {
+ updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
+ } else {
+ // Skip update, but store it in user bubbles so it gets restored after user switch
+ mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(),
+ true /* shownInShade */);
+ if (DEBUG_BUBBLE_CONTROLLER) {
+ Log.d(TAG,
+ "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId
+ + " current userId=" + mCurrentUserId);
+ }
+ }
}
/**
@@ -1842,4 +1867,33 @@ public class BubbleController implements ConfigurationChangeListener {
}
}
}
+
+ /**
+ * Bubble data that is stored per user.
+ * Used to store and restore active bubbles during user switching.
+ */
+ private static class UserBubbleData {
+ private final Map<String, Boolean> mKeyToShownInShadeMap = new HashMap<>();
+
+ /**
+ * Add bubble key and whether it should be shown in notification shade
+ */
+ void add(String key, boolean shownInShade) {
+ mKeyToShownInShadeMap.put(key, shownInShade);
+ }
+
+ /**
+ * Get all bubble keys stored for this user
+ */
+ Set<String> getKeys() {
+ return mKeyToShownInShadeMap.keySet();
+ }
+
+ /**
+ * Check if this bubble with the given key should be shown in the notification shade
+ */
+ boolean isShownInShade(String key) {
+ return mKeyToShownInShadeMap.get(key);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index f8ccf2364b4c..cf792cda91b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -23,12 +23,10 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.app.NotificationChannel;
import android.content.pm.UserInfo;
-import android.content.res.Configuration;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.util.ArraySet;
import android.util.Pair;
import android.util.SparseArray;
@@ -42,6 +40,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -284,7 +283,7 @@ public interface Bubbles {
void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
- void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+ void getShouldRestoredEntries(Set<String> savedBubbleKeys,
Consumer<List<BubbleEntry>> callback);
void setNotificationInterruption(String key);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index 28c7367662a2..ae1f43320c8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -28,6 +28,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -47,10 +48,15 @@ public class DisplayChangeController {
private final CopyOnWriteArrayList<OnDisplayChangingListener> mDisplayChangeListener =
new CopyOnWriteArrayList<>();
- public DisplayChangeController(IWindowManager wmService, ShellExecutor mainExecutor) {
+ public DisplayChangeController(IWindowManager wmService, ShellInit shellInit,
+ ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
mWmService = wmService;
mControllerImpl = new DisplayChangeWindowControllerImpl();
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
try {
mWmService.setDisplayChangeWindowController(mControllerImpl);
} catch (RemoteException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 764936cceb01..f07ea751b044 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -34,6 +34,7 @@ import androidx.annotation.BinderThread;
import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
import java.util.List;
@@ -57,19 +58,23 @@ public class DisplayController {
private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
- public DisplayController(Context context, IWindowManager wmService,
+ public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
mContext = context;
mWmService = wmService;
- mChangeController = new DisplayChangeController(mWmService, mainExecutor);
+ // TODO: Inject this instead
+ mChangeController = new DisplayChangeController(mWmService, shellInit, mainExecutor);
mDisplayContainerListener = new DisplayWindowListenerImpl();
+ // Note, add this after DisplaceChangeController is constructed to ensure that is
+ // initialized first
+ shellInit.addInitCallback(this::onInit, this);
}
/**
* Initializes the window listener.
*/
- public void initialize() {
+ public void onInit() {
try {
int[] displayIds = mWmService.registerDisplayWindowListener(mDisplayContainerListener);
for (int i = 0; i < displayIds.length; i++) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index b3f62477077c..266cf294a950 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -44,6 +44,7 @@ import android.view.animation.PathInterpolator;
import androidx.annotation.VisibleForTesting;
import com.android.internal.view.IInputMethodManager;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
import java.util.concurrent.Executor;
@@ -74,18 +75,24 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
- public DisplayImeController(IWindowManager wmService, DisplayController displayController,
+ public DisplayImeController(IWindowManager wmService,
+ ShellInit shellInit,
+ DisplayController displayController,
DisplayInsetsController displayInsetsController,
- Executor mainExecutor, TransactionPool transactionPool) {
+ TransactionPool transactionPool,
+ Executor mainExecutor) {
mWmService = wmService;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
mMainExecutor = mainExecutor;
mTransactionPool = transactionPool;
+ shellInit.addInitCallback(this::onInit, this);
}
- /** Starts monitor displays changes and set insets controller for each displays. */
- public void startMonitorDisplays() {
+ /**
+ * Starts monitor displays changes and set insets controller for each displays.
+ */
+ public void onInit() {
mDisplayController.addDisplayWindowListener(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index f546f110e87c..90a01f8c5295 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -29,6 +29,7 @@ import android.view.InsetsVisibilities;
import androidx.annotation.BinderThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -45,17 +46,20 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
private final SparseArray<CopyOnWriteArrayList<OnInsetsChangedListener>> mListeners =
new SparseArray<>();
- public DisplayInsetsController(IWindowManager wmService, DisplayController displayController,
+ public DisplayInsetsController(IWindowManager wmService,
+ ShellInit shellInit,
+ DisplayController displayController,
ShellExecutor mainExecutor) {
mWmService = wmService;
mDisplayController = displayController;
mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
}
/**
* Starts listening for insets for each display.
**/
- public void initialize() {
+ public void onInit() {
mDisplayController.addDisplayWindowListener(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java
index 806f795d1015..10b121bbc32c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java
@@ -92,6 +92,8 @@ import javax.inject.Qualifier;
*
* For example, this uses the same setup as above, but the interface provided (if bound) is used
* otherwise the default is created:
+ *
+ * BaseModule:
* @BindsOptionalOf
* @DynamicOverride
* abstract Interface dynamicInterface();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTrigger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTrigger.java
new file mode 100644
index 000000000000..482b19983850
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTrigger.java
@@ -0,0 +1,38 @@
+/*
+ * 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.wm.shell.dagger;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * An annotation to specifically mark the provider that is triggering the creation of independent
+ * shell components that are not created as a part of the dependencies for interfaces passed to
+ * SysUI.
+ *
+ * TODO: This will be removed once we have a more explicit method for specifying components to start
+ * with SysUI
+ */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellCreateTrigger {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTriggerOverride.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTriggerOverride.java
new file mode 100644
index 000000000000..31c678968a25
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTriggerOverride.java
@@ -0,0 +1,38 @@
+/*
+ * 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.wm.shell.dagger;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * An annotation for non-base modules to specifically mark the provider that is triggering the
+ * creation of independent shell components that are not created as a part of the dependencies for
+ * interfaces passed to SysUI.
+ *
+ * TODO: This will be removed once we have a more explicit method for specifying components to start
+ * with SysUI
+ */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellCreateTriggerOverride {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index a168cb22f6ad..3add4179ef82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -112,16 +112,20 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static DisplayController provideDisplayController(Context context,
- IWindowManager wmService, @ShellMainThread ShellExecutor mainExecutor) {
- return new DisplayController(context, wmService, mainExecutor);
+ IWindowManager wmService,
+ ShellInit shellInit,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new DisplayController(context, wmService, shellInit, mainExecutor);
}
@WMSingleton
@Provides
- static DisplayInsetsController provideDisplayInsetsController( IWindowManager wmService,
+ static DisplayInsetsController provideDisplayInsetsController(IWindowManager wmService,
+ ShellInit shellInit,
DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor) {
- return new DisplayInsetsController(wmService, displayController, mainExecutor);
+ return new DisplayInsetsController(wmService, shellInit, displayController,
+ mainExecutor);
}
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@@ -134,16 +138,17 @@ public abstract class WMShellBaseModule {
static DisplayImeController provideDisplayImeController(
@DynamicOverride Optional<DisplayImeController> overrideDisplayImeController,
IWindowManager wmService,
+ ShellInit shellInit,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- @ShellMainThread ShellExecutor mainExecutor,
- TransactionPool transactionPool
+ TransactionPool transactionPool,
+ @ShellMainThread ShellExecutor mainExecutor
) {
if (overrideDisplayImeController.isPresent()) {
return overrideDisplayImeController.get();
}
- return new DisplayImeController(wmService, displayController, displayInsetsController,
- mainExecutor, transactionPool);
+ return new DisplayImeController(wmService, shellInit, displayController,
+ displayInsetsController, transactionPool, mainExecutor);
}
@WMSingleton
@@ -155,42 +160,45 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static DragAndDropController provideDragAndDropController(Context context,
+ ShellInit shellInit,
ShellController shellController,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
@ShellMainThread ShellExecutor mainExecutor) {
- return new DragAndDropController(context, shellController, displayController, uiEventLogger,
- iconProvider, mainExecutor);
+ return new DragAndDropController(context, shellInit, shellController, displayController,
+ uiEventLogger, iconProvider, mainExecutor);
}
@WMSingleton
@Provides
- static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
- Context context,
+ static ShellTaskOrganizer provideShellTaskOrganizer(
+ ShellInit shellInit,
CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasksOptional
+ Optional<RecentTasksController> recentTasksOptional,
+ @ShellMainThread ShellExecutor mainExecutor
) {
- return new ShellTaskOrganizer(mainExecutor, context, compatUI, unfoldAnimationController,
- recentTasksOptional);
+ return new ShellTaskOrganizer(shellInit, compatUI, unfoldAnimationController,
+ recentTasksOptional, mainExecutor);
}
@WMSingleton
@Provides
static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler,
Context context,
+ ShellInit shellInit,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasksOptional
+ Optional<RecentTasksController> recentTasksOptional,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler
) {
- return new KidsModeTaskOrganizer(mainExecutor, mainHandler, context, syncTransactionQueue,
+ return new KidsModeTaskOrganizer(context, shellInit, syncTransactionQueue,
displayController, displayInsetsController, unfoldAnimationController,
- recentTasksOptional);
+ recentTasksOptional, mainExecutor, mainHandler);
}
@WMSingleton
@@ -256,6 +264,20 @@ public abstract class WMShellBaseModule {
return backAnimationController.map(BackAnimationController::getBackAnimationImpl);
}
+ @WMSingleton
+ @Provides
+ static Optional<BackAnimationController> provideBackAnimationController(
+ Context context,
+ @ShellMainThread ShellExecutor shellExecutor,
+ @ShellBackgroundThread Handler backgroundHandler
+ ) {
+ if (BackAnimationController.IS_ENABLED) {
+ return Optional.of(
+ new BackAnimationController(shellExecutor, backgroundHandler, context));
+ }
+ return Optional.empty();
+ }
+
//
// Bubbles (optional feature)
//
@@ -282,12 +304,15 @@ public abstract class WMShellBaseModule {
@Provides
static FullscreenTaskListener provideFullscreenTaskListener(
@DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
Optional<RecentTasksController> recentTasksOptional) {
if (fullscreenTaskListener.isPresent()) {
return fullscreenTaskListener.get();
} else {
- return new FullscreenTaskListener(syncQueue, recentTasksOptional);
+ return new FullscreenTaskListener(shellInit, shellTaskOrganizer, syncQueue,
+ recentTasksOptional);
}
}
@@ -343,7 +368,7 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Optional<FreeformComponents> provideFreeformTaskListener(
+ static Optional<FreeformComponents> provideFreeformComponents(
@DynamicOverride Optional<FreeformComponents> freeformComponents,
Context context) {
if (FreeformComponents.isFreeformEnabled(context)) {
@@ -440,11 +465,13 @@ public abstract class WMShellBaseModule {
@Provides
static Optional<RecentTasksController> provideRecentTasksController(
Context context,
+ ShellInit shellInit,
TaskStackListenerImpl taskStackListener,
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
- RecentTasksController.create(context, taskStackListener, mainExecutor));
+ RecentTasksController.create(context, shellInit, taskStackListener,
+ mainExecutor));
}
//
@@ -459,12 +486,15 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
- DisplayController displayController, Context context,
+ static Transitions provideTransitions(Context context,
+ ShellInit shellInit,
+ ShellTaskOrganizer organizer,
+ TransactionPool pool,
+ DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
- return new Transitions(organizer, pool, displayController, context, mainExecutor,
+ return new Transitions(context, shellInit, organizer, pool, displayController, mainExecutor,
mainHandler, animExecutor);
}
@@ -542,11 +572,13 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static StartingWindowController provideStartingWindowController(Context context,
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
@ShellSplashscreenThread ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
TransactionPool pool) {
- return new StartingWindowController(context, splashScreenExecutor,
- startingWindowTypeAlgorithm, iconProvider, pool);
+ return new StartingWindowController(context, shellInit, shellTaskOrganizer,
+ splashScreenExecutor, startingWindowTypeAlgorithm, iconProvider, pool);
}
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@@ -595,9 +627,11 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Optional<ActivityEmbeddingController> provideActivityEmbeddingController(
- Context context, Transitions transitions) {
- return Optional.of(new ActivityEmbeddingController(context, transitions));
+ static ActivityEmbeddingController provideActivityEmbeddingController(
+ Context context,
+ ShellInit shellInit,
+ Transitions transitions) {
+ return new ActivityEmbeddingController(context, shellInit, transitions);
}
//
@@ -606,24 +640,34 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static ShellInterface provideShellSysuiCallbacks(ShellController shellController) {
+ static ShellInterface provideShellSysuiCallbacks(
+ @ShellCreateTrigger Object createTrigger,
+ ShellController shellController) {
return shellController.asShell();
}
@WMSingleton
@Provides
- static ShellController provideShellController(@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellController(mainExecutor);
+ static ShellController provideShellController(ShellInit shellInit,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new ShellController(shellInit, mainExecutor);
}
//
// Misc
//
+ // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+ @BindsOptionalOf
+ @ShellCreateTriggerOverride
+ abstract Object provideIndependentShellComponentsToCreateOverride();
+
+ // TODO: Temporarily move dependencies to this instead of ShellInit since that is needed to add
+ // the callback. We will be moving to a different explicit startup mechanism in a follow- up CL.
@WMSingleton
+ @ShellCreateTrigger
@Provides
- static ShellInit provideShellInitImpl(
- ShellController shellController,
+ static Object provideIndependentShellComponentsToCreate(
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
@@ -638,29 +682,17 @@ public abstract class WMShellBaseModule {
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformComponents> freeformComponents,
Optional<RecentTasksController> recentTasksOptional,
- Optional<ActivityEmbeddingController> activityEmbeddingOptional,
+ ActivityEmbeddingController activityEmbeddingOptional,
Transitions transitions,
StartingWindowController startingWindow,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new ShellInit(shellController,
- displayController,
- displayImeController,
- displayInsetsController,
- dragAndDropController,
- shellTaskOrganizer,
- kidsModeTaskOrganizer,
- bubblesOptional,
- splitScreenOptional,
- pipTouchHandlerOptional,
- fullscreenTaskListener,
- unfoldAnimationController,
- unfoldTransitionHandler,
- freeformComponents,
- recentTasksOptional,
- activityEmbeddingOptional,
- transitions,
- startingWindow,
- mainExecutor);
+ @ShellCreateTriggerOverride Optional<Object> overriddenCreateTrigger) {
+ return new Object();
+ }
+
+ @WMSingleton
+ @Provides
+ static ShellInit provideShellInit(@ShellMainThread ShellExecutor mainExecutor) {
+ return new ShellInit(mainExecutor);
}
@WMSingleton
@@ -679,18 +711,4 @@ public abstract class WMShellBaseModule {
kidsModeTaskOrganizer, splitScreenOptional, pipOptional, oneHandedOptional,
hideDisplayCutout, recentTasksOptional, mainExecutor);
}
-
- @WMSingleton
- @Provides
- static Optional<BackAnimationController> provideBackAnimationController(
- Context context,
- @ShellMainThread ShellExecutor shellExecutor,
- @ShellBackgroundThread Handler backgroundHandler
- ) {
- if (BackAnimationController.IS_ENABLED) {
- return Optional.of(
- new BackAnimationController(shellExecutor, backgroundHandler, context));
- }
- return Optional.empty();
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 588a1aa57a3a..e2bf7678f4b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -75,6 +75,8 @@ import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.SplitscreenPipMixedHandler;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -138,6 +140,7 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static BubbleController provideBubbleController(Context context,
+ ShellInit shellInit,
ShellController shellController,
BubbleData data,
FloatingContentCoordinator floatingContentCoordinator,
@@ -158,8 +161,8 @@ public abstract class WMShellModule {
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
- return new BubbleController(context, shellController, data, null /* synchronizer */,
- floatingContentCoordinator,
+ return new BubbleController(context, shellInit, shellController, data,
+ null /* synchronizer */, floatingContentCoordinator,
new BubbleDataRepository(context, launcherApps, mainExecutor),
statusBarService, windowManager, windowManagerShellWrapper, userManager,
launcherApps, logger, taskStackListener, organizer, positioner, displayController,
@@ -205,18 +208,34 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static FreeformTaskListener<?> provideFreeformTaskListener(
+ Context context,
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
WindowDecorViewModel<?> windowDecorViewModel) {
- return new FreeformTaskListener<>(windowDecorViewModel);
+ // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+ // override for this controller from the base module
+ ShellInit init = FreeformComponents.isFreeformEnabled(context)
+ ? shellInit
+ : null;
+ return new FreeformTaskListener<>(init, shellTaskOrganizer,
+ windowDecorViewModel);
}
@WMSingleton
@Provides
- static FreeformTaskTransitionHandler provideTaskTransitionHandler(
+ static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
+ Context context,
+ ShellInit shellInit,
Transitions transitions,
WindowDecorViewModel<?> windowDecorViewModel,
FreeformTaskListener<?> freeformTaskListener) {
- return new FreeformTaskTransitionHandler(transitions, windowDecorViewModel,
- freeformTaskListener);
+ // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+ // override for this controller from the base module
+ ShellInit init = FreeformComponents.isFreeformEnabled(context)
+ ? shellInit
+ : null;
+ return new FreeformTaskTransitionHandler(init, transitions,
+ windowDecorViewModel, freeformTaskListener);
}
//
@@ -247,20 +266,25 @@ public abstract class WMShellModule {
@Provides
@DynamicOverride
static SplitScreenController provideSplitScreenController(
+ Context context,
+ ShellInit shellInit,
ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue, Context context,
+ SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- @ShellMainThread ShellExecutor mainExecutor,
DisplayController displayController,
DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController, Transitions transitions,
- TransactionPool transactionPool, IconProvider iconProvider,
- Optional<RecentTasksController> recentTasks) {
- return new SplitScreenController(shellController, shellTaskOrganizer, syncQueue, context,
- rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController,
- displayInsetsController, transitions, transactionPool, iconProvider,
- recentTasks);
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ Optional<RecentTasksController> recentTasks,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new SplitScreenController(context, shellInit, shellController, shellTaskOrganizer,
+ syncQueue, rootTaskDisplayAreaOrganizer, displayController, displayImeController,
+ displayInsetsController, dragAndDropController, transitions, transactionPool,
+ iconProvider, recentTasks, mainExecutor);
}
//
@@ -332,14 +356,16 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static PipTouchHandler providePipTouchHandler(Context context,
- PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm,
+ ShellInit shellInit,
+ PhonePipMenuController menuPhoneController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor) {
- return new PipTouchHandler(context, menuPhoneController, pipBoundsAlgorithm,
+ return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
pipBoundsState, pipTaskOrganizer, pipMotionHelper,
floatingContentCoordinator, pipUiEventLogger, mainExecutor);
}
@@ -414,9 +440,31 @@ public abstract class WMShellModule {
floatingContentCoordinator);
}
+ @WMSingleton
+ @Provides
+ static PipParamsChangedForwarder providePipParamsChangedForwarder() {
+ return new PipParamsChangedForwarder();
+ }
+
+ //
+ // Transitions
+ //
+
+ @WMSingleton
+ @Provides
+ static SplitscreenPipMixedHandler provideSplitscreenPipMixedHandler(
+ ShellInit shellInit,
+ Optional<SplitScreenController> splitScreenOptional,
+ Optional<PipTouchHandler> pipTouchHandlerOptional,
+ Transitions transitions) {
+ return new SplitscreenPipMixedHandler(shellInit, splitScreenOptional,
+ pipTouchHandlerOptional, transitions);
+ }
+
//
// Unfold transition
//
+
@WMSingleton
@Provides
@DynamicOverride
@@ -426,6 +474,7 @@ public abstract class WMShellModule {
@UnfoldTransition SplitTaskUnfoldAnimator splitAnimator,
FullscreenUnfoldTaskAnimator fullscreenAnimator,
Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
+ ShellInit shellInit,
@ShellMainThread ShellExecutor mainExecutor
) {
final List<UnfoldTaskAnimator> animators = new ArrayList<>();
@@ -433,6 +482,7 @@ public abstract class WMShellModule {
animators.add(fullscreenAnimator);
return new UnfoldAnimationController(
+ shellInit,
transactionPool,
progressProvider.get(),
animators,
@@ -441,7 +491,6 @@ public abstract class WMShellModule {
);
}
-
@Provides
static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator(
Context context,
@@ -460,6 +509,10 @@ public abstract class WMShellModule {
Lazy<Optional<SplitScreenController>> splitScreenOptional,
DisplayInsetsController displayInsetsController
) {
+ // TODO(b/238217847): The lazy reference here causes some dependency issues since it
+ // immediately registers a listener on that controller on init. We should reference the
+ // controller directly once we refactor ShellTaskOrganizer to not depend on the unfold
+ // animation controller directly.
return new SplitTaskUnfoldAnimator(context, executor, splitScreenOptional,
backgroundController, displayInsetsController);
}
@@ -485,8 +538,9 @@ public abstract class WMShellModule {
@UnfoldShellTransition SplitTaskUnfoldAnimator unfoldAnimator,
TransactionPool transactionPool,
Transitions transitions,
- @ShellMainThread ShellExecutor executor) {
- return new UnfoldTransitionHandler(progressProvider.get(), animator,
+ @ShellMainThread ShellExecutor executor,
+ ShellInit shellInit) {
+ return new UnfoldTransitionHandler(shellInit, progressProvider.get(), animator,
unfoldAnimator, transactionPool, executor, transitions);
}
@@ -502,9 +556,17 @@ public abstract class WMShellModule {
);
}
+ //
+ // Misc
+ //
+
+ // TODO: Temporarily move dependencies to this instead of ShellInit since that is needed to add
+ // the callback. We will be moving to a different explicit startup mechanism in a follow- up CL.
@WMSingleton
+ @ShellCreateTriggerOverride
@Provides
- static PipParamsChangedForwarder providePipParamsChangedForwarder() {
- return new PipParamsChangedForwarder();
+ static Object provideIndependentShellComponentsToCreate(
+ SplitscreenPipMixedHandler splitscreenPipMixedHandler) {
+ return new Object();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index f4e2f20f4637..2aa933d641fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -44,10 +44,24 @@ to SysUI where it posts the work to the main Shell thread.
### Component initialization
To initialize the component:
-- On the Shell side, update `ShellInitImpl` to get a signal to initialize when the SysUI is started
+- On the Shell side, you potentially need to do two things to initialize the component:
+ - Inject `ShellInit` into your component and add an init callback
+ - Ensure that your component is a part of the dagger dependency graph, either by:
+ - Making this component a dependency of an existing component already exposed to SystemUI
+ - Explicitly add this component to the WMShellBaseModule @ShellCreateTrigger provider or
+ the @ShellCreateTriggerOverride provider for your product module to expose it explicitly
+ if it is a completely independent component
- On the SysUI side, update `WMShell` to setup any bindings for the component that depend on
SysUI code
+To verify that your component is being initialized at startup, you can enable the `WM_SHELL_INIT`
+protolog group and restart the SysUI process:
+```shell
+adb shell wm logging enable-text WM_SHELL_INIT
+adb shell kill `pid com.android.systemui`
+adb logcat *:S WindowManagerShell
+```
+
### General Do's & Dont's
Do:
- Do add unit tests for all new components
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index c5df53b6dbc8..4697a0184eb4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -62,9 +62,9 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
-import java.util.Optional;
/**
* Handles the global drag and drop handling for the Shell.
@@ -94,6 +94,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
}
public DragAndDropController(Context context,
+ ShellInit shellInit,
ShellController shellController,
DisplayController displayController,
UiEventLogger uiEventLogger,
@@ -105,14 +106,29 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
}
- public void initialize(Optional<SplitScreenController> splitscreen) {
- mSplitScreen = splitscreen.orElse(null);
- mDisplayController.addDisplayWindowListener(this);
+ /**
+ * Called when the controller is initialized.
+ */
+ public void onInit() {
+ // TODO(b/238217847): The dependency from SplitscreenController on DragAndDropController is
+ // inverted, which leads to SplitscreenController not setting its instance until after
+ // onDisplayAdded. We can remove this post once we fix that dependency.
+ mMainExecutor.executeDelayed(() -> {
+ mDisplayController.addDisplayWindowListener(this);
+ }, 0);
mShellController.addConfigurationChangeListener(this);
}
+ /**
+ * Sets the splitscreen controller to use if the feature is available.
+ */
+ public void setSplitScreenController(SplitScreenController splitscreen) {
+ mSplitScreen = splitscreen;
+ }
+
/** Adds a listener to be notified of drag and drop events. */
public void addListener(DragAndDropListener listener) {
mListeners.add(listener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index ba093a5fcc05..ab66107399c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.freeform;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
+
import android.app.ActivityManager.RunningTaskInfo;
import android.util.Log;
import android.util.SparseArray;
@@ -27,6 +29,7 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -42,6 +45,7 @@ public class FreeformTaskListener<T extends AutoCloseable>
implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FreeformTaskListener";
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final WindowDecorViewModel<T> mWindowDecorationViewModel;
private final SparseArray<State<T>> mTasks = new SparseArray<>();
@@ -53,8 +57,19 @@ public class FreeformTaskListener<T extends AutoCloseable>
T mWindowDecoration;
}
- public FreeformTaskListener(WindowDecorViewModel<T> windowDecorationViewModel) {
+ public FreeformTaskListener(
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ WindowDecorViewModel<T> windowDecorationViewModel) {
+ mShellTaskOrganizer = shellTaskOrganizer;
mWindowDecorationViewModel = windowDecorationViewModel;
+ if (shellInit != null) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 8731eb604cbc..20d77259406a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -32,6 +32,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -49,17 +50,26 @@ public class FreeformTaskTransitionHandler
private final Transitions mTransitions;
private final FreeformTaskListener<?> mFreeformTaskListener;
+ private final WindowDecorViewModel<?> mWindowDecorViewModel;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
public FreeformTaskTransitionHandler(
+ ShellInit shellInit,
Transitions transitions,
WindowDecorViewModel<?> windowDecorViewModel,
FreeformTaskListener<?> freeformTaskListener) {
mTransitions = transitions;
mFreeformTaskListener = freeformTaskListener;
+ mWindowDecorViewModel = windowDecorViewModel;
+ if (shellInit != null && Transitions.ENABLE_SHELL_TRANSITIONS) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
- windowDecorViewModel.setFreeformTaskTransitionStarter(this);
+ private void onInit() {
+ mWindowDecorViewModel.setFreeformTaskTransitionStarter(this);
+ mTransitions.addHandler(this);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 79e363bcdb41..0ba4afc24c45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -32,6 +32,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
@@ -43,19 +44,34 @@ import java.util.Optional;
public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FullscreenTaskListener";
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
+ /**
+ * This constructor is used by downstream products.
+ */
public FullscreenTaskListener(SyncTransactionQueue syncQueue) {
- this(syncQueue, Optional.empty());
+ this(null /* shellInit */, null /* shellTaskOrganizer */, syncQueue, Optional.empty());
}
- public FullscreenTaskListener(SyncTransactionQueue syncQueue,
- Optional<RecentTasksController> recentTasks) {
+ public FullscreenTaskListener(ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ Optional<RecentTasksController> recentTasksOptional) {
+ mShellTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
- mRecentTasksOptional = recentTasks;
+ mRecentTasksOptional = recentTasksOptional;
+ // Note: Some derivative FullscreenTaskListener implementations do not use ShellInit
+ if (shellInit != null) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FULLSCREEN);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 2c8ba0970ccc..73b9b89e6993 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -50,7 +50,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldAnimationController;
import java.io.PrintWriter;
@@ -140,18 +140,18 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
@VisibleForTesting
KidsModeTaskOrganizer(
- ITaskOrganizerController taskOrganizerController,
- ShellExecutor mainExecutor,
- Handler mainHandler,
Context context,
+ ITaskOrganizerController taskOrganizerController,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
- KidsModeSettingsObserver kidsModeSettingsObserver) {
- super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null,
- unfoldAnimationController, recentTasks);
+ KidsModeSettingsObserver kidsModeSettingsObserver,
+ ShellExecutor mainExecutor,
+ Handler mainHandler) {
+ super(/* shellInit= */ null, taskOrganizerController, /* compatUI= */ null,
+ unfoldAnimationController, recentTasks, mainExecutor);
mContext = context;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
@@ -161,27 +161,30 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
}
public KidsModeTaskOrganizer(
- ShellExecutor mainExecutor,
- Handler mainHandler,
Context context,
+ ShellInit shellInit,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasks) {
- super(mainExecutor, context, /* compatUI= */ null, unfoldAnimationController, recentTasks);
+ Optional<RecentTasksController> recentTasks,
+ ShellExecutor mainExecutor,
+ Handler mainHandler) {
+ // Note: we don't call super with the shell init because we will be initializing manually
+ super(/* shellInit= */ null, /* compatUI= */ null, unfoldAnimationController, recentTasks,
+ mainExecutor);
mContext = context;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
+ shellInit.addInitCallback(this::onInit, this);
}
/**
* Initializes kids mode status.
*/
- public void initialize(StartingWindowController startingWindowController) {
- initStartingWindow(startingWindowController);
+ public void onInit() {
if (mKidsModeSettingsObserver == null) {
mKidsModeSettingsObserver = new KidsModeSettingsObserver(mMainHandler, mContext);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index a2ff97247189..c86c1368b88e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -57,6 +57,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
@@ -165,6 +166,7 @@ public class PipTouchHandler {
@SuppressLint("InflateParams")
public PipTouchHandler(Context context,
+ ShellInit shellInit,
PhonePipMenuController menuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
@@ -173,7 +175,6 @@ public class PipTouchHandler {
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
ShellExecutor mainExecutor) {
- // Initialize the Pip input consumer
mContext = context;
mMainExecutor = mainExecutor;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -213,9 +214,17 @@ public class PipTouchHandler {
mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
this::onAccessibilityShowMenu, this::updateMovementBounds,
this::animateToUnStashedState, mainExecutor);
+
+ // TODO(b/181599115): This should really be initializes as part of the pip controller, but
+ // until all PIP implementations derive from the controller, just initialize the touch handler
+ // if it is needed
+ shellInit.addInitCallback(this::onInit, this);
}
- public void init() {
+ /**
+ * Called when the touch handler is initialized.
+ */
+ public void onInit() {
Resources res = mContext.getResources();
mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
reloadResources();
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 49042e663f6e..3d1a7e98e20d 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
@@ -44,6 +44,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -85,28 +86,32 @@ public class RecentTasksController implements TaskStackListenerCallback,
@Nullable
public static RecentTasksController create(
Context context,
+ ShellInit shellInit,
TaskStackListenerImpl taskStackListener,
@ShellMainThread ShellExecutor mainExecutor
) {
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
return null;
}
- return new RecentTasksController(context, taskStackListener, mainExecutor);
+ return new RecentTasksController(context, shellInit, taskStackListener, mainExecutor);
}
- RecentTasksController(Context context, TaskStackListenerImpl taskStackListener,
+ RecentTasksController(Context context,
+ ShellInit shellInit,
+ TaskStackListenerImpl taskStackListener,
ShellExecutor mainExecutor) {
mContext = context;
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
}
public RecentTasks asRecentTasks() {
return mImpl;
}
- public void init() {
+ private void onInit() {
mTaskStackListener.addListener(this);
}
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 7fb961fc0761..1be17f9bc5fd 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
@@ -32,6 +32,7 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
@@ -46,6 +47,7 @@ import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -76,12 +78,14 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -139,6 +143,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
+ private final DragAndDropController mDragAndDropController;
private final Transitions mTransitions;
private final TransactionPool mTransactionPool;
private final SplitscreenEventLogger mLogger;
@@ -150,15 +155,21 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mSplitTasksContainerLayer;
- public SplitScreenController(ShellController shellController,
+ public SplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue, Context context,
+ SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
- ShellExecutor mainExecutor, DisplayController displayController,
+ DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
- Optional<RecentTasksController> recentTasks) {
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ Optional<RecentTasksController> recentTasks,
+ ShellExecutor mainExecutor) {
mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
@@ -168,28 +179,29 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
+ mDragAndDropController = dragAndDropController;
mTransitions = transitions;
mTransactionPool = transactionPool;
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
+ // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+ // override for this controller from the base module
+ if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
public SplitScreen asSplitScreen() {
return mImpl;
}
- @Override
- public Context getContext() {
- return mContext;
- }
-
- @Override
- public ShellExecutor getRemoteCallExecutor() {
- return mMainExecutor;
- }
-
- public void onOrganizerRegistered() {
+ /**
+ * This will be called after ShellTaskOrganizer has initialized/registered because of the
+ * dependency order.
+ */
+ @VisibleForTesting
+ void onInit() {
mShellController.addKeyguardChangeListener(this);
if (mStageCoordinator == null) {
// TODO: Multi-display
@@ -198,6 +210,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
mIconProvider, mMainExecutor, mRecentTasksOptional);
}
+ mDragAndDropController.setSplitScreenController(this);
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
}
public boolean isSplitScreenVisible() {
@@ -334,17 +357,37 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user) {
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to invoke onAnimationFinished", e);
+ }
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ mStageCoordinator.prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ }
+ };
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
null /* wct */);
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- mStageCoordinator.prepareEvictChildTasks(position, evictWct);
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
+ 0 /* duration */, 0 /* statusBarTransitionDelay */);
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
try {
- LauncherApps launcherApps =
- mContext.getSystemService(LauncherApps.class);
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
- options, user);
- mSyncQueue.queue(evictWct);
+ activityOptions.toBundle(), user);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Failed to launch shortcut", e);
}
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 66f5bb663fbe..2b3b61bbbd84 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
@@ -610,6 +610,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ void prepareEvictNonOpeningChildTasks(@SplitPosition int position, RemoteAnimationTarget[] apps,
+ WindowContainerTransaction wct) {
+ if (position == mSideStagePosition) {
+ mSideStage.evictNonOpeningChildren(apps, wct);
+ } else {
+ mMainStage.evictNonOpeningChildren(apps, wct);
+ }
+ }
+
void prepareEvictInvisibleChildTasks(WindowContainerTransaction wct) {
mMainStage.evictInvisibleChildren(wct);
mSideStage.evictInvisibleChildren(wct);
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 f6dc68be946a..f414d69d37ec 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
@@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
@@ -33,6 +34,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.SparseArray;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -334,6 +336,19 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
+ void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) {
+ final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone();
+ for (int i = 0; i < apps.length; i++) {
+ if (apps[i].mode == MODE_OPENING) {
+ toBeEvict.remove(apps[i].taskId);
+ }
+ }
+ for (int i = toBeEvict.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo taskInfo = toBeEvict.valueAt(i);
+ wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
+ }
+ }
+
void evictInvisibleChildren(WindowContainerTransaction wct) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index fbc992378e50..379af21ac956 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -42,10 +42,12 @@ import androidx.annotation.BinderThread;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.TriConsumer;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
/**
* Implementation to draw the starting window to an application, and remove the starting window
@@ -74,6 +76,7 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback;
private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
private final Context mContext;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final ShellExecutor mSplashScreenExecutor;
/**
* Need guarded because it has exposed to StartingSurface
@@ -81,14 +84,20 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
@GuardedBy("mTaskBackgroundColors")
private final SparseIntArray mTaskBackgroundColors = new SparseIntArray();
- public StartingWindowController(Context context, ShellExecutor splashScreenExecutor,
- StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
+ public StartingWindowController(Context context,
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ ShellExecutor splashScreenExecutor,
+ StartingWindowTypeAlgorithm startingWindowTypeAlgorithm,
+ IconProvider iconProvider,
TransactionPool pool) {
mContext = context;
+ mShellTaskOrganizer = shellTaskOrganizer;
mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
iconProvider, pool);
mStartingWindowTypeAlgorithm = startingWindowTypeAlgorithm;
mSplashScreenExecutor = splashScreenExecutor;
+ shellInit.addInitCallback(this::onInit, this);
}
/**
@@ -98,6 +107,10 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
return mImpl;
}
+ private void onInit() {
+ mShellTaskOrganizer.initStartingWindow(this);
+ }
+
@Override
public Context getContext() {
return mContext;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
index 0427efb30b9f..f4fc0c4c36bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
@@ -45,7 +45,6 @@ public final class ShellCommandHandler {
private final Optional<RecentTasksController> mRecentTasks;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
- private final ShellExecutor mMainExecutor;
public ShellCommandHandler(
ShellController shellController,
@@ -64,7 +63,6 @@ public final class ShellCommandHandler {
mPipOptional = pipOptional;
mOneHandedOptional = oneHandedOptional;
mHideDisplayCutout = hideDisplayCutout;
- mMainExecutor = mainExecutor;
// TODO(238217847): To be removed once the command handler dependencies are inverted
shellController.setShellCommandHandler(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 618028c1544f..f1f317f65ba9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -44,10 +44,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class ShellController {
private static final String TAG = ShellController.class.getSimpleName();
+ private final ShellInit mShellInit;
private final ShellExecutor mMainExecutor;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
- private ShellInit mShellInit;
private ShellCommandHandler mShellCommandHandler;
private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
@@ -57,7 +57,8 @@ public class ShellController {
private Configuration mLastConfiguration;
- public ShellController(ShellExecutor mainExecutor) {
+ public ShellController(ShellInit shellInit, ShellExecutor mainExecutor) {
+ mShellInit = shellInit;
mMainExecutor = mainExecutor;
}
@@ -69,15 +70,6 @@ public class ShellController {
}
/**
- * Sets the init handler to call back to.
- * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
- * init handler to other classes.
- */
- public void setShellInit(ShellInit shellInit) {
- mShellInit = shellInit;
- }
-
- /**
* Sets the command handler to call back to.
* TODO(238217847): This is only exposed this way until we can remove the dependencies from the
* command handler to other classes.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index dd7fab7c5f46..c250e0313255 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.sysui;
-import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
import android.os.Build;
@@ -26,149 +25,27 @@ import android.util.Pair;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.freeform.FreeformComponents;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.startingsurface.StartingWindowController;
-import com.android.wm.shell.transition.DefaultMixedHandler;
-import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.unfold.UnfoldAnimationController;
-import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import java.util.ArrayList;
-import java.util.Optional;
/**
* The entry point implementation into the shell for initializing shell internal state. Classes
- * which need to setup on start should inject an instance of this class and add an init callback.
+ * which need to initialize on start of the host SysUI should inject an instance of this class and
+ * add an init callback.
*/
public class ShellInit {
private static final String TAG = ShellInit.class.getSimpleName();
- private final DisplayController mDisplayController;
- private final DisplayImeController mDisplayImeController;
- private final DisplayInsetsController mDisplayInsetsController;
- private final DragAndDropController mDragAndDropController;
- private final ShellTaskOrganizer mShellTaskOrganizer;
- private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
- private final Optional<BubbleController> mBubblesOptional;
- private final Optional<SplitScreenController> mSplitScreenOptional;
- private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
- private final FullscreenTaskListener mFullscreenTaskListener;
- private final Optional<UnfoldAnimationController> mUnfoldController;
- private final Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler;
- private final Optional<FreeformComponents> mFreeformComponentsOptional;
private final ShellExecutor mMainExecutor;
- private final Transitions mTransitions;
- private final StartingWindowController mStartingWindow;
- private final Optional<RecentTasksController> mRecentTasks;
- private final Optional<ActivityEmbeddingController> mActivityEmbeddingOptional;
// An ordered list of init callbacks to be made once shell is first started
private final ArrayList<Pair<String, Runnable>> mInitCallbacks = new ArrayList<>();
private boolean mHasInitialized;
- public ShellInit(
- ShellController shellController,
- DisplayController displayController,
- DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController,
- DragAndDropController dragAndDropController,
- ShellTaskOrganizer shellTaskOrganizer,
- KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<BubbleController> bubblesOptional,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<PipTouchHandler> pipTouchHandlerOptional,
- FullscreenTaskListener fullscreenTaskListener,
- Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
- Optional<FreeformComponents> freeformComponentsOptional,
- Optional<RecentTasksController> recentTasks,
- Optional<ActivityEmbeddingController> activityEmbeddingOptional,
- Transitions transitions,
- StartingWindowController startingWindow,
- ShellExecutor mainExecutor) {
- mDisplayController = displayController;
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mDragAndDropController = dragAndDropController;
- mShellTaskOrganizer = shellTaskOrganizer;
- mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
- mBubblesOptional = bubblesOptional;
- mSplitScreenOptional = splitScreenOptional;
- mFullscreenTaskListener = fullscreenTaskListener;
- mPipTouchHandlerOptional = pipTouchHandlerOptional;
- mUnfoldController = unfoldAnimationController;
- mUnfoldTransitionHandler = unfoldTransitionHandler;
- mFreeformComponentsOptional = freeformComponentsOptional;
- mRecentTasks = recentTasks;
- mActivityEmbeddingOptional = activityEmbeddingOptional;
- mTransitions = transitions;
- mMainExecutor = mainExecutor;
- mStartingWindow = startingWindow;
- // TODO(238217847): To be removed once the init dependencies are inverted
- shellController.setShellInit(this);
- }
-
- private void legacyInit() {
- // Start listening for display and insets changes
- mDisplayController.initialize();
- mDisplayInsetsController.initialize();
- mDisplayImeController.startMonitorDisplays();
-
- // Setup the shell organizer
- mShellTaskOrganizer.addListenerForType(
- mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
- mShellTaskOrganizer.initStartingWindow(mStartingWindow);
- mShellTaskOrganizer.registerOrganizer();
- mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
- mBubblesOptional.ifPresent(BubbleController::initialize);
-
- // Bind the splitscreen impl to the drag drop controller
- mDragAndDropController.initialize(mSplitScreenOptional);
-
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.register(mShellTaskOrganizer);
- mActivityEmbeddingOptional.ifPresent(ActivityEmbeddingController::init);
- mUnfoldTransitionHandler.ifPresent(UnfoldTransitionHandler::init);
- mFreeformComponentsOptional.flatMap(f -> f.mTaskTransitionHandler)
- .ifPresent(mTransitions::addHandler);
- if (mSplitScreenOptional.isPresent() && mPipTouchHandlerOptional.isPresent()) {
- final DefaultMixedHandler mixedHandler = new DefaultMixedHandler(mTransitions,
- mPipTouchHandlerOptional.get().getTransitionHandler(),
- mSplitScreenOptional.get().getTransitionHandler());
- // Added at end so that it has highest priority.
- mTransitions.addHandler(mixedHandler);
- }
- }
-
- // TODO(b/181599115): This should really be the pip controller, but until we can provide the
- // controller instead of the feature interface, can just initialize the touch handler if
- // needed
- mPipTouchHandlerOptional.ifPresent((handler) -> handler.init());
-
- // Initialize optional freeform
- mFreeformComponentsOptional.ifPresent(f ->
- mShellTaskOrganizer.addListenerForType(
- f.mTaskListener, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM));
-
- mUnfoldController.ifPresent(UnfoldAnimationController::init);
- mRecentTasks.ifPresent(RecentTasksController::init);
-
- // Initialize kids mode task organizer
- mKidsModeTaskOrganizer.initialize(mStartingWindow);
+ public ShellInit(ShellExecutor mainExecutor) {
+ mMainExecutor = mainExecutor;
}
/**
@@ -201,13 +78,9 @@ public class ShellInit {
final long t1 = SystemClock.uptimeMillis();
info.second.run();
final long t2 = SystemClock.uptimeMillis();
- ProtoLog.v(WM_SHELL_INIT, "\t%s took %dms", info.first, (t2 - t1));
+ ProtoLog.v(WM_SHELL_INIT, "\t%s init took %dms", info.first, (t2 - t1));
}
mInitCallbacks.clear();
-
- // TODO: To be removed
- legacyInit();
-
mHasInitialized = true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 0bec54399dd8..afc70a448aa8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -109,6 +109,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
import java.util.List;
@@ -136,6 +137,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private final TransactionPool mTransactionPool;
private final DisplayController mDisplayController;
private final Context mContext;
+ private final Handler mMainHandler;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionAnimation mTransitionAnimation;
@@ -167,27 +169,33 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
};
- DefaultTransitionHandler(@NonNull DisplayController displayController,
- @NonNull TransactionPool transactionPool, Context context,
+ DefaultTransitionHandler(@NonNull Context context,
+ @NonNull ShellInit shellInit,
+ @NonNull DisplayController displayController,
+ @NonNull TransactionPool transactionPool,
@NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor) {
mDisplayController = displayController;
mTransactionPool = transactionPool;
mContext = context;
+ mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
mCurrentUserId = UserHandle.myUserId();
+ mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+ shellInit.addInitCallback(this::onInit, this);
+ }
- mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ private void onInit() {
updateEnterpriseThumbnailDrawable();
mContext.registerReceiver(
mEnterpriseResourceUpdatedReceiver,
new IntentFilter(ACTION_DEVICE_POLICY_RESOURCE_UPDATED),
/* broadcastPermission = */ null,
- mainHandler);
+ mMainHandler);
- AttributeCache.init(context);
+ AttributeCache.init(mContext);
}
private void updateEnterpriseThumbnailDrawable() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SplitscreenPipMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SplitscreenPipMixedHandler.java
new file mode 100644
index 000000000000..678e91fd8829
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SplitscreenPipMixedHandler.java
@@ -0,0 +1,55 @@
+/*
+ * 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.wm.shell.transition;
+
+import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.util.Optional;
+
+/**
+ * Handles transitions between the Splitscreen and PIP components.
+ */
+public class SplitscreenPipMixedHandler {
+
+ private final Optional<SplitScreenController> mSplitScreenOptional;
+ private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
+ private final Transitions mTransitions;
+
+ public SplitscreenPipMixedHandler(ShellInit shellInit,
+ Optional<SplitScreenController> splitScreenControllerOptional,
+ Optional<PipTouchHandler> pipTouchHandlerOptional,
+ Transitions transitions) {
+ mSplitScreenOptional = splitScreenControllerOptional;
+ mPipTouchHandlerOptional = pipTouchHandlerOptional;
+ mTransitions = transitions;
+ if (Transitions.ENABLE_SHELL_TRANSITIONS
+ && mSplitScreenOptional.isPresent() && mPipTouchHandlerOptional.isPresent()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ // Special handling for initializing based on multiple components
+ final DefaultMixedHandler mixedHandler = new DefaultMixedHandler(mTransitions,
+ mPipTouchHandlerOptional.get().getTransitionHandler(),
+ mSplitScreenOptional.get().getTransitionHandler());
+ // Added at end so that it has highest priority.
+ mTransitions.addHandler(mixedHandler);
+ }
+}
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 881b7a1699f6..e6006c4fc6d0 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
@@ -59,13 +59,13 @@ import androidx.annotation.BinderThread;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
import java.util.Arrays;
@@ -111,6 +111,7 @@ public class Transitions implements RemoteCallable<Transitions> {
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionPlayerImpl mPlayerImpl;
+ private final DefaultTransitionHandler mDefaultTransitionHandler;
private final RemoteTransitionHandler mRemoteTransitionHandler;
private final DisplayController mDisplayController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
@@ -136,8 +137,11 @@ public class Transitions implements RemoteCallable<Transitions> {
/** Keeps track of currently playing transitions in the order of receipt. */
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
- public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
- @NonNull DisplayController displayController, @NonNull Context context,
+ public Transitions(@NonNull Context context,
+ @NonNull ShellInit shellInit,
+ @NonNull WindowOrganizer organizer,
+ @NonNull TransactionPool pool,
+ @NonNull DisplayController displayController,
@NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor) {
mOrganizer = organizer;
@@ -146,33 +150,35 @@ public class Transitions implements RemoteCallable<Transitions> {
mAnimExecutor = animExecutor;
mDisplayController = displayController;
mPlayerImpl = new TransitionPlayerImpl();
+ mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
+ displayController, pool, mainExecutor, mainHandler, animExecutor);
+ mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
// The very last handler (0 in the list) should be the default one.
- mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
- mainHandler, animExecutor));
+ mHandlers.add(mDefaultTransitionHandler);
// Next lowest priority is remote transitions.
- mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
mHandlers.add(mRemoteTransitionHandler);
- ContentResolver resolver = context.getContentResolver();
+ ContentResolver resolver = mContext.getContentResolver();
mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
Settings.Global.TRANSITION_ANIMATION_SCALE,
- context.getResources().getFloat(
+ mContext.getResources().getFloat(
R.dimen.config_appTransitionAnimationDurationScaleDefault));
dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
resolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
new SettingsObserver());
- }
- private Transitions() {
- mOrganizer = null;
- mContext = null;
- mMainExecutor = null;
- mAnimExecutor = null;
- mDisplayController = null;
- mPlayerImpl = null;
- mRemoteTransitionHandler = null;
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ // Register this transition handler with Core
+ mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ // Pre-load the instance.
+ TransitionMetrics.getInstance();
+ }
}
public ShellTransitions asRemoteTransitions() {
@@ -195,14 +201,6 @@ public class Transitions implements RemoteCallable<Transitions> {
}
}
- /** Register this transition handler with Core */
- public void register(ShellTaskOrganizer taskOrganizer) {
- if (mPlayerImpl == null) return;
- taskOrganizer.registerTransitionPlayer(mPlayerImpl);
- // Pre-load the instance.
- TransitionMetrics.getInstance();
- }
-
/**
* Adds a handler candidate.
* @see TransitionHandler
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index 05a024a0eb12..6b59e313b01b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -24,13 +24,14 @@ import android.app.TaskInfo;
import android.util.SparseArray;
import android.view.SurfaceControl;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
import java.util.List;
import java.util.Optional;
-import java.util.concurrent.Executor;
import dagger.Lazy;
@@ -47,7 +48,7 @@ import dagger.Lazy;
public class UnfoldAnimationController implements UnfoldListener {
private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
- private final Executor mExecutor;
+ private final ShellExecutor mExecutor;
private final TransactionPool mTransactionPool;
private final List<UnfoldTaskAnimator> mAnimators;
private final Lazy<Optional<UnfoldTransitionHandler>> mUnfoldTransitionHandler;
@@ -55,28 +56,36 @@ public class UnfoldAnimationController implements UnfoldListener {
private final SparseArray<SurfaceControl> mTaskSurfaces = new SparseArray<>();
private final SparseArray<UnfoldTaskAnimator> mAnimatorsByTaskId = new SparseArray<>();
- public UnfoldAnimationController(@NonNull TransactionPool transactionPool,
+ public UnfoldAnimationController(
+ @NonNull ShellInit shellInit,
+ @NonNull TransactionPool transactionPool,
@NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
@NonNull List<UnfoldTaskAnimator> animators,
@NonNull Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
- @NonNull Executor executor) {
+ @NonNull ShellExecutor executor) {
mUnfoldProgressProvider = unfoldProgressProvider;
mUnfoldTransitionHandler = unfoldTransitionHandler;
mTransactionPool = transactionPool;
mExecutor = executor;
mAnimators = animators;
+ // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+ // override for this controller from the base module
+ if (unfoldProgressProvider != ShellUnfoldProgressProvider.NO_PROVIDER) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
/**
* Initializes the controller, starts listening for the external events
*/
- public void init() {
+ public void onInit() {
mUnfoldProgressProvider.addListener(mExecutor, this);
for (int i = 0; i < mAnimators.size(); i++) {
final UnfoldTaskAnimator animator = mAnimators.get(i);
animator.init();
- animator.start();
+ // TODO(b/238217847): See #provideSplitTaskUnfoldAnimatorBase
+ mExecutor.executeDelayed(animator::start, 0);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 9bf32faa12bd..5d7b62905d3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -28,6 +28,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
import com.android.wm.shell.transition.Transitions.TransitionHandler;
@@ -59,11 +60,13 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>();
- public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider,
+ public UnfoldTransitionHandler(ShellInit shellInit,
+ ShellUnfoldProgressProvider unfoldProgressProvider,
FullscreenUnfoldTaskAnimator fullscreenUnfoldAnimator,
SplitTaskUnfoldAnimator splitUnfoldTaskAnimator,
TransactionPool transactionPool,
- Executor executor, Transitions transitions) {
+ Executor executor,
+ Transitions transitions) {
mUnfoldProgressProvider = unfoldProgressProvider;
mTransactionPool = transactionPool;
mExecutor = executor;
@@ -71,9 +74,18 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
mAnimators.add(splitUnfoldTaskAnimator);
mAnimators.add(fullscreenUnfoldAnimator);
+ // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+ // override for this controller from the base module
+ if (unfoldProgressProvider != ShellUnfoldProgressProvider.NO_PROVIDER
+ && Transitions.ENABLE_SHELL_TRANSITIONS) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
- public void init() {
+ /**
+ * Called when the transition handler is initialized.
+ */
+ public void onInit() {
for (int i = 0; i < mAnimators.size(); i++) {
mAnimators.get(i).init();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
index 6cd5677db2c3..4bcdcaae230b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
@@ -24,25 +24,8 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.freeform.FreeformComponents;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.startingsurface.StartingWindowController;
-import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.unfold.UnfoldAnimationController;
-import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import org.junit.Before;
import org.junit.Test;
@@ -51,31 +34,12 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class ShellInitTest extends ShellTestCase {
- @Mock private ShellController mShellController;
- @Mock private DisplayController mDisplayController;
- @Mock private DisplayImeController mDisplayImeController;
- @Mock private DisplayInsetsController mDisplayInsetsController;
- @Mock private DragAndDropController mDragAndDropController;
- @Mock private ShellTaskOrganizer mShellTaskOrganizer;
- @Mock private KidsModeTaskOrganizer mKidsModeTaskOrganizer;
- @Mock private Optional<BubbleController> mBubblesOptional;
- @Mock private Optional<SplitScreenController> mSplitScreenOptional;
- @Mock private Optional<PipTouchHandler> mPipTouchHandlerOptional;
- @Mock private FullscreenTaskListener mFullscreenTaskListener;
- @Mock private Optional<UnfoldAnimationController> mUnfoldAnimationController;
- @Mock private Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler;
- @Mock private Optional<FreeformComponents> mFreeformComponentsOptional;
- @Mock private Optional<RecentTasksController> mRecentTasks;
- @Mock private Optional<ActivityEmbeddingController> mActivityEmbeddingController;
- @Mock private Transitions mTransitions;
- @Mock private StartingWindowController mStartingWindow;
@Mock private ShellExecutor mMainExecutor;
private ShellInit mImpl;
@@ -83,12 +47,7 @@ public class ShellInitTest extends ShellTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mImpl = new ShellInit(mShellController, mDisplayController, mDisplayImeController,
- mDisplayInsetsController, mDragAndDropController, mShellTaskOrganizer,
- mKidsModeTaskOrganizer, mBubblesOptional, mSplitScreenOptional,
- mPipTouchHandlerOptional, mFullscreenTaskListener, mUnfoldAnimationController,
- mUnfoldTransitionHandler, mFreeformComponentsOptional, mRecentTasks,
- mActivityEmbeddingController, mTransitions, mStartingWindow, mMainExecutor);
+ mImpl = new ShellInit(mMainExecutor);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 3dd00329253c..7517e8ab6826 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -36,11 +36,11 @@ import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
-import android.content.Context;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -58,9 +58,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
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.compatui.CompatUIController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -84,13 +83,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
@Mock
private ITaskOrganizerController mTaskOrganizerController;
@Mock
- private Context mContext;
- @Mock
private CompatUIController mCompatUI;
+ @Mock
+ private ShellInit mShellInit;
ShellTaskOrganizer mOrganizer;
- private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
- private final TransactionPool mTransactionPool = mock(TransactionPool.class);
private final ShellExecutor mTestExecutor = mock(ShellExecutor.class);
private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener {
@@ -135,8 +132,13 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
- mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
- mCompatUI, Optional.empty(), Optional.empty()));
+ mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mTaskOrganizerController,
+ mCompatUI, Optional.empty(), Optional.empty(), mTestExecutor));
+ }
+
+ @Test
+ public void instantiate_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
new file mode 100644
index 000000000000..bfe3b5468085
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -0,0 +1,67 @@
+/*
+ * 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.wm.shell.activityembedding;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the activity embedding controller.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ActivityEmbeddingControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ActivityEmbeddingControllerTests extends ShellTestCase {
+
+ private @Mock Context mContext;
+ private @Mock ShellInit mShellInit;
+ private @Mock Transitions mTransitions;
+ private ActivityEmbeddingController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mController = spy(new ActivityEmbeddingController(mContext, mShellInit, mTransitions));
+ }
+
+ @Test
+ public void instantiate_addInitCallback() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java
new file mode 100644
index 000000000000..b8aa8e7cbc48
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java
@@ -0,0 +1,64 @@
+/*
+ * 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.wm.shell.common;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.view.IWindowManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the display change controller.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DisplayChangeControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayChangeControllerTests extends ShellTestCase {
+
+ private @Mock IWindowManager mWM;
+ private @Mock ShellInit mShellInit;
+ private @Mock ShellExecutor mMainExecutor;
+ private DisplayChangeController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mController = spy(new DisplayChangeController(mWM, mShellInit, mMainExecutor));
+ }
+
+ @Test
+ public void instantiate_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
new file mode 100644
index 000000000000..1e5e153fdfe1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
@@ -0,0 +1,65 @@
+/*
+ * 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.wm.shell.common;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.view.IWindowManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the display controller.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DisplayControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayControllerTests extends ShellTestCase {
+
+ private @Mock Context mContext;
+ private @Mock IWindowManager mWM;
+ private @Mock ShellInit mShellInit;
+ private @Mock ShellExecutor mMainExecutor;
+ private DisplayController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mController = new DisplayController(mContext, mWM, mShellInit, mMainExecutor);
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mController));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 5b691f231d85..9967e5f47752 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -26,6 +26,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -40,26 +41,32 @@ import androidx.test.filters.SmallTest;
import com.android.internal.view.IInputMethodManager;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.concurrent.Executor;
@SmallTest
public class DisplayImeControllerTest extends ShellTestCase {
+ @Mock
private SurfaceControl.Transaction mT;
- private DisplayImeController.PerDisplay mPerDisplay;
+ @Mock
private IInputMethodManager mMock;
+ @Mock
+ private ShellInit mShellInit;
+ private DisplayImeController.PerDisplay mPerDisplay;
private Executor mExecutor;
@Before
public void setUp() throws Exception {
- mT = mock(SurfaceControl.Transaction.class);
- mMock = mock(IInputMethodManager.class);
+ MockitoAnnotations.initMocks(this);
mExecutor = spy(Runnable::run);
- mPerDisplay = new DisplayImeController(null, null, null, mExecutor, new TransactionPool() {
+ mPerDisplay = new DisplayImeController(null, mShellInit, null, null, new TransactionPool() {
@Override
public SurfaceControl.Transaction acquire() {
return mT;
@@ -68,7 +75,7 @@ public class DisplayImeControllerTest extends ShellTestCase {
@Override
public void release(SurfaceControl.Transaction t) {
}
- }) {
+ }, mExecutor) {
@Override
public IInputMethodManager getImms() {
return mMock;
@@ -79,6 +86,11 @@ public class DisplayImeControllerTest extends ShellTestCase {
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void insetsControlChanged_schedulesNoWorkOnExecutor() {
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
verifyZeroInteractions(mExecutor);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 4a7fd3d259da..5f5a3c584ee0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.common;
import static android.view.Display.DEFAULT_DISPLAY;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.times;
@@ -37,6 +38,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -55,6 +57,8 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
private IWindowManager mWm;
@Mock
private DisplayController mDisplayController;
+ @Mock
+ private ShellInit mShellInit;
private DisplayInsetsController mController;
private SparseArray<IDisplayWindowInsetsController> mInsetsControllersByDisplayId;
private TestShellExecutor mExecutor;
@@ -69,11 +73,16 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
mInsetsControllersByDisplayId = new SparseArray<>();
mDisplayIdCaptor = ArgumentCaptor.forClass(Integer.class);
mInsetsControllerCaptor = ArgumentCaptor.forClass(IDisplayWindowInsetsController.class);
- mController = new DisplayInsetsController(mWm, mDisplayController, mExecutor);
+ mController = new DisplayInsetsController(mWm, mShellInit, mDisplayController, mExecutor);
addDisplay(DEFAULT_DISPLAY);
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void testOnDisplayAdded_setsDisplayWindowInsetsControllerOnWMService()
throws RemoteException {
addDisplay(SECOND_DISPLAY);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index e209971998c8..b6dbcf204364 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -50,6 +50,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -57,8 +58,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
/**
* Tests for the drag and drop controller.
*/
@@ -69,6 +68,8 @@ public class DragAndDropControllerTest extends ShellTestCase {
@Mock
private Context mContext;
@Mock
+ private ShellInit mShellInit;
+ @Mock
private ShellController mShellController;
@Mock
private DisplayController mDisplayController;
@@ -88,9 +89,14 @@ public class DragAndDropControllerTest extends ShellTestCase {
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
- mController = new DragAndDropController(mContext, mShellController, mDisplayController,
- mUiEventLogger, mIconProvider, mMainExecutor);
- mController.initialize(Optional.of(mSplitScreenController));
+ mController = new DragAndDropController(mContext, mShellInit, mShellController,
+ mDisplayController, mUiEventLogger, mIconProvider, mMainExecutor);
+ mController.onInit();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index 184a8dfecff9..a919ad0b4765 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -49,7 +49,7 @@ import com.android.wm.shell.common.DisplayController;
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.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -73,7 +73,7 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
@Mock private WindowContainerToken mToken;
@Mock private WindowContainerTransaction mTransaction;
@Mock private KidsModeSettingsObserver mObserver;
- @Mock private StartingWindowController mStartingWindowController;
+ @Mock private ShellInit mShellInit;
@Mock private DisplayInsetsController mDisplayInsetsController;
KidsModeTaskOrganizer mOrganizer;
@@ -87,10 +87,9 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
} catch (RemoteException e) {
}
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
- mOrganizer = spy(new KidsModeTaskOrganizer(mTaskOrganizerController, mTestExecutor,
- mHandler, mContext, mSyncTransactionQueue, mDisplayController,
- mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver));
- mOrganizer.initialize(mStartingWindowController);
+ mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mTaskOrganizerController,
+ mSyncTransactionQueue, mDisplayController, mDisplayInsetsController,
+ Optional.empty(), Optional.empty(), mObserver, mTestExecutor, mHandler));
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 74519eaf3ebf..ecefd89d8778 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -38,6 +38,7 @@ import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -78,6 +79,9 @@ public class PipTouchHandlerTest extends ShellTestCase {
private PipUiEventLogger mPipUiEventLogger;
@Mock
+ private ShellInit mShellInit;
+
+ @Mock
private ShellExecutor mMainExecutor;
private PipBoundsState mPipBoundsState;
@@ -104,11 +108,11 @@ public class PipTouchHandlerTest extends ShellTestCase {
PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
- mPipTouchHandler = new PipTouchHandler(mContext, mPhonePipMenuController,
- mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
- pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger,
- mMainExecutor);
- mPipTouchHandler.init();
+ mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
+ mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer, pipMotionHelper,
+ mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
+ // We aren't actually using ShellInit, so just call init directly
+ mPipTouchHandler.onInit();
mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
mPipTouchHandler.setPipMotionHelper(mMotionHelper);
@@ -133,6 +137,11 @@ public class PipTouchHandlerTest extends ShellTestCase {
}
@Test
+ public void instantiate_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void updateMovementBounds_minMaxBounds() {
final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
mPipBoundsState.getDisplayBounds().height());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index b1e0911bd0a8..d406a4ed3fd7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -48,6 +48,7 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -71,6 +72,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
private Context mContext;
@Mock
private TaskStackListenerImpl mTaskStackListener;
+ @Mock
+ private ShellInit mShellInit;
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
@@ -80,10 +83,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void setUp() {
mMainExecutor = new TestShellExecutor();
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
- mRecentTasksController = spy(new RecentTasksController(mContext, mTaskStackListener,
- mMainExecutor));
- mShellTaskOrganizer = new ShellTaskOrganizer(mMainExecutor, mContext,
- null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController));
+ mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
+ mTaskStackListener, mMainExecutor));
+ mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit,
+ null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
+ mMainExecutor);
}
@Test
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 c7a261f32e43..10788f9df32f 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
@@ -51,8 +51,10 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -71,6 +73,7 @@ import java.util.Optional;
public class SplitScreenControllerTests extends ShellTestCase {
@Mock ShellController mShellController;
+ @Mock ShellInit mShellInit;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock SyncTransactionQueue mSyncQueue;
@Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@@ -78,6 +81,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Mock DisplayController mDisplayController;
@Mock DisplayImeController mDisplayImeController;
@Mock DisplayInsetsController mDisplayInsetsController;
+ @Mock DragAndDropController mDragAndDropController;
@Mock Transitions mTransitions;
@Mock TransactionPool mTransactionPool;
@Mock IconProvider mIconProvider;
@@ -88,16 +92,21 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mSplitScreenController = spy(new SplitScreenController(mShellController, mTaskOrganizer,
- mSyncQueue, mContext, mRootTDAOrganizer, mMainExecutor, mDisplayController,
- mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool,
- mIconProvider, mRecentTasks));
+ mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
+ mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController,
+ mDisplayImeController, mDisplayInsetsController, mDragAndDropController,
+ mTransitions, mTransactionPool, mIconProvider, mRecentTasks, mMainExecutor));
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
}
@Test
public void testControllerRegistersKeyguardChangeListener() {
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
- mSplitScreenController.onOrganizerRegistered();
+ mSplitScreenController.onInit();
verify(mShellController, times(1)).addKeyguardChangeListener(any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
new file mode 100644
index 000000000000..35515e3bb6e8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -0,0 +1,80 @@
+/*
+ * 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.wm.shell.startingsurface;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the starting window controller.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:StartingWindowControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StartingWindowControllerTests extends ShellTestCase {
+
+ private @Mock Context mContext;
+ private @Mock DisplayManager mDisplayManager;
+ private @Mock ShellInit mShellInit;
+ private @Mock ShellTaskOrganizer mTaskOrganizer;
+ private @Mock ShellExecutor mMainExecutor;
+ private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm;
+ private @Mock IconProvider mIconProvider;
+ private @Mock TransactionPool mTransactionPool;
+ private StartingWindowController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt());
+ doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
+ mController = new StartingWindowController(mContext, mShellInit, mTaskOrganizer,
+ mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ }
+
+ @Test
+ public void instantiate_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 1c0e46f7264e..02311bab2e4d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -43,6 +43,8 @@ import java.util.Locale;
public class ShellControllerTest extends ShellTestCase {
@Mock
+ private ShellInit mShellInit;
+ @Mock
private ShellExecutor mExecutor;
private ShellController mController;
@@ -54,7 +56,7 @@ public class ShellControllerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
- mController = new ShellController(mExecutor);
+ mController = new ShellController(mShellInit, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e2f2b71cea04..388792b63db3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -84,6 +84,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -117,6 +118,14 @@ public class ShellTransitionTests extends ShellTestCase {
}
@Test
+ public void instantiate_addInitCallback() {
+ ShellInit shellInit = mock(ShellInit.class);
+ final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
+ createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ verify(shellInit, times(1)).addInitCallback(any(), eq(t));
+ }
+
+ @Test
public void testBasicTransitionFlow() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -832,14 +841,18 @@ public class ShellTransitionTests extends ShellTestCase {
} catch (RemoteException e) {
// No remote stuff happening, so this can't be hit
}
- DisplayController out = new DisplayController(mContext, mockWM, mMainExecutor);
- out.initialize();
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ DisplayController out = new DisplayController(mContext, mockWM, shellInit, mMainExecutor);
+ shellInit.init();
return out;
}
private Transitions createTestTransitions() {
- return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
- mContext, mMainExecutor, mMainHandler, mAnimExecutor);
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
+ createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ shellInit.init();
+ return t;
}
//
// private class TestDisplayController extends DisplayController {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
index 46de60772766..81eefe25704e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
@@ -20,7 +20,10 @@ import static com.android.wm.shell.unfold.UnfoldAnimationControllerTest.TestUnfo
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
@@ -33,6 +36,7 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
import org.junit.Before;
@@ -65,6 +69,8 @@ public class UnfoldAnimationControllerTest extends ShellTestCase {
@Mock
private UnfoldTransitionHandler mUnfoldTransitionHandler;
@Mock
+ private ShellInit mShellInit;
+ @Mock
private SurfaceControl mLeash;
private UnfoldAnimationController mUnfoldAnimationController;
@@ -85,6 +91,7 @@ public class UnfoldAnimationControllerTest extends ShellTestCase {
animators.add(mTaskAnimator1);
animators.add(mTaskAnimator2);
mUnfoldAnimationController = new UnfoldAnimationController(
+ mShellInit,
mTransactionPool,
mProgressProvider,
animators,
@@ -94,6 +101,11 @@ public class UnfoldAnimationControllerTest extends ShellTestCase {
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void testAppearedMatchingTask_appliesUnfoldProgress() {
mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
@@ -244,7 +256,8 @@ public class UnfoldAnimationControllerTest extends ShellTestCase {
@Test
public void testInit_initsAndStartsAnimators() {
- mUnfoldAnimationController.init();
+ mUnfoldAnimationController.onInit();
+ mShellExecutor.flushAll();
assertThat(mTaskAnimator1.mInitialized).isTrue();
assertThat(mTaskAnimator1.mStarted).isTrue();
diff --git a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
index 5ce08b7747a4..27425589c822 100644
--- a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
+++ b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.activityembedding">
+ package="com.android.settingslib.widget">
<uses-sdk android:minSdkVersion="21" />
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
index 1c47f5ff862a..244b367423a4 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.collapsingtoolbar">
+ package="com.android.settingslib.widget">
<uses-sdk android:minSdkVersion="29" />
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
index 8ebbac39d1d0..3582897014ff 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
@@ -20,6 +20,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceFragmentCompat;
import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.R;
import com.google.android.material.appbar.AppBarLayout;
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 77d65834da37..8c8b47875bec 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -27,6 +27,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.R;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index 31e8cc709749..ec091bf4de67 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -30,6 +30,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import com.android.settingslib.widget.R;
+
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index 1ead2f337314..a8c7a3f936ee 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -36,7 +36,7 @@ import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import com.android.settingslib.collapsingtoolbar.R;
+import com.android.settingslib.widget.R;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
diff --git a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
index b6aff53e0290..244b367423a4 100644
--- a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
+++ b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.transition">
+ package="com.android.settingslib.widget">
<uses-sdk android:minSdkVersion="29" />
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 4f84c8c817cb..322e1be250a6 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1608,6 +1608,8 @@
<string name="dream_complication_title_aqi">Air Quality</string>
<!-- Screensaver overlay which displays cast info. [CHAR LIMIT=20] -->
<string name="dream_complication_title_cast_info">Cast Info</string>
+ <!-- Screensaver overlay which displays home controls. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_home_controls">Home Controls</string>
<!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] -->
<string name="avatar_picker_title">Choose a profile picture</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 01d0cc40c0bc..a46e23235843 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -91,7 +91,8 @@ public class DreamBackend {
COMPLICATION_TYPE_DATE,
COMPLICATION_TYPE_WEATHER,
COMPLICATION_TYPE_AIR_QUALITY,
- COMPLICATION_TYPE_CAST_INFO
+ COMPLICATION_TYPE_CAST_INFO,
+ COMPLICATION_TYPE_HOME_CONTROLS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ComplicationType {}
@@ -101,6 +102,7 @@ public class DreamBackend {
public static final int COMPLICATION_TYPE_WEATHER = 3;
public static final int COMPLICATION_TYPE_AIR_QUALITY = 4;
public static final int COMPLICATION_TYPE_CAST_INFO = 5;
+ public static final int COMPLICATION_TYPE_HOME_CONTROLS = 6;
private final Context mContext;
private final IDreamManager mDreamManager;
@@ -346,6 +348,9 @@ public class DreamBackend {
case COMPLICATION_TYPE_CAST_INFO:
res = R.string.dream_complication_title_cast_info;
break;
+ case COMPLICATION_TYPE_HOME_CONTROLS:
+ res = R.string.dream_complication_title_home_controls;
+ break;
default:
return null;
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 1344144ee6fc..cd3a72271603 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -145,25 +145,10 @@ filegroup {
filegroup {
name: "SystemUI-tests-utils",
srcs: [
- "tests/src/com/android/systemui/SysuiBaseFragmentTest.java",
- "tests/src/com/android/systemui/SysuiTestCase.java",
- "tests/src/com/android/systemui/TestableDependency.java",
- "tests/src/com/android/systemui/classifier/FalsingManagerFake.java",
- "tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java",
- "tests/src/com/android/systemui/statusbar/RankingBuilder.java",
- "tests/src/com/android/systemui/statusbar/SbnBuilder.java",
- "tests/src/com/android/systemui/SysuiTestableContext.java",
- "tests/src/com/android/systemui/util/**/*Fake.java",
- "tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java",
- "tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java",
- "tests/src/com/android/systemui/**/Fake*.java",
- "tests/src/com/android/systemui/**/Fake*.kt",
+ "tests/utils/src/**/*.java",
+ "tests/utils/src/**/*.kt",
],
- exclude_srcs: [
- "tests/src/com/android/systemui/**/*Test.java",
- "tests/src/com/android/systemui/**/*Test.kt",
- ],
- path: "tests/src",
+ path: "tests/utils/src",
}
java_library {
@@ -171,8 +156,8 @@ java_library {
srcs: [
"src/com/android/systemui/util/concurrency/DelayableExecutor.java",
"src/com/android/systemui/util/time/SystemClock.java",
- "tests/src/com/android/systemui/util/concurrency/FakeExecutor.java",
- "tests/src/com/android/systemui/util/time/FakeSystemClock.java",
+ "tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java",
+ "tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java",
],
jarjar_rules: ":jarjar-rules-shared",
}
@@ -195,6 +180,7 @@ android_library {
"src/**/*.java",
"src/**/I*.aidl",
":ReleaseJavaFiles",
+ ":SystemUI-tests-utils",
],
static_libs: [
"WifiTrackerLib",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index ff64c7891128..d427a57f3b87 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -485,7 +485,13 @@ class TextInterpolator(
val out = mutableListOf<List<PositionedGlyphs>>()
for (lineNo in 0 until layout.lineCount) { // Shape all lines.
val lineStart = layout.getLineStart(lineNo)
- val count = layout.getLineEnd(lineNo) - lineStart
+ var count = layout.getLineEnd(lineNo) - lineStart
+ // Do not render the last character in the line if it's a newline and unprintable
+ val last = lineStart + count - 1
+ if (last > lineStart && last < layout.text.length && layout.text[last] == '\n') {
+ count--
+ }
+
val runs = mutableListOf<PositionedGlyphs>()
TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic,
paint) { _, _, glyphs, _ ->
diff --git a/packages/SystemUI/docs/device-entry/keyguard.md b/packages/SystemUI/docs/device-entry/keyguard.md
index 337f73b79260..8634c950f96c 100644
--- a/packages/SystemUI/docs/device-entry/keyguard.md
+++ b/packages/SystemUI/docs/device-entry/keyguard.md
@@ -30,6 +30,10 @@ An indication to power off the device most likely comes from one of two signals:
### How the device locks
+### Quick Affordances
+
+These are interactive UI elements that appear on the lockscreen when the device is locked. They allow the user to perform quick actions without unlocking their device. To learn more about them, please see [this dedicated document](quickaffordance.md)
+
## Debugging Tips
Enable verbose keyguard logs that will print to logcat. Should only be used temporarily for debugging. See [KeyguardConstants][5].
```
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
new file mode 100644
index 000000000000..a96e533933f4
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -0,0 +1,25 @@
+# Keyguard Quick Affordances
+These are interactive UI elements that appear at the bottom of the lockscreen when the device is
+locked. They allow the user to perform quick actions without unlocking their device. For example:
+opening an screen that lets them control the smart devices in their home, access their touch-to-pay
+credit card, etc.
+
+## Adding a new Quick Affordance
+### Step 1: create a new quick affordance config
+* Create a new class under the [systemui/keyguard/data/quickaffordance](../../src/com/android/systemui/keyguard/data/quickaffordance) directory
+* Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
+* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
+ * The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
+ * It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
+ * When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
+* Please include a unit test for your new implementation under [the correct directory](../../tests/src/com/android/systemui/keyguard/data/quickaffordance)
+
+### Step 2: choose a position and priority
+* Add the new class as a dependency in the constructor of [KeyguardQuickAffordanceConfigs](../../src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt)
+* Place the new class in one of the available positions in the `configsByPosition` property, note:
+ * In each position, there is a list. The order matters. The order of that list is the priority order in which the framework considers each config. The first config whose state property returns `State.Visible` determines the button that is shown for that position
+ * Please only add to one position. The framework treats each position individually and there is no good way to prevent the same config from making its button appear in more than one position at the same time
+
+### Step 3: manually verify the new quick affordance
+* Build and launch SysUI on a device
+* Verify that the quick affordance button for the new implementation is correctly visible and clicking it does the right thing
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 0c1916074e0c..cafdc8676173 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -13,9 +13,9 @@
*/
package com.android.systemui.plugins
+import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.view.View
-import com.android.internal.colorextraction.ColorExtractor
import com.android.systemui.plugins.annotations.ProvidesInterface
import java.io.PrintWriter
import java.util.Locale
@@ -57,7 +57,15 @@ interface Clock {
val events: ClockEvents
/** Triggers for various animations */
- val animation: ClockAnimation
+ val animations: ClockAnimations
+
+ /** Initializes various rendering parameters. If never called, provides reasonable defaults. */
+ fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+ events.onColorPaletteChanged(resources)
+ animations.doze(dozeFraction)
+ animations.fold(foldFraction)
+ events.onTimeTick()
+ }
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter) { }
@@ -80,15 +88,12 @@ interface ClockEvents {
/** Call whenever font settings change */
fun onFontSettingChanged() { }
- /** Call whenever the color pallete should update */
- fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) { }
+ /** Call whenever the color palette should update */
+ fun onColorPaletteChanged(resources: Resources) { }
}
/** Methods which trigger various clock animations */
-interface ClockAnimation {
- /** Initializes the doze & fold animation positions. Defaults to neither folded nor dozing. */
- fun initialize(dozeFraction: Float, foldFraction: Float) { }
-
+interface ClockAnimations {
/** Runs an enter animation (if any) */
fun enter() { }
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 5b9299c278a9..7538555e1bcd 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -11,6 +11,11 @@
-keep class * extends com.android.systemui.CoreStartable
-keep class * implements com.android.systemui.CoreStartable$Injector
+# Needed for builds to properly initialize KeyFrames from xml scene
+-keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key {
+ public <init>();
+}
+
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
diff --git a/packages/SystemUI/res-keyguard/font/clock.xml b/packages/SystemUI/res-keyguard/font/clock.xml
deleted file mode 100644
index 0137dc39921f..000000000000
--- a/packages/SystemUI/res-keyguard/font/clock.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2020, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License")
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!--
-** AOD/LockScreen Clock font.
-** Should include all numeric glyphs in all supported locales.
-** Recommended: font with variable width to support AOD => LS animations
--->
-<!-- TODO: Remove when clock migration complete -->
-<font-family xmlns:android="http://schemas.android.com/apk/res/android">
- <font android:typeface="monospace"/>
-</font-family> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 6a38507b2ad7..8b8ebf00e190 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -31,42 +31,14 @@
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:paddingStart="@dimen/clock_padding_start">
- <com.android.systemui.shared.clocks.AnimatableClockView
- android:id="@+id/animatable_clock_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:gravity="start"
- android:textSize="@dimen/clock_text_size"
- android:fontFamily="@font/clock"
- android:elegantTextHeight="false"
- android:singleLine="true"
- android:fontFeatureSettings="pnum"
- chargeAnimationDelay="350"
- dozeWeight="200"
- lockScreenWeight="400"
- />
</FrameLayout>
<FrameLayout
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_below="@id/keyguard_slice_view"
+ android:paddingTop="@dimen/keyguard_large_clock_top_padding"
android:visibility="gone">
- <com.android.systemui.shared.clocks.AnimatableClockView
- android:id="@+id/animatable_clock_view_large"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center_horizontal"
- android:textSize="@dimen/large_clock_text_size"
- android:fontFamily="@font/clock"
- android:typeface="monospace"
- android:elegantTextHeight="false"
- chargeAnimationDelay="200"
- dozeWeight="200"
- lockScreenWeight="400"
- />
</FrameLayout>
<!-- Not quite optimal but needed to translate these items as a group. The
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index 7a57293f58bd..5f4e310f975c 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -19,7 +19,7 @@
android:id="@+id/time_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:fontFamily="@font/clock"
+ android:fontFamily="@*android:string/config_clockFontFamily"
android:includeFontPadding="false"
android:textColor="@android:color/white"
android:format12Hour="@string/dream_time_complication_12_hr_time_format"
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 12dfa1042dd7..0ca19d98a097 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -60,6 +60,30 @@
</LinearLayout>
<ImageView
+ android:id="@+id/start_button"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_gravity="bottom|start"
+ android:scaleType="center"
+ android:tint="?android:attr/textColorPrimary"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
+ android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:visibility="gone" />
+
+ <ImageView
+ android:id="@+id/end_button"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_gravity="bottom|end"
+ android:scaleType="center"
+ android:tint="?android:attr/textColorPrimary"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
+ android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:visibility="gone" />
+
+ <ImageView
android:id="@+id/wallet_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 36cc0ad70315..c0071cb45e21 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -40,4 +40,6 @@
<bool name="config_use_large_screen_shade_header">true</bool>
+ <!-- Whether to show the side fps hint while on bouncer -->
+ <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 82a3b58a5155..ec22c609b0b9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -548,6 +548,9 @@
<!-- Package name of the preferred system app to perform eSOS action -->
<string name="config_preferredEmergencySosPackage" translatable="false"></string>
+ <!-- Whether to show the side fps hint while on bouncer -->
+ <bool name="config_show_sidefps_hint_on_bouncer">false</bool>
+
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3fb00a33e6b6..205b11784634 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -664,13 +664,7 @@
<!-- 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 -->
- <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
-
- <!-- TODO: Remove during migration -->
- <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
- <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
- <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
- <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+ <dimen name="keyguard_large_clock_top_padding">100dp</dimen>
<dimen name="notification_scrim_corner_radius">32dp</dimen>
@@ -890,11 +884,6 @@
burn-in on AOD. -->
<dimen name="burn_in_prevention_offset_y_clock">42dp</dimen>
- <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
- <!-- TODO: Remove when clock migration complete -->
- <dimen name="large_clock_text_size">150dp</dimen>
- <dimen name="clock_text_size">86dp</dimen>
-
<!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. -->
<dimen name="default_burn_in_prevention_offset">15dp</dimen>
diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/shared/res/layout/clock_default_large.xml
index 8510a0a8b550..0139d50dcfba 100644
--- a/packages/SystemUI/shared/res/layout/clock_default_large.xml
+++ b/packages/SystemUI/shared/res/layout/clock_default_large.xml
@@ -18,7 +18,6 @@
-->
<com.android.systemui.shared.clocks.AnimatableClockView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/animatable_clock_view_large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/shared/res/layout/clock_default_small.xml
index ec0e427e6a4d..390ff5e3ff78 100644
--- a/packages/SystemUI/shared/res/layout/clock_default_small.xml
+++ b/packages/SystemUI/shared/res/layout/clock_default_small.xml
@@ -18,7 +18,6 @@
-->
<com.android.systemui.shared.clocks.AnimatableClockView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/animatable_clock_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
@@ -26,6 +25,7 @@
android:textSize="@dimen/small_clock_text_size"
android:fontFamily="@*android:string/config_clockFontFamily"
android:elegantTextHeight="false"
+ android:ellipsize="none"
android:singleLine="true"
android:fontFeatureSettings="pnum"
chargeAnimationDelay="350"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 2739d59dbf00..8f1959e884cf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -20,12 +20,15 @@ import android.annotation.ColorInt
import android.annotation.FloatRange
import android.annotation.IntRange
import android.annotation.SuppressLint
+import android.app.compat.ChangeIdStateCache.invalidate
import android.content.Context
import android.graphics.Canvas
import android.text.TextUtils
import android.text.format.DateFormat
import android.util.AttributeSet
import android.widget.TextView
+import com.android.internal.R.attr.contentDescription
+import com.android.internal.R.attr.format
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.TextAnimator
@@ -75,6 +78,12 @@ class AnimatableClockView @JvmOverloads constructor(
val lockScreenWeight: Int
get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
+ /**
+ * The number of pixels below the baseline. For fonts that support languages such as
+ * Burmese, this space can be significant and should be accounted for when computing layout.
+ */
+ val bottom get() = paint?.fontMetrics?.bottom ?: 0f
+
init {
val animatableClockViewAttributes = context.obtainStyledAttributes(
attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
@@ -133,6 +142,15 @@ class AnimatableClockView @JvmOverloads constructor(
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
+
+ // Because the TextLayout may mutate under the hood as a result of the new text, we
+ // notify the TextAnimator that it may have changed and request a measure/layout. A
+ // crash will occur on the next invocation of setTextStyle if the layout is mutated
+ // without being notified TextInterpolator being notified.
+ if (layout != null) {
+ textAnimator?.updateLayout(layout)
+ }
+ requestLayout()
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index a4c03b0b57c8..835d6e92a63d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -35,8 +35,6 @@ import javax.inject.Inject
private val TAG = ClockRegistry::class.simpleName
private val DEBUG = true
-typealias ClockChangeListener = () -> Unit
-
/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
val context: Context,
@@ -51,12 +49,19 @@ open class ClockRegistry(
defaultClockProvider: DefaultClockProvider
) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
+ // Usually this would be a typealias, but a SAM provides better java interop
+ fun interface ClockChangeListener {
+ fun onClockChanged()
+ }
+
+ var isEnabled: Boolean = false
+
private val gson = Gson()
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver = object : ContentObserver(handler) {
override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
- clockChangeListeners.forEach { it() }
+ clockChangeListeners.forEach { it.onClockChanged() }
}
private val pluginListener = object : PluginListener<ClockProviderPlugin> {
@@ -69,22 +74,37 @@ open class ClockRegistry(
open var currentClockId: ClockId
get() {
- val json = Settings.Secure.getString(
- context.contentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
- )
- return gson.fromJson(json, ClockSetting::class.java).clockId
+ return try {
+ val json = Settings.Secure.getString(
+ context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
+ )
+ gson.fromJson(json, ClockSetting::class.java)?.clockId ?: DEFAULT_CLOCK_ID
+ } catch (ex: Exception) {
+ Log.e(TAG, "Failed to parse clock setting", ex)
+ DEFAULT_CLOCK_ID
+ }
}
set(value) {
- val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
- Settings.Secure.putString(
- context.contentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
- )
+ try {
+ val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
+ Settings.Secure.putString(
+ context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
+ )
+ } catch (ex: Exception) {
+ Log.e(TAG, "Failed to set clock setting", ex)
+ }
}
init {
connectClocks(defaultClockProvider)
+ if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) {
+ throw IllegalArgumentException(
+ "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID"
+ )
+ }
+
pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
context.contentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
@@ -100,8 +120,11 @@ open class ClockRegistry(
val id = clock.clockId
val current = availableClocks[id]
if (current != null) {
- Log.e(TAG, "Clock Id conflict: $id is registered by both " +
- "${provider::class.simpleName} and ${current.provider::class.simpleName}")
+ Log.e(
+ TAG,
+ "Clock Id conflict: $id is registered by both " +
+ "${provider::class.simpleName} and ${current.provider::class.simpleName}"
+ )
return
}
@@ -110,7 +133,7 @@ open class ClockRegistry(
if (DEBUG) {
Log.i(TAG, "Current clock ($currentId) was connected")
}
- clockChangeListeners.forEach { it() }
+ clockChangeListeners.forEach { it.onClockChanged() }
}
}
}
@@ -122,12 +145,17 @@ open class ClockRegistry(
if (currentId == clock.clockId) {
Log.w(TAG, "Current clock ($currentId) was disconnected")
- clockChangeListeners.forEach { it() }
+ clockChangeListeners.forEach { it.onClockChanged() }
}
}
}
- fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }
+ fun getClocks(): List<ClockMetadata> {
+ if (!isEnabled) {
+ return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
+ }
+ return availableClocks.map { (_, clock) -> clock.metadata }
+ }
fun getClockThumbnail(clockId: ClockId): Drawable? =
availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
@@ -142,7 +170,7 @@ open class ClockRegistry(
fun createCurrentClock(): Clock {
val clockId = currentClockId
- if (!clockId.isNullOrEmpty()) {
+ if (isEnabled && clockId.isNotEmpty()) {
val clock = createClock(clockId)
if (clock != null) {
return clock
@@ -164,6 +192,6 @@ open class ClockRegistry(
private data class ClockSetting(
val clockId: ClockId,
- val _applied_timestamp: Long
+ val _applied_timestamp: Long?
)
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 5d8da5985768..1d8abe3fdf42 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -19,10 +19,9 @@ import android.graphics.drawable.Drawable
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
-import com.android.internal.colorextraction.ColorExtractor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.Clock
-import com.android.systemui.plugins.ClockAnimation
+import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
@@ -102,10 +101,13 @@ class DefaultClock(
TypedValue.COMPLEX_UNIT_PX,
resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
)
+ recomputePadding()
}
- override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) =
- clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) }
+ override fun onColorPaletteChanged(resources: Resources) {
+ val color = resources.getColor(android.R.color.system_accent1_100)
+ clocks.forEach { it.setColors(DOZE_COLOR, color) }
+ }
override fun onLocaleChanged(locale: Locale) {
val nf = NumberFormat.getInstance(locale)
@@ -119,8 +121,17 @@ class DefaultClock(
}
}
- override val animation = object : ClockAnimation {
- override fun initialize(dozeFraction: Float, foldFraction: Float) {
+ override var animations = DefaultClockAnimations(0f, 0f)
+ private set
+
+ inner class DefaultClockAnimations(
+ dozeFraction: Float,
+ foldFraction: Float
+ ) : ClockAnimations {
+ private var foldState = AnimationState(0f)
+ private var dozeState = AnimationState(0f)
+
+ init {
dozeState = AnimationState(dozeFraction)
foldState = AnimationState(foldFraction)
@@ -132,14 +143,13 @@ class DefaultClock(
}
override fun enter() {
- if (dozeState.isActive) {
+ if (!dozeState.isActive) {
clocks.forEach { it.animateAppearOnLockscreen() }
}
}
override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
- private var foldState = AnimationState(0f)
override fun fold(fraction: Float) {
val (hasChanged, hasJumped) = foldState.update(fraction)
if (hasChanged) {
@@ -147,7 +157,6 @@ class DefaultClock(
}
}
- private var dozeState = AnimationState(0f)
override fun doze(fraction: Float) {
val (hasChanged, hasJumped) = dozeState.update(fraction)
if (hasChanged) {
@@ -172,6 +181,19 @@ class DefaultClock(
init {
events.onLocaleChanged(Locale.getDefault())
+ clocks.forEach { it.setColors(DOZE_COLOR, DOZE_COLOR) }
+ }
+
+ override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+ recomputePadding()
+ animations = DefaultClockAnimations(dozeFraction, foldFraction)
+ events.onColorPaletteChanged(resources)
+ events.onTimeTick()
+ }
+
+ private fun recomputePadding() {
+ val topPadding = -1 * (largeClock.bottom.toInt() - 180)
+ largeClock.setPadding(0, topPadding, 0, 0)
}
override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index afa255a09441..ef9e0951abf0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -79,6 +79,8 @@ public class RemoteAnimationTargetCompat {
// Fields used only to unrap into RemoteAnimationTarget
private final Rect startBounds;
+ public final boolean willShowImeOnTarget;
+
public RemoteAnimationTargetCompat(RemoteAnimationTarget app) {
taskId = app.taskId;
mode = app.mode;
@@ -102,6 +104,7 @@ public class RemoteAnimationTargetCompat {
windowType = app.windowType;
windowConfiguration = app.windowConfiguration;
startBounds = app.startBounds;
+ willShowImeOnTarget = app.willShowImeOnTarget;
}
private static int newModeToLegacyMode(int newMode) {
@@ -118,14 +121,15 @@ public class RemoteAnimationTargetCompat {
}
public RemoteAnimationTarget unwrap() {
- return new RemoteAnimationTarget(
+ final RemoteAnimationTarget target = new RemoteAnimationTarget(
taskId, mode, leash, isTranslucent, clipRect, contentInsets,
prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfiguration,
isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType
);
+ target.setWillShowImeOnTarget(willShowImeOnTarget);
+ return target;
}
-
/**
* Almost a copy of Transitions#setupStartState.
* TODO: remove when there is proper cross-process transaction sync.
@@ -234,6 +238,7 @@ public class RemoteAnimationTargetCompat {
? change.getTaskInfo().configuration.windowConfiguration
: new WindowConfiguration();
startBounds = change.getStartAbsBounds();
+ willShowImeOnTarget = (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0;
}
public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 609846e8c729..7c1ef8c76926 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -230,6 +230,7 @@ public class RemoteTransitionCompat implements Parcelable {
private IBinder mTransition = null;
private boolean mKeyguardLocked = false;
private RemoteAnimationTargetCompat[] mAppearedTargets;
+ private boolean mWillFinishToHome = false;
void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
IRemoteTransitionFinishedCallback finishCB,
@@ -392,7 +393,7 @@ public class RemoteTransitionCompat implements Parcelable {
if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
else wct.restoreTransientOrder(mRecentsTask);
}
- if (!toHome && mPausingTasks != null && mOpeningLeashes == null) {
+ if (!toHome && !mWillFinishToHome && mPausingTasks != null && mOpeningLeashes == null) {
// The gesture went back to opening the app rather than continuing with
// recents, so end the transition by moving the app back to the top (and also
// re-showing it's task).
@@ -476,6 +477,7 @@ public class RemoteTransitionCompat implements Parcelable {
}
@Override public void setWillFinishToHome(boolean willFinishToHome) {
+ mWillFinishToHome = willFinishToHome;
if (mWrapped != null) mWrapped.setWillFinishToHome(willFinishToHome);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index c69ff7ee1cd8..e0b11d83bf75 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -182,18 +182,6 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
- /**
- * @return the number of pixels below the baseline. For fonts that support languages such as
- * Burmese, this space can be significant.
- */
- public float getBottom() {
- if (mView.getPaint() != null && mView.getPaint().getFontMetrics() != null) {
- return mView.getPaint().getFontMetrics().bottom;
- }
-
- return 0f;
- }
-
/** Animate the clock appearance */
public void animateAppear() {
if (!mIsDozing) mView.animateAppearOnLockscreen();
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
new file mode 100644
index 000000000000..efd7bcf10cd2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.keyguard
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.res.Resources
+import android.text.format.DateFormat
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
+import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import javax.inject.Inject
+
+/**
+ * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
+ * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
+ */
+class ClockEventController @Inject constructor(
+ private val statusBarStateController: StatusBarStateController,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val batteryController: BatteryController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val configurationController: ConfigurationController,
+ @Main private val resources: Resources,
+ private val context: Context
+) {
+ var clock: Clock? = null
+ set(value) {
+ field = value
+ if (value != null) {
+ value.initialize(resources, dozeAmount, 0f)
+ }
+ }
+
+ private var isDozing = false
+ private set
+
+ private var isCharging = false
+ private var dozeAmount = 0f
+ private var isKeyguardShowing = false
+
+ private val configListener = object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ clock?.events?.onColorPaletteChanged(resources)
+ }
+ }
+
+ private val batteryCallback = object : BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ if (isKeyguardShowing && !isCharging && charging) {
+ clock?.animations?.charge()
+ }
+ isCharging = charging
+ }
+ }
+
+ private val localeBroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ clock?.events?.onLocaleChanged(Locale.getDefault())
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ clock?.animations?.doze(linear)
+
+ isDozing = linear > dozeAmount
+ dozeAmount = linear
+ }
+ }
+
+ private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+ override fun onKeyguardVisibilityChanged(showing: Boolean) {
+ isKeyguardShowing = showing
+ if (!isKeyguardShowing) {
+ clock?.animations?.doze(if (isDozing) 1f else 0f)
+ }
+ }
+
+ override fun onTimeFormatChanged(timeFormat: String) {
+ clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) {
+ clock?.events?.onTimeZoneChanged(timeZone)
+ }
+
+ override fun onUserSwitchComplete(userId: Int) {
+ clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+ }
+
+ init {
+ isDozing = statusBarStateController.isDozing
+ }
+
+ fun registerListeners() {
+ dozeAmount = statusBarStateController.dozeAmount
+ isDozing = statusBarStateController.isDozing || dozeAmount != 0f
+
+ broadcastDispatcher.registerReceiver(
+ localeBroadcastReceiver,
+ IntentFilter(Intent.ACTION_LOCALE_CHANGED)
+ )
+ configurationController.addCallback(configListener)
+ batteryController.addCallback(batteryCallback)
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ statusBarStateController.addCallback(statusBarStateListener)
+ }
+
+ fun unregisterListeners() {
+ broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
+ configurationController.removeCallback(configListener)
+ batteryController.removeCallback(batteryCallback)
+ keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
+ statusBarStateController.removeCallback(statusBarStateListener)
+ }
+
+ /**
+ * Dump information for debugging
+ */
+ fun dump(pw: PrintWriter) {
+ pw.println(this)
+ clock?.dump(pw)
+ }
+
+ companion object {
+ private val TAG = ClockEventController::class.simpleName
+ private const val FORMAT_NUMBER = 1234567890
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 206b8bee323c..e1fabdef3651 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,10 +5,8 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
import android.util.AttributeSet;
-import android.util.TypedValue;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -17,19 +15,14 @@ import android.widget.RelativeLayout;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.dagger.KeyguardStatusViewScope;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.plugins.Clock;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.TimeZone;
-
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
@@ -50,17 +43,10 @@ public class KeyguardClockSwitch extends RelativeLayout {
public static final int SMALL = 1;
/**
- * Optional/alternative clock injected via plugin.
- */
- private ClockPlugin mClockPlugin;
-
- /**
* Frame for small/large clocks
*/
- private FrameLayout mClockFrame;
+ private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
- private AnimatableClockView mClockView;
- private AnimatableClockView mLargeClockView;
private View mStatusArea;
private int mSmartspaceTopOffset;
@@ -80,12 +66,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
@VisibleForTesting AnimatorSet mClockOutAnim = null;
private ObjectAnimator mStatusAreaAnim = null;
- /**
- * If the Keyguard Slice has a header (big center-aligned text.)
- */
- private boolean mSupportsDarkText;
- private int[] mColorPalette;
-
private int mClockSwitchYAmount;
@VisibleForTesting boolean mChildrenAreLaidOut = false;
@@ -97,97 +77,38 @@ public class KeyguardClockSwitch extends RelativeLayout {
* Apply dp changes on font/scale change
*/
public void onDensityOrFontScaleChanged() {
- mLargeClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
- .getDimensionPixelSize(R.dimen.large_clock_text_size));
- mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
- .getDimensionPixelSize(R.dimen.clock_text_size));
-
mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_clock_switch_y_shift);
-
mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_smartspace_top_offset);
}
- /**
- * Returns if this view is presenting a custom clock, or the default implementation.
- */
- public boolean hasCustomClock() {
- return mClockPlugin != null;
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mClockFrame = findViewById(R.id.lockscreen_clock_view);
- mClockView = findViewById(R.id.animatable_clock_view);
+ mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
- mLargeClockView = findViewById(R.id.animatable_clock_view_large);
mStatusArea = findViewById(R.id.keyguard_status_area);
onDensityOrFontScaleChanged();
}
- void setClockPlugin(ClockPlugin plugin, int statusBarState) {
+ void setClock(Clock clock, int statusBarState) {
// Disconnect from existing plugin.
- if (mClockPlugin != null) {
- View smallClockView = mClockPlugin.getView();
- if (smallClockView != null && smallClockView.getParent() == mClockFrame) {
- mClockFrame.removeView(smallClockView);
- }
- View bigClockView = mClockPlugin.getBigClockView();
- if (bigClockView != null && bigClockView.getParent() == mLargeClockFrame) {
- mLargeClockFrame.removeView(bigClockView);
- }
- mClockPlugin.onDestroyView();
- mClockPlugin = null;
- }
- if (plugin == null) {
- mClockView.setVisibility(View.VISIBLE);
- mLargeClockView.setVisibility(View.VISIBLE);
- return;
- }
- // Attach small and big clock views to hierarchy.
- View smallClockView = plugin.getView();
- if (smallClockView != null) {
- mClockFrame.addView(smallClockView, -1,
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- mClockView.setVisibility(View.GONE);
- }
- View bigClockView = plugin.getBigClockView();
- if (bigClockView != null) {
- mLargeClockFrame.addView(bigClockView);
- mLargeClockView.setVisibility(View.GONE);
- }
-
- // Initialize plugin parameters.
- mClockPlugin = plugin;
- mClockPlugin.setStyle(getPaint().getStyle());
- mClockPlugin.setTextColor(getCurrentTextColor());
- mClockPlugin.setDarkAmount(mDarkAmount);
- if (mColorPalette != null) {
- mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
- }
- }
+ mSmallClockFrame.removeAllViews();
+ mLargeClockFrame.removeAllViews();
- /**
- * It will also update plugin setStyle if plugin is connected.
- */
- public void setStyle(Style style) {
- if (mClockPlugin != null) {
- mClockPlugin.setStyle(style);
+ if (clock == null) {
+ Log.e(TAG, "No clock being shown");
+ return;
}
- }
- /**
- * It will also update plugin setTextColor if plugin is connected.
- */
- public void setTextColor(int color) {
- if (mClockPlugin != null) {
- mClockPlugin.setTextColor(color);
- }
+ // Attach small and big clock views to hierarchy.
+ mSmallClockFrame.addView(clock.getSmallClock(), -1,
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mLargeClockFrame.addView(clock.getLargeClock());
}
private void updateClockViews(boolean useLargeClock, boolean animate) {
@@ -203,14 +124,14 @@ public class KeyguardClockSwitch extends RelativeLayout {
int direction = 1;
float statusAreaYTranslation;
if (useLargeClock) {
- out = mClockFrame;
+ out = mSmallClockFrame;
in = mLargeClockFrame;
if (indexOfChild(in) == -1) addView(in);
direction = -1;
- statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop()
+ statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
+ mSmartspaceTopOffset;
} else {
- in = mClockFrame;
+ in = mSmallClockFrame;
out = mLargeClockFrame;
statusAreaYTranslation = 0f;
@@ -269,18 +190,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
/**
- * Set the amount (ratio) that the device has transitioned to doze.
- *
- * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
- */
- public void setDarkAmount(float darkAmount) {
- mDarkAmount = darkAmount;
- if (mClockPlugin != null) {
- mClockPlugin.setDarkAmount(darkAmount);
- }
- }
-
- /**
* Display the desired clock and hide the other one
*
* @return true if desired clock appeared and false if it was already visible
@@ -311,64 +220,11 @@ public class KeyguardClockSwitch extends RelativeLayout {
mChildrenAreLaidOut = true;
}
- public Paint getPaint() {
- return mClockView.getPaint();
- }
-
- public int getCurrentTextColor() {
- return mClockView.getCurrentTextColor();
- }
-
- public float getTextSize() {
- return mClockView.getTextSize();
- }
-
- /**
- * Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
- */
- public void refresh() {
- if (mClockPlugin != null) {
- mClockPlugin.onTimeTick();
- }
- }
-
- /**
- * Notifies that the time zone has changed.
- */
- public void onTimeZoneChanged(TimeZone timeZone) {
- if (mClockPlugin != null) {
- mClockPlugin.onTimeZoneChanged(timeZone);
- }
- }
-
- /**
- * Notifies that the time format has changed.
- *
- * @param timeFormat "12" for 12-hour format, "24" for 24-hour format
- */
- public void onTimeFormatChanged(String timeFormat) {
- if (mClockPlugin != null) {
- mClockPlugin.onTimeFormatChanged(timeFormat);
- }
- }
-
- void updateColors(ColorExtractor.GradientColors colors) {
- mSupportsDarkText = colors.supportsDarkText();
- mColorPalette = colors.getColorPalette();
- if (mClockPlugin != null) {
- mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
- }
- }
-
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardClockSwitch:");
- pw.println(" mClockPlugin: " + mClockPlugin);
- pw.println(" mClockFrame: " + mClockFrame);
+ pw.println(" mClockFrame: " + mSmallClockFrame);
pw.println(" mLargeClockFrame: " + mLargeClockFrame);
pw.println(" mStatusArea: " + mStatusArea);
- pw.println(" mDarkAmount: " + mDarkAmount);
- pw.println(" mSupportsDarkText: " + mSupportsDarkText);
- pw.println(" mColorPalette: " + Arrays.toString(mColorPalette));
pw.println(" mDisplayedClockSize: " + mDisplayedClockSize);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 6c32a4910c56..dd78f1cef78c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -22,8 +22,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
-import android.app.WallpaperManager;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.UserHandle;
import android.provider.Settings;
@@ -32,34 +30,30 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.clock.ClockManager;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.plugins.Clock;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.util.Locale;
-import java.util.TimeZone;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -69,48 +63,24 @@ import javax.inject.Inject;
*/
public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch>
implements Dumpable {
- private static final boolean CUSTOM_CLOCKS_ENABLED = true;
-
private final StatusBarStateController mStatusBarStateController;
- private final SysuiColorExtractor mColorExtractor;
- private final ClockManager mClockManager;
+ private final ClockRegistry mClockRegistry;
private final KeyguardSliceViewController mKeyguardSliceViewController;
private final NotificationIconAreaController mNotificationIconAreaController;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final BatteryController mBatteryController;
private final LockscreenSmartspaceController mSmartspaceController;
- private final Resources mResources;
private final SecureSettings mSecureSettings;
private final DumpManager mDumpManager;
+ private final ClockEventController mClockEventController;
- /**
- * Clock for both small and large sizes
- */
- private AnimatableClockController mClockViewController;
- private FrameLayout mClockFrame; // top aligned clock
- private AnimatableClockController mLargeClockViewController;
+ /** Clock frames for both small and large sizes */
+ private FrameLayout mSmallClockFrame; // top aligned clock
private FrameLayout mLargeClockFrame; // centered clock
@KeyguardClockSwitch.ClockSize
private int mCurrentClockSize = SMALL;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
private int mKeyguardClockTopMargin = 0;
-
- /**
- * Listener for changes to the color palette.
- *
- * The color palette changes when the wallpaper is changed.
- */
- private final ColorExtractor.OnColorsChangedListener mColorsListener =
- (extractor, which) -> {
- if ((which & WallpaperManager.FLAG_LOCK) != 0) {
- mView.updateColors(getGradientColors());
- }
- };
-
- private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
+ private final ClockRegistry.ClockChangeListener mClockChangedListener;
private ViewGroup mStatusArea;
// If set will replace keyguard_slice_view
@@ -119,9 +89,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private boolean mOnlyClock = false;
- private Executor mUiExecutor;
+ private final Executor mUiExecutor;
private boolean mCanShowDoubleLineClock = true;
- private ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
+ private final ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
@Override
public void onChange(boolean change) {
updateDoubleLineClock();
@@ -142,34 +112,32 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
public KeyguardClockSwitchController(
KeyguardClockSwitch keyguardClockSwitch,
StatusBarStateController statusBarStateController,
- SysuiColorExtractor colorExtractor,
- ClockManager clockManager,
+ ClockRegistry clockRegistry,
KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController,
- BroadcastDispatcher broadcastDispatcher,
- BatteryController batteryController,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
LockscreenSmartspaceController smartspaceController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SecureSettings secureSettings,
@Main Executor uiExecutor,
- @Main Resources resources,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ ClockEventController clockEventController,
+ FeatureFlags featureFlags) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
- mColorExtractor = colorExtractor;
- mClockManager = clockManager;
+ mClockRegistry = clockRegistry;
mKeyguardSliceViewController = keyguardSliceViewController;
mNotificationIconAreaController = notificationIconAreaController;
- mBroadcastDispatcher = broadcastDispatcher;
- mBatteryController = batteryController;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mSmartspaceController = smartspaceController;
- mResources = resources;
mSecureSettings = secureSettings;
mUiExecutor = uiExecutor;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mDumpManager = dumpManager;
+ mClockEventController = clockEventController;
+
+ mClockRegistry.setEnabled(featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS));
+ mClockChangedListener = () -> {
+ setClock(mClockRegistry.createCurrentClock());
+ };
}
/**
@@ -186,40 +154,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
public void onInit() {
mKeyguardSliceViewController.init();
- mClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
+ mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
- mClockViewController =
- new AnimatableClockController(
- mView.findViewById(R.id.animatable_clock_view),
- mStatusBarStateController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
- mResources);
- mClockViewController.init();
-
- mLargeClockViewController =
- new AnimatableClockController(
- mView.findViewById(R.id.animatable_clock_view_large),
- mStatusBarStateController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
- mResources);
- mLargeClockViewController.init();
-
mDumpManager.unregisterDumpable(getClass().toString()); // unregister previous clocks
mDumpManager.registerDumpable(getClass().toString(), this);
}
@Override
protected void onViewAttached() {
- if (CUSTOM_CLOCKS_ENABLED) {
- mClockManager.addOnClockChangedListener(mClockChangedListener);
- }
- mColorExtractor.addOnColorsChangedListener(mColorsListener);
- mView.updateColors(getGradientColors());
+ mClockRegistry.registerClockChangeListener(mClockChangedListener);
+ setClock(mClockRegistry.createCurrentClock());
+ mClockEventController.registerListeners();
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
@@ -242,7 +188,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
ksv.setVisibility(View.GONE);
addSmartspaceView(ksvIndex);
- updateClockLayout();
}
mSecureSettings.registerContentObserverForUser(
@@ -264,11 +209,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
@Override
protected void onViewDetached() {
- if (CUSTOM_CLOCKS_ENABLED) {
- mClockManager.removeOnClockChangedListener(mClockChangedListener);
- }
- mColorExtractor.removeOnColorsChangedListener(mColorsListener);
- mView.setClockPlugin(null, mStatusBarStateController.getState());
+ mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
+ mClockEventController.unregisterListeners();
+ setClock(null);
mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
@@ -307,18 +250,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mView.onDensityOrFontScaleChanged();
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
-
- updateClockLayout();
- }
-
- private void updateClockLayout() {
- int largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
- R.dimen.keyguard_large_clock_top_margin)
- - (int) mLargeClockViewController.getBottom();
- RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
- MATCH_PARENT);
- lp.topMargin = largeClockTopMargin;
- mLargeClockFrame.setLayoutParams(lp);
}
/**
@@ -332,46 +263,34 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mCurrentClockSize = clockSize;
+ Clock clock = getClock();
boolean appeared = mView.switchToClock(clockSize, animate);
- if (animate && appeared && clockSize == LARGE) {
- mLargeClockViewController.animateAppear();
+ if (clock != null && animate && appeared && clockSize == LARGE) {
+ clock.getAnimations().enter();
}
}
- public void animateFoldToAod() {
- if (mClockViewController != null) {
- mClockViewController.animateFoldAppear();
- mLargeClockViewController.animateFoldAppear();
- }
- }
-
- /**
- * If we're presenting a custom clock of just the default one.
- */
- public boolean hasCustomClock() {
- return mView.hasCustomClock();
- }
-
/**
- * Get the clock text size.
+ * Animates the clock view between folded and unfolded states
*/
- public float getClockTextSize() {
- return mView.getTextSize();
+ public void animateFoldToAod(float foldFraction) {
+ Clock clock = getClock();
+ if (clock != null) {
+ clock.getAnimations().fold(foldFraction);
+ }
}
/**
* Refresh clock. Called in response to TIME_TICK broadcasts.
*/
void refresh() {
- if (mClockViewController != null) {
- mClockViewController.refreshTime();
- mLargeClockViewController.refreshTime();
- }
if (mSmartspaceController != null) {
mSmartspaceController.requestSmartspaceUpdate();
}
-
- mView.refresh();
+ Clock clock = getClock();
+ if (clock != null) {
+ clock.getEvents().onTimeTick();
+ }
}
/**
@@ -383,7 +302,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
- PropertyAnimator.setProperty(mClockFrame, AnimatableProperty.TRANSLATION_X,
+ PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
x, props, animate);
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
scale, props, animate);
@@ -396,25 +315,39 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
}
- void updateTimeZone(TimeZone timeZone) {
- mView.onTimeZoneChanged(timeZone);
- }
-
/**
* Get y-bottom position of the currently visible clock on the keyguard.
* We can't directly getBottom() because clock changes positions in AOD for burn-in
*/
int getClockBottom(int statusBarHeaderHeight) {
+ Clock clock = getClock();
+ if (clock == null) {
+ return 0;
+ }
+
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
- View clock = mLargeClockFrame.findViewById(
- com.android.systemui.R.id.animatable_clock_view_large);
int frameHeight = mLargeClockFrame.getHeight();
- int clockHeight = clock.getHeight();
+ int clockHeight = clock.getLargeClock().getHeight();
return frameHeight / 2 + clockHeight / 2;
} else {
- return mClockFrame.findViewById(
- com.android.systemui.R.id.animatable_clock_view).getHeight()
- + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ int clockHeight = clock.getSmallClock().getHeight();
+ return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ }
+ }
+
+ /**
+ * Get the height of the currently visible clock on the keyguard.
+ */
+ int getClockHeight() {
+ Clock clock = getClock();
+ if (clock == null) {
+ return 0;
+ }
+
+ if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
+ return clock.getLargeClock().getHeight();
+ } else {
+ return clock.getSmallClock().getHeight();
}
}
@@ -429,12 +362,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mNotificationIconAreaController.setupAodIcons(nic);
}
- private void setClockPlugin(ClockPlugin plugin) {
- mView.setClockPlugin(plugin, mStatusBarStateController.getState());
+ private void setClock(Clock clock) {
+ mClockEventController.setClock(clock);
+ mView.setClock(clock, mStatusBarStateController.getState());
}
- private ColorExtractor.GradientColors getGradientColors() {
- return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
+ private Clock getClock() {
+ return mClockEventController.getClock();
}
private int getCurrentLayoutDirection() {
@@ -467,8 +401,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
- mClockViewController.dump(pw);
- mLargeClockViewController.dump(pw);
+ Clock clock = getClock();
+ if (clock != null) {
+ clock.dump(pw);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 5ee659be6dd2..0b3fe46007e5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -343,7 +343,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
if (!mSidefpsController.isPresent()) {
return;
}
- if (mBouncerVisible && mView.isSidedSecurityMode()
+ if (mBouncerVisible
+ && getResources().getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
&& mUpdateMonitor.isFingerprintDetectionRunning()
&& !mUpdateMonitor.userNeedsStrongAuth()) {
mSidefpsController.get().show();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index cb3172dabdb1..83e23bd52f19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -17,14 +17,11 @@
package com.android.keyguard;
import android.content.Context;
-import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridLayout;
-import androidx.core.graphics.ColorUtils;
-
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -46,7 +43,6 @@ public class KeyguardStatusView extends GridLayout {
private View mMediaHostContainer;
private float mDarkAmount = 0;
- private int mTextColor;
public KeyguardStatusView(Context context) {
this(context, null, 0);
@@ -71,7 +67,6 @@ public class KeyguardStatusView extends GridLayout {
}
mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
- mTextColor = mClockView.getCurrentTextColor();
mMediaHostContainer = findViewById(R.id.status_view_media_container);
@@ -83,15 +78,12 @@ public class KeyguardStatusView extends GridLayout {
return;
}
mDarkAmount = darkAmount;
- mClockView.setDarkAmount(darkAmount);
CrossFadeHelper.fadeOut(mMediaHostContainer, darkAmount);
updateDark();
}
void updateDark() {
- final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
mKeyguardSlice.setDarkAmount(mDarkAmount);
- mClockView.setTextColor(blendedTextColor);
}
/** Sets a translationY value on every child view except for the media view. */
@@ -113,7 +105,6 @@ public class KeyguardStatusView extends GridLayout {
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardStatusView:");
pw.println(" mDarkAmount: " + mDarkAmount);
- pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
if (mClockView != null) {
mClockView.dump(pw, args);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 014d08288158..c715a4eaef2b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -30,8 +30,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
-import java.util.TimeZone;
-
import javax.inject.Inject;
/**
@@ -96,13 +94,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
/**
- * The amount we're in doze.
- */
- public void setDarkAmount(float darkAmount) {
- mView.setDarkAmount(darkAmount);
- }
-
- /**
* Set which clock should be displayed on the keyguard. The other one will be automatically
* hidden.
*/
@@ -114,16 +105,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
* Performs fold to aod animation of the clocks (changes font weight from bold to thin).
* This animation is played when AOD is enabled and foldable device is fully folded, it is
* displayed on the outer screen
+ * @param foldFraction current fraction of fold animation complete
*/
- public void animateFoldToAod() {
- mKeyguardClockSwitchController.animateFoldToAod();
- }
-
- /**
- * If we're presenting a custom clock of just the default one.
- */
- public boolean hasCustomClock() {
- return mKeyguardClockSwitchController.hasCustomClock();
+ public void animateFoldToAod(float foldFraction) {
+ mKeyguardClockSwitchController.animateFoldToAod(foldFraction);
}
/**
@@ -143,24 +128,11 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
/**
- * Set pivot x.
- */
- public void setPivotX(float pivot) {
- mView.setPivotX(pivot);
- }
-
- /**
- * Set pivot y.
- */
- public void setPivotY(float pivot) {
- mView.setPivotY(pivot);
- }
-
- /**
- * Get the clock text size.
+ * Update the pivot position based on the parent view
*/
- public float getClockTextSize() {
- return mKeyguardClockSwitchController.getClockTextSize();
+ public void updatePivot(float parentWidth, float parentHeight) {
+ mView.setPivotX(parentWidth / 2f);
+ mView.setPivotY(mKeyguardClockSwitchController.getClockHeight() / 2f);
}
/**
@@ -240,11 +212,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
@Override
- public void onTimeZoneChanged(TimeZone timeZone) {
- mKeyguardClockSwitchController.updateTimeZone(timeZone);
- }
-
- @Override
public void onKeyguardVisibilityChanged(boolean showing) {
if (showing) {
if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 4ae2cad4a01f..2b44199ac9ee 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -66,7 +66,6 @@ import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.nfc.NfcAdapter;
-import android.os.Build;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IRemoteCallback;
@@ -96,6 +95,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.Dumpable;
@@ -141,12 +141,6 @@ import javax.inject.Inject;
public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable {
private static final String TAG = "KeyguardUpdateMonitor";
- private static final boolean DEBUG = KeyguardConstants.DEBUG;
- private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES;
- private static final boolean DEBUG_FACE = Build.IS_DEBUGGABLE;
- private static final boolean DEBUG_FINGERPRINT = Build.IS_DEBUGGABLE;
- private static final boolean DEBUG_ACTIVE_UNLOCK = Build.IS_DEBUGGABLE;
- private static final boolean DEBUG_SPEW = false;
private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
// Callback messages
@@ -241,6 +235,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private final Context mContext;
+ private final KeyguardUpdateMonitorLogger mLogger;
private final boolean mIsPrimaryUser;
private final AuthController mAuthController;
private final StatusBarStateController mStatusBarStateController;
@@ -337,17 +332,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final int HAL_ERROR_RETRY_TIMEOUT = 500; // ms
private static final int HAL_ERROR_RETRY_MAX = 20;
- private final Runnable mFpCancelNotReceived = () -> {
- Log.e(TAG, "Fp cancellation not received, transitioning to STOPPED");
- mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
- };
+ private final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived;
- private final Runnable mFaceCancelNotReceived = () -> {
- Log.e(TAG, "Face cancellation not received, transitioning to STOPPED");
- mFaceRunningState = BIOMETRIC_STATE_STOPPED;
- updateFaceListeningState(BIOMETRIC_ACTION_STOP);
- };
+ private final Runnable mFaceCancelNotReceived = this::onFaceCancelNotReceived;
private final Handler mHandler;
@@ -470,17 +457,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void handleSimSubscriptionInfoChanged() {
Assert.isMainThread();
- if (DEBUG_SIM_STATES) {
- Log.v(TAG, "onSubscriptionInfoChanged()");
- List<SubscriptionInfo> sil = mSubscriptionManager
- .getCompleteActiveSubscriptionInfoList();
- if (sil != null) {
- for (SubscriptionInfo subInfo : sil) {
- Log.v(TAG, "SubInfo:" + subInfo);
- }
- } else {
- Log.v(TAG, "onSubscriptionInfoChanged: list is null");
+ mLogger.v("onSubscriptionInfoChanged()");
+ List<SubscriptionInfo> sil = mSubscriptionManager
+ .getCompleteActiveSubscriptionInfoList();
+ if (sil != null) {
+ for (SubscriptionInfo subInfo : sil) {
+ mLogger.logSubInfo(subInfo);
}
+ } else {
+ mLogger.v("onSubscriptionInfoChanged: list is null");
}
List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */);
@@ -504,8 +489,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
while (iter.hasNext()) {
Map.Entry<Integer, SimData> simData = iter.next();
if (!activeSubIds.contains(simData.getKey())) {
- Log.i(TAG, "Previously active sub id " + simData.getKey() + " is now invalid, "
- + "will remove");
+ mLogger.logInvalidSubId(simData.getKey());
iter.remove();
SimData data = simData.getValue();
@@ -690,7 +674,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
try {
mDreamManager.awaken();
} catch (RemoteException e) {
- Log.e(TAG, "Unable to awaken from dream");
+ mLogger.logException(e, "Unable to awaken from dream");
}
}
}
@@ -774,15 +758,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
try {
userId = ActivityManager.getService().getCurrentUser().id;
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get current user id: ", e);
+ mLogger.logException(e, "Failed to get current user id");
return;
}
if (userId != authUserId) {
- Log.d(TAG, "Fingerprint authenticated for wrong user: " + authUserId);
+ mLogger.logFingerprintAuthForWrongUser(authUserId);
return;
}
if (isFingerprintDisabled(userId)) {
- Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);
+ mLogger.logFingerprintDisabledForUser(userId);
return;
}
onFingerprintAuthenticated(userId, isStrongBiometric);
@@ -805,8 +789,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private Runnable mRetryFingerprintAuthentication = new Runnable() {
@Override
public void run() {
- Log.w(TAG,
- "Retrying fingerprint attempt: " + mHardwareFingerprintUnavailableRetryCount);
+ mLogger.logRetryAfterFpHwUnavailable(mHardwareFingerprintUnavailableRetryCount);
if (mFpm.isHardwareDetected()) {
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
@@ -816,6 +799,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
};
+ private void onFingerprintCancelNotReceived() {
+ mLogger.e("Fp cancellation not received, transitioning to STOPPED");
+ mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
+ }
+
private void handleFingerprintError(int msgId, String errString) {
Assert.isMainThread();
if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
@@ -843,7 +832,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
mFingerprintLockedOutPermanent = true;
- Log.d(TAG, "Fingerprint locked out - requiring strong auth");
+ mLogger.d("Fingerprint locked out - requiring strong auth");
mLockPatternUtils.requireStrongAuth(
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
}
@@ -871,7 +860,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private void handleFingerprintLockoutReset(@LockoutMode int mode) {
- Log.d(TAG, "handleFingerprintLockoutReset: " + mode);
+ mLogger.logFingerprintLockoutReset(mode);
final boolean wasLockout = mFingerprintLockedOut;
final boolean wasLockoutPermanent = mFingerprintLockedOutPermanent;
mFingerprintLockedOut = (mode == BIOMETRIC_LOCKOUT_TIMED)
@@ -902,7 +891,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
mFingerprintRunningState = fingerprintRunningState;
- Log.d(TAG, "fingerprintRunningState: " + mFingerprintRunningState);
+ mLogger.logFingerprintRunningState(mFingerprintRunningState);
// Clients of KeyguardUpdateMonitor don't care about the internal state about the
// asynchronousness of the cancel cycle. So only notify them if the actually running state
// has changed.
@@ -969,7 +958,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void handleFaceAcquired(int acquireInfo) {
Assert.isMainThread();
- if (DEBUG_FACE) Log.d(TAG, "Face acquired acquireInfo=" + acquireInfo);
+ mLogger.logFaceAcquired(acquireInfo);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -982,25 +971,25 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
try {
if (mGoingToSleep) {
- Log.d(TAG, "Aborted successful auth because device is going to sleep.");
+ mLogger.d("Aborted successful auth because device is going to sleep.");
return;
}
final int userId;
try {
userId = ActivityManager.getService().getCurrentUser().id;
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get current user id: ", e);
+ mLogger.logException(e, "Failed to get current user id");
return;
}
if (userId != authUserId) {
- Log.d(TAG, "Face authenticated for wrong user: " + authUserId);
+ mLogger.logFaceAuthForWrongUser(authUserId);
return;
}
if (isFaceDisabled(userId)) {
- Log.d(TAG, "Face authentication disabled by DPM for userId: " + userId);
+ mLogger.logFaceAuthDisabledForUser(userId);
return;
}
- if (DEBUG_FACE) Log.d(TAG, "Face auth succeeded for user " + userId);
+ mLogger.logFaceAuthSuccess(userId);
onFaceAuthenticated(userId, isStrongBiometric);
} finally {
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
@@ -1010,7 +999,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void handleFaceHelp(int msgId, String helpString) {
Assert.isMainThread();
- if (DEBUG_FACE) Log.d(TAG, "Face help received: " + helpString);
+ mLogger.logFaceAuthHelpMsg(msgId, helpString);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1022,15 +1011,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private Runnable mRetryFaceAuthentication = new Runnable() {
@Override
public void run() {
- Log.w(TAG, "Retrying face after HW unavailable, attempt " +
- mHardwareFaceUnavailableRetryCount);
+ mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
}
};
- private void handleFaceError(int msgId, String errString) {
+ private void onFaceCancelNotReceived() {
+ mLogger.e("Face cancellation not received, transitioning to STOPPED");
+ mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+ KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP);
+ }
+
+ private void handleFaceError(int msgId, final String originalErrMsg) {
Assert.isMainThread();
- if (DEBUG_FACE) Log.d(TAG, "Face error received: " + errString + " msgId=" + msgId);
+ String errString = originalErrMsg;
+ mLogger.logFaceAuthError(msgId, originalErrMsg);
if (mHandler.hasCallbacks(mFaceCancelNotReceived)) {
mHandler.removeCallbacks(mFaceCancelNotReceived);
}
@@ -1087,7 +1082,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private void handleFaceLockoutReset(@LockoutMode int mode) {
- Log.d(TAG, "handleFaceLockoutReset: " + mode);
+ mLogger.logFaceLockoutReset(mode);
final boolean wasLockoutPermanent = mFaceLockedOutPermanent;
mFaceLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent);
@@ -1105,7 +1100,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
boolean wasRunning = mFaceRunningState == BIOMETRIC_STATE_RUNNING;
boolean isRunning = faceRunningState == BIOMETRIC_STATE_RUNNING;
mFaceRunningState = faceRunningState;
- Log.d(TAG, "faceRunningState: " + mFaceRunningState);
+ mLogger.logFaceRunningState(mFaceRunningState);
// Clients of KeyguardUpdateMonitor don't care about the internal state or about the
// asynchronousness of the cancel cycle. So only notify them if the actually running state
// has changed.
@@ -1221,8 +1216,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
UserHandle.of(userId));
if (supervisorComponent == null) {
- Log.e(TAG, "No Profile Owner or Device Owner supervision app found for User "
- + userId);
+ mLogger.logMissingSupervisorAppError(userId);
} else {
Intent intent =
new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE)
@@ -1354,7 +1348,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (DEBUG) Log.d(TAG, "received broadcast " + action);
+ mLogger.logBroadcastReceived(action);
if (Intent.ACTION_TIME_TICK.equals(action)
|| Intent.ACTION_TIME_CHANGED.equals(action)) {
@@ -1381,12 +1375,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
return;
}
- if (DEBUG_SIM_STATES) {
- Log.v(TAG, "action " + action
- + " state: " + intent.getStringExtra(
- Intent.EXTRA_SIM_STATE)
- + " slotId: " + args.slotId + " subid: " + args.subId);
- }
+ mLogger.logSimStateFromIntent(action,
+ intent.getStringExtra(Intent.EXTRA_SIM_STATE),
+ args.slotId,
+ args.subId);
mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, args.subId, args.slotId, args.simState)
.sendToTarget();
} else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
@@ -1398,10 +1390,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- if (DEBUG) {
- Log.v(TAG, "action " + action + " serviceState=" + serviceState + " subId="
- + subId);
- }
+ mLogger.logServiceStateIntent(action, serviceState, subId);
mHandler.sendMessage(
mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
} else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
@@ -1521,7 +1510,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
@Override
public void onUdfpsPointerDown(int sensorId) {
- Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId);
+ mLogger.logUdfpsPointerDown(sensorId);
requestFaceAuth(true);
}
@@ -1530,7 +1519,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
@Override
public void onUdfpsPointerUp(int sensorId) {
- Log.d(TAG, "onUdfpsPointerUp, sensorId: " + sensorId);
+ mLogger.logUdfpsPointerUp(sensorId);
}
};
@@ -1826,7 +1815,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
TelephonyListenerManager telephonyListenerManager,
InteractionJankMonitor interactionJankMonitor,
LatencyTracker latencyTracker,
- ActiveUnlockConfig activeUnlockConfiguration) {
+ ActiveUnlockConfig activeUnlockConfiguration,
+ KeyguardUpdateMonitorLogger logger) {
+ mLogger = logger;
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mTelephonyListenerManager = telephonyListenerManager;
@@ -2170,13 +2161,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
if (runningOrRestarting && !shouldListenForFingerprint) {
if (action == BIOMETRIC_ACTION_START) {
- Log.v(TAG, "Ignoring stopListeningForFingerprint()");
+ mLogger.v("Ignoring stopListeningForFingerprint()");
return;
}
stopListeningForFingerprint();
} else if (!runningOrRestarting && shouldListenForFingerprint) {
if (action == BIOMETRIC_ACTION_STOP) {
- Log.v(TAG, "Ignoring startListeningForFingerprint()");
+ mLogger.v("Ignoring startListeningForFingerprint()");
return;
}
startListeningForFingerprint();
@@ -2202,7 +2193,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* @param active If the interrupt started or ended.
*/
public void onAuthInterruptDetected(boolean active) {
- if (DEBUG) Log.d(TAG, "onAuthInterruptDetected(" + active + ")");
+ mLogger.logAuthInterruptDetected(active);
if (mAuthInterruptActive == active) {
return;
}
@@ -2217,7 +2208,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* @param userInitiatedRequest true if the user explicitly requested face auth
*/
public void requestFaceAuth(boolean userInitiatedRequest) {
- if (DEBUG) Log.d(TAG, "requestFaceAuth() userInitiated=" + userInitiatedRequest);
+ mLogger.logFaceAuthRequested(userInitiatedRequest);
mIsFaceAuthUserRequested |= userInitiatedRequest;
updateFaceListeningState(BIOMETRIC_ACTION_START);
}
@@ -2243,14 +2234,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
boolean shouldListenForFace = shouldListenForFace();
if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) {
if (action == BIOMETRIC_ACTION_START) {
- Log.v(TAG, "Ignoring stopListeningForFace()");
+ mLogger.v("Ignoring stopListeningForFace()");
return;
}
mIsFaceAuthUserRequested = false;
stopListeningForFace();
} else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING && shouldListenForFace) {
if (action == BIOMETRIC_ACTION_STOP) {
- Log.v(TAG, "Ignoring startListeningForFace()");
+ mLogger.v("Ignoring startListeningForFace()");
return;
}
startListeningForFace();
@@ -2267,9 +2258,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
if (shouldTriggerActiveUnlock()) {
- if (DEBUG_ACTIVE_UNLOCK) {
- Log.d("ActiveUnlock", "initiate active unlock triggerReason=" + reason);
- }
+ mLogger.logActiveUnlockTriggered(reason);
mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser());
}
}
@@ -2297,12 +2286,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
if (allowRequest && shouldTriggerActiveUnlock()) {
- if (DEBUG_ACTIVE_UNLOCK) {
- Log.d("ActiveUnlock", "reportUserRequestedUnlock"
- + " origin=" + requestOrigin.name()
- + " reason=" + reason
- + " dismissKeyguard=" + dismissKeyguard);
- }
+ mLogger.logUserRequestedUnlock(requestOrigin, reason, dismissKeyguard);
mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser(),
dismissKeyguard);
}
@@ -2452,32 +2436,30 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
&& shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
- if (DEBUG_FINGERPRINT || DEBUG_SPEW) {
- maybeLogListenerModelData(
- new KeyguardFingerprintListenModel(
- System.currentTimeMillis(),
- user,
- shouldListen,
- biometricEnabledForUser,
- mBouncerIsOrWillBeShowing,
- userCanSkipBouncer,
- mCredentialAttempted,
- mDeviceInteractive,
- mIsDreaming,
- isEncryptedOrLockdownForUser,
- fingerprintDisabledForUser,
- mFingerprintLockedOut,
- mGoingToSleep,
- mKeyguardGoingAway,
- mKeyguardIsVisible,
- mKeyguardOccluded,
- mOccludingAppRequestingFp,
- mIsPrimaryUser,
- shouldListenForFingerprintAssistant,
- mSwitchingUser,
- isUdfps,
- userDoesNotHaveTrust));
- }
+ maybeLogListenerModelData(
+ new KeyguardFingerprintListenModel(
+ System.currentTimeMillis(),
+ user,
+ shouldListen,
+ biometricEnabledForUser,
+ mBouncerIsOrWillBeShowing,
+ userCanSkipBouncer,
+ mCredentialAttempted,
+ mDeviceInteractive,
+ mIsDreaming,
+ isEncryptedOrLockdownForUser,
+ fingerprintDisabledForUser,
+ mFingerprintLockedOut,
+ mGoingToSleep,
+ mKeyguardGoingAway,
+ mKeyguardIsVisible,
+ mKeyguardOccluded,
+ mOccludingAppRequestingFp,
+ mIsPrimaryUser,
+ shouldListenForFingerprintAssistant,
+ mSwitchingUser,
+ isUdfps,
+ userDoesNotHaveTrust));
return shouldListen;
}
@@ -2552,59 +2534,49 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& !fpLockedout;
// Aggregate relevant fields for debug logging.
- if (DEBUG_FACE || DEBUG_SPEW) {
- maybeLogListenerModelData(
- new KeyguardFaceListenModel(
- System.currentTimeMillis(),
- user,
- shouldListen,
- mAuthInterruptActive,
- becauseCannotSkipBouncer,
- biometricEnabledForUser,
- mBouncerFullyShown,
- faceAuthenticated,
- faceDisabledForUser,
- mGoingToSleep,
- awakeKeyguard,
- mKeyguardGoingAway,
- shouldListenForFaceAssistant,
- mOccludingAppRequestingFace,
- mIsPrimaryUser,
- strongAuthAllowsScanning,
- mSecureCameraLaunched,
- mSwitchingUser,
- mUdfpsBouncerShowing));
- }
+ maybeLogListenerModelData(
+ new KeyguardFaceListenModel(
+ System.currentTimeMillis(),
+ user,
+ shouldListen,
+ mAuthInterruptActive,
+ becauseCannotSkipBouncer,
+ biometricEnabledForUser,
+ mBouncerFullyShown,
+ faceAuthenticated,
+ faceDisabledForUser,
+ mGoingToSleep,
+ awakeKeyguard,
+ mKeyguardGoingAway,
+ shouldListenForFaceAssistant,
+ mOccludingAppRequestingFace,
+ mIsPrimaryUser,
+ strongAuthAllowsScanning,
+ mSecureCameraLaunched,
+ mSwitchingUser,
+ mUdfpsBouncerShowing));
return shouldListen;
}
private void maybeLogListenerModelData(KeyguardListenModel model) {
- // Too chatty, but very useful when debugging issues.
- if (DEBUG_SPEW) {
- Log.v(TAG, model.toString());
- }
+ mLogger.logKeyguardListenerModel(model);
- if (DEBUG_ACTIVE_UNLOCK
- && model instanceof KeyguardActiveUnlockModel) {
+ if (model instanceof KeyguardActiveUnlockModel) {
mListenModels.add(model);
return;
}
// Add model data to the historical buffer.
final boolean notYetRunning =
- (DEBUG_FACE
- && model instanceof KeyguardFaceListenModel
- && mFaceRunningState != BIOMETRIC_STATE_RUNNING)
- || (DEBUG_FINGERPRINT
- && model instanceof KeyguardFingerprintListenModel
- && mFingerprintRunningState != BIOMETRIC_STATE_RUNNING);
+ (model instanceof KeyguardFaceListenModel
+ && mFaceRunningState != BIOMETRIC_STATE_RUNNING)
+ || (model instanceof KeyguardFingerprintListenModel
+ && mFingerprintRunningState != BIOMETRIC_STATE_RUNNING);
final boolean running =
- (DEBUG_FACE
- && model instanceof KeyguardFaceListenModel
+ (model instanceof KeyguardFaceListenModel
&& mFaceRunningState == BIOMETRIC_STATE_RUNNING)
- || (DEBUG_FINGERPRINT
- && model instanceof KeyguardFingerprintListenModel
+ || (model instanceof KeyguardFingerprintListenModel
&& mFingerprintRunningState == BIOMETRIC_STATE_RUNNING);
if (notYetRunning && model.getListening()
|| running && !model.getListening()) {
@@ -2616,9 +2588,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final int userId = getCurrentUser();
final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
if (mFingerprintCancelSignal != null) {
- Log.e(TAG, "Cancellation signal is not null, high chance of bug in fp auth lifecycle"
- + " management. FP state: " + mFingerprintRunningState
- + ", unlockPossible: " + unlockPossible);
+ mLogger.logUnexpectedFpCancellationSignalState(
+ mFingerprintRunningState,
+ unlockPossible);
}
if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING) {
@@ -2629,7 +2601,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// Waiting for restart via handleFingerprintError().
return;
}
- if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
+ mLogger.v("startListeningForFingerprint()");
if (unlockPossible) {
mFingerprintCancelSignal = new CancellationSignal();
@@ -2650,9 +2622,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final int userId = getCurrentUser();
final boolean unlockPossible = isUnlockWithFacePossible(userId);
if (mFaceCancelSignal != null) {
- Log.e(TAG, "Cancellation signal is not null, high chance of bug in face auth lifecycle"
- + " management. Face state: " + mFaceRunningState
- + ", unlockPossible: " + unlockPossible);
+ mLogger.logUnexpectedFaceCancellationSignalState(mFaceRunningState, unlockPossible);
}
if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {
@@ -2662,7 +2632,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// Waiting for ERROR_CANCELED before requesting auth again
return;
}
- if (DEBUG) Log.v(TAG, "startListeningForFace(): " + mFaceRunningState);
+ mLogger.logStartedListeningForFace(mFaceRunningState);
if (unlockPossible) {
mFaceCancelSignal = new CancellationSignal();
@@ -2728,7 +2698,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private void stopListeningForFingerprint() {
- if (DEBUG) Log.v(TAG, "stopListeningForFingerprint()");
+ mLogger.v("stopListeningForFingerprint()");
if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING) {
if (mFingerprintCancelSignal != null) {
mFingerprintCancelSignal.cancel();
@@ -2744,7 +2714,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private void stopListeningForFace() {
- if (DEBUG) Log.v(TAG, "stopListeningForFace()");
+ mLogger.v("stopListeningForFace()");
if (mFaceRunningState == BIOMETRIC_STATE_RUNNING) {
if (mFaceCancelSignal != null) {
mFaceCancelSignal.cancel();
@@ -2773,7 +2743,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
if (mDeviceProvisioned) {
mHandler.sendEmptyMessage(MSG_DEVICE_PROVISIONED);
}
- if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
+ mLogger.logDeviceProvisionedState(mDeviceProvisioned);
}
};
@@ -2878,7 +2848,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
private void handlePhoneStateChanged(String newState) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")");
+ mLogger.logPhoneStateChanged(newState);
if (TelephonyManager.EXTRA_STATE_IDLE.equals(newState)) {
mPhoneState = TelephonyManager.CALL_STATE_IDLE;
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(newState)) {
@@ -2899,7 +2869,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
private void handleTimeUpdate() {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleTimeUpdate");
+ mLogger.d("handleTimeUpdate");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2913,7 +2883,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
private void handleTimeZoneUpdate(String timeZone) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleTimeZoneUpdate");
+ mLogger.d("handleTimeZoneUpdate");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2931,7 +2901,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
private void handleTimeFormatUpdate(String timeFormat) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleTimeFormatUpdate timeFormat=" + timeFormat);
+ mLogger.logTimeFormatChanged(timeFormat);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2945,7 +2915,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
private void handleBatteryUpdate(BatteryStatus status) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleBatteryUpdate");
+ mLogger.d("handleBatteryUpdate");
final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status);
mBatteryStatus = status;
if (batteryUpdateInteresting) {
@@ -2982,14 +2952,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@VisibleForTesting
void handleSimStateChange(int subId, int slotId, int state) {
Assert.isMainThread();
- if (DEBUG_SIM_STATES) {
- Log.d(TAG, "handleSimStateChange(subId=" + subId + ", slotId="
- + slotId + ", state=" + state + ")");
- }
+ mLogger.logSimState(subId, slotId, state);
boolean becameAbsent = false;
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- Log.w(TAG, "invalid subId in handleSimStateChange()");
+ mLogger.w("invalid subId in handleSimStateChange()");
/* Only handle No SIM(ABSENT) and Card Error(CARD_IO_ERROR) due to
* handleServiceStateChange() handle other case */
if (state == TelephonyManager.SIM_STATE_ABSENT) {
@@ -3038,13 +3005,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
@VisibleForTesting
void handleServiceStateChange(int subId, ServiceState serviceState) {
- if (DEBUG) {
- Log.d(TAG,
- "handleServiceStateChange(subId=" + subId + ", serviceState=" + serviceState);
- }
+ mLogger.logServiceStateChange(subId, serviceState);
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- Log.w(TAG, "invalid subId in handleServiceStateChange()");
+ mLogger.w("invalid subId in handleServiceStateChange()");
return;
} else {
updateTelephonyCapable(true);
@@ -3066,7 +3030,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
public void onKeyguardVisibilityChanged(boolean showing) {
Assert.isMainThread();
- Log.d(TAG, "onKeyguardVisibilityChanged(" + showing + ")");
+ mLogger.logKeyguardVisibilityChanged(showing);
mKeyguardIsVisible = showing;
if (showing) {
@@ -3086,7 +3050,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* Handle {@link #MSG_KEYGUARD_RESET}
*/
private void handleKeyguardReset() {
- if (DEBUG) Log.d(TAG, "handleKeyguardReset");
+ mLogger.d("handleKeyguardReset");
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
}
@@ -3100,8 +3064,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
0 /* flags */, getCurrentUser());
if (resolveInfo == null) {
- Log.w(TAG, "resolveNeedsSlowUnlockTransition: returning false since activity "
- + "could not be resolved.");
+ mLogger.w("resolveNeedsSlowUnlockTransition: returning false since activity could "
+ + "not be resolved.");
return false;
}
@@ -3119,11 +3083,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean wasBouncerFullyShown = mBouncerFullyShown;
mBouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing == 1;
mBouncerFullyShown = bouncerFullyShown == 1;
- if (DEBUG) {
- Log.d(TAG, "handleKeyguardBouncerChanged"
- + " bouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing
- + " bouncerFullyShowing=" + mBouncerFullyShown);
- }
+ mLogger.logKeyguardBouncerChanged(mBouncerIsOrWillBeShowing, mBouncerFullyShown);
if (mBouncerFullyShown) {
// If the bouncer is shown, always clear this flag. This can happen in the following
@@ -3243,9 +3203,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
public void removeCallback(KeyguardUpdateMonitorCallback callback) {
Assert.isMainThread();
- if (DEBUG) {
- Log.v(TAG, "*** unregister callback for " + callback);
- }
+ mLogger.logUnregisterCallback(callback);
mCallbacks.removeIf(el -> el.get() == callback);
}
@@ -3258,15 +3216,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
public void registerCallback(KeyguardUpdateMonitorCallback callback) {
Assert.isMainThread();
- if (DEBUG) Log.v(TAG, "*** register callback for " + callback);
+ mLogger.logRegisterCallback(callback);
// Prevent adding duplicate callbacks
for (int i = 0; i < mCallbacks.size(); i++) {
if (mCallbacks.get(i).get() == callback) {
- if (DEBUG) {
- Log.e(TAG, "Object tried to add another callback",
- new Exception("Called by"));
- }
+ mLogger.logException(
+ new Exception("Called by"),
+ "Object tried to add another callback");
return;
}
}
@@ -3316,11 +3273,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
public void sendKeyguardBouncerChanged(boolean bouncerIsOrWillBeShowing,
boolean bouncerFullyShown) {
- if (DEBUG) {
- Log.d(TAG, "sendKeyguardBouncerChanged"
- + " bouncerIsOrWillBeShowing=" + bouncerIsOrWillBeShowing
- + " bouncerFullyShown=" + bouncerFullyShown);
- }
+ mLogger.logSendKeyguardBouncerChanged(bouncerIsOrWillBeShowing, bouncerFullyShown);
Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
message.arg1 = bouncerIsOrWillBeShowing ? 1 : 0;
message.arg2 = bouncerFullyShown ? 1 : 0;
@@ -3337,7 +3290,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
@MainThread
public void reportSimUnlocked(int subId) {
- if (DEBUG_SIM_STATES) Log.v(TAG, "reportSimUnlocked(subId=" + subId + ")");
+ mLogger.logSimUnlocked(subId);
handleSimStateChange(subId, getSlotId(subId), TelephonyManager.SIM_STATE_READY);
}
@@ -3614,7 +3567,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
- Log.d(TAG, "RemoteException onDestroy. cannot unregister userSwitchObserver");
+ mLogger.logException(
+ e,
+ "RemoteException onDestroy. cannot unregister userSwitchObserver");
}
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
new file mode 100644
index 000000000000..035b7f07a5f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -0,0 +1,326 @@
+/*
+ * 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.keyguard.logging
+
+import android.hardware.biometrics.BiometricConstants.LockoutMode
+import android.telephony.ServiceState
+import android.telephony.SubscriptionInfo
+import com.android.keyguard.ActiveUnlockConfig
+import com.android.keyguard.KeyguardListenModel
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "KeyguardUpdateMonitorLog"
+
+/**
+ * Helper class for logging for [com.android.keyguard.KeyguardUpdateMonitor]
+ */
+class KeyguardUpdateMonitorLogger @Inject constructor(
+ @KeyguardUpdateMonitorLog private val logBuffer: LogBuffer
+) {
+ fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+
+ fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+ fun v(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+ fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+
+ fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
+
+ fun logActiveUnlockTriggered(reason: String) {
+ logBuffer.log("ActiveUnlock", DEBUG,
+ { str1 = reason },
+ { "initiate active unlock triggerReason=$str1" })
+ }
+
+ fun logAuthInterruptDetected(active: Boolean) {
+ logBuffer.log(TAG, DEBUG,
+ { bool1 = active },
+ { "onAuthInterruptDetected($bool1)" })
+ }
+
+ fun logBroadcastReceived(action: String?) {
+ logBuffer.log(TAG, DEBUG, { str1 = action }, { "received broadcast $str1" })
+ }
+
+ fun logDeviceProvisionedState(deviceProvisioned: Boolean) {
+ logBuffer.log(TAG, DEBUG,
+ { bool1 = deviceProvisioned },
+ { "DEVICE_PROVISIONED state = $bool1" })
+ }
+
+ fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
+ logBuffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
+ }
+
+ fun logFaceAcquired(acquireInfo: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = acquireInfo },
+ { "Face acquired acquireInfo=$int1" })
+ }
+
+ fun logFaceAuthDisabledForUser(userId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = userId },
+ { "Face authentication disabled by DPM for userId: $int1" })
+ }
+ fun logFaceAuthError(msgId: Int, originalErrMsg: String) {
+ logBuffer.log(TAG, DEBUG, {
+ str1 = originalErrMsg
+ int1 = msgId
+ }, { "Face error received: $str1 msgId= $int1" })
+ }
+
+ fun logFaceAuthForWrongUser(authUserId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = authUserId },
+ { "Face authenticated for wrong user: $int1" })
+ }
+
+ fun logFaceAuthHelpMsg(msgId: Int, helpMsg: String) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = msgId
+ str1 = helpMsg
+ }, { "Face help received, msgId: $int1 msg: $str1" })
+ }
+
+ fun logFaceAuthRequested(userInitiatedRequest: Boolean) {
+ logBuffer.log(TAG, DEBUG,
+ { bool1 = userInitiatedRequest },
+ { "requestFaceAuth() userInitiated=$bool1" })
+ }
+
+ fun logFaceAuthSuccess(userId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = userId },
+ { "Face auth succeeded for user $int1" })
+ }
+
+ fun logFaceLockoutReset(@LockoutMode mode: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFaceLockoutReset: $int1" })
+ }
+
+ fun logFaceRunningState(faceRunningState: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = faceRunningState }, { "faceRunningState: $int1" })
+ }
+
+ fun logFingerprintAuthForWrongUser(authUserId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = authUserId },
+ { "Fingerprint authenticated for wrong user: $int1" })
+ }
+
+ fun logFingerprintDisabledForUser(userId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = userId },
+ { "Fingerprint disabled by DPM for userId: $int1" })
+ }
+
+ fun logFingerprintLockoutReset(@LockoutMode mode: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFingerprintLockoutReset: $int1" })
+ }
+
+ fun logFingerprintRunningState(fingerprintRunningState: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = fingerprintRunningState },
+ { "fingerprintRunningState: $int1" })
+ }
+
+ fun logInvalidSubId(subId: Int) {
+ logBuffer.log(TAG, INFO,
+ { int1 = subId },
+ { "Previously active sub id $int1 is now invalid, will remove" })
+ }
+
+ fun logKeyguardBouncerChanged(bouncerIsOrWillBeShowing: Boolean, bouncerFullyShown: Boolean) {
+ logBuffer.log(TAG, DEBUG, {
+ bool1 = bouncerIsOrWillBeShowing
+ bool2 = bouncerFullyShown
+ }, {
+ "handleKeyguardBouncerChanged " +
+ "bouncerIsOrWillBeShowing=$bool1 bouncerFullyShowing=$bool2"
+ })
+ }
+
+ fun logKeyguardListenerModel(model: KeyguardListenModel) {
+ logBuffer.log(TAG, VERBOSE, { str1 = "$model" }, { str1!! })
+ }
+
+ fun logKeyguardVisibilityChanged(showing: Boolean) {
+ logBuffer.log(TAG, DEBUG, { bool1 = showing }, { "onKeyguardVisibilityChanged($bool1)" })
+ }
+
+ fun logMissingSupervisorAppError(userId: Int) {
+ logBuffer.log(TAG, ERROR,
+ { int1 = userId },
+ { "No Profile Owner or Device Owner supervision app found for User $int1" })
+ }
+
+ fun logPhoneStateChanged(newState: String) {
+ logBuffer.log(TAG, DEBUG,
+ { str1 = newState },
+ { "handlePhoneStateChanged($str1)" })
+ }
+
+ fun logRegisterCallback(callback: KeyguardUpdateMonitorCallback?) {
+ logBuffer.log(TAG, VERBOSE,
+ { str1 = "$callback" },
+ { "*** register callback for $str1" })
+ }
+
+ fun logRetryingAfterFaceHwUnavailable(retryCount: Int) {
+ logBuffer.log(TAG, WARNING,
+ { int1 = retryCount },
+ { "Retrying face after HW unavailable, attempt $int1" })
+ }
+
+ fun logRetryAfterFpHwUnavailable(retryCount: Int) {
+ logBuffer.log(TAG, WARNING,
+ { int1 = retryCount },
+ { "Retrying fingerprint attempt: $int1" })
+ }
+
+ fun logSendKeyguardBouncerChanged(
+ bouncerIsOrWillBeShowing: Boolean,
+ bouncerFullyShown: Boolean,
+ ) {
+ logBuffer.log(TAG, DEBUG, {
+ bool1 = bouncerIsOrWillBeShowing
+ bool2 = bouncerFullyShown
+ }, {
+ "sendKeyguardBouncerChanged bouncerIsOrWillBeShowing=$bool1 " +
+ "bouncerFullyShown=$bool2"
+ })
+ }
+
+ fun logServiceStateChange(subId: Int, serviceState: ServiceState?) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = subId
+ str1 = "$serviceState"
+ }, { "handleServiceStateChange(subId=$int1, serviceState=$str1)" })
+ }
+
+ fun logServiceStateIntent(action: String, serviceState: ServiceState?, subId: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ str1 = action
+ str2 = "$serviceState"
+ int1 = subId
+ }, { "action $str1 serviceState=$str2 subId=$int1" })
+ }
+
+ fun logSimState(subId: Int, slotId: Int, state: Int) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = subId
+ int2 = slotId
+ long1 = state.toLong()
+ }, { "handleSimStateChange(subId=$int1, slotId=$int2, state=$long1)" })
+ }
+
+ fun logSimStateFromIntent(action: String, extraSimState: String, slotId: Int, subId: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ str1 = action
+ str2 = extraSimState
+ int1 = slotId
+ int2 = subId
+ }, { "action $str1 state: $str2 slotId: $int1 subid: $int2" })
+ }
+
+ fun logSimUnlocked(subId: Int) {
+ logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" })
+ }
+
+ fun logStartedListeningForFace(faceRunningState: Int) {
+ logBuffer.log(TAG, VERBOSE,
+ { int1 = faceRunningState },
+ { "startListeningForFace(): $int1" })
+ }
+
+ fun logSubInfo(subInfo: SubscriptionInfo?) {
+ logBuffer.log(TAG, VERBOSE,
+ { str1 = "$subInfo" },
+ { "SubInfo:$str1" })
+ }
+
+ fun logTimeFormatChanged(newTimeFormat: String) {
+ logBuffer.log(TAG, DEBUG,
+ { str1 = newTimeFormat },
+ { "handleTimeFormatUpdate timeFormat=$str1" })
+ }
+
+ fun logUdfpsPointerDown(sensorId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = sensorId },
+ { "onUdfpsPointerDown, sensorId: $int1" })
+ }
+ fun logUdfpsPointerUp(sensorId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = sensorId },
+ { "onUdfpsPointerUp, sensorId: $int1" })
+ }
+
+ fun logUnexpectedFaceCancellationSignalState(faceRunningState: Int, unlockPossible: Boolean) {
+ logBuffer.log(TAG, ERROR, {
+ int1 = faceRunningState
+ bool1 = unlockPossible
+ }, {
+ "Cancellation signal is not null, high chance of bug in " +
+ "face auth lifecycle management. " +
+ "Face state: $int1, unlockPossible: $bool1"
+ })
+ }
+
+ fun logUnexpectedFpCancellationSignalState(
+ fingerprintRunningState: Int,
+ unlockPossible: Boolean
+ ) {
+ logBuffer.log(TAG, ERROR, {
+ int1 = fingerprintRunningState
+ bool1 = unlockPossible
+ }, {
+ "Cancellation signal is not null, high chance of bug in " +
+ "fp auth lifecycle management. FP state: $int1, unlockPossible: $bool1"
+ })
+ }
+
+ fun logUnregisterCallback(callback: KeyguardUpdateMonitorCallback?) {
+ logBuffer.log(TAG, VERBOSE,
+ { str1 = "$callback" },
+ { "*** unregister callback for $str1" })
+ }
+
+ fun logUserRequestedUnlock(
+ requestOrigin: ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN,
+ reason: String,
+ dismissKeyguard: Boolean
+ ) {
+ logBuffer.log("ActiveUnlock", DEBUG, {
+ str1 = requestOrigin.name
+ str2 = reason
+ bool1 = dismissKeyguard
+ }, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 720d70890eb3..5c84ff38d761 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -81,7 +81,6 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
@@ -323,7 +322,6 @@ public class Dependency {
@Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
@Inject Lazy<NotificationListener> mNotificationListener;
@Inject Lazy<NotificationLogger> mNotificationLogger;
- @Inject Lazy<NotificationViewHierarchyManager> mNotificationViewHierarchyManager;
@Inject Lazy<NotificationFilter> mNotificationFilter;
@Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
@Inject Lazy<SmartReplyController> mSmartReplyController;
@@ -540,8 +538,6 @@ public class Dependency {
mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
mProviders.put(NotificationListener.class, mNotificationListener::get);
mProviders.put(NotificationLogger.class, mNotificationLogger::get);
- mProviders.put(NotificationViewHierarchyManager.class,
- mNotificationViewHierarchyManager::get);
mProviders.put(NotificationFilter.class, mNotificationFilter::get);
mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
mProviders.put(SmartReplyController.class, mSmartReplyController::get);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index e9ca0fdbb929..5f586c927ef7 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -79,11 +79,6 @@ public abstract class SystemUIInitializer {
// Stand up WMComponent
setupWmComponent(mContext);
- if (initializeComponents) {
- // Only initialize when not starting from tests since this currently initializes some
- // components that shouldn't be run in the test environment
- mWMComponent.init();
- }
// And finally, retrieve whatever SysUI needs from WMShell and build SysUI.
SysUIComponent.Builder builder = mRootComponent.getSysUIComponent();
@@ -102,6 +97,10 @@ public abstract class SystemUIInitializer {
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
.setBackAnimation(mWMComponent.getBackAnimation());
+
+ // Only initialize when not starting from tests since this currently initializes some
+ // components that shouldn't be run in the test environment
+ mWMComponent.init();
} else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
// is separating this logic into newly creating SystemUITestsFactory.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 448b99b6e5d0..a1288b531955 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -500,7 +500,7 @@ public class SystemActions extends CoreStartable {
private void handleTakeScreenshot() {
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true,
+ screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
index bbffb73b7503..d03106b4e6bc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -43,6 +43,8 @@ import android.view.View.AccessibilityDelegate
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.RawRes
import com.airbnb.lottie.LottieAnimationView
@@ -130,7 +132,7 @@ class SidefpsController @Inject constructor(
fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
gravity = Gravity.TOP or Gravity.LEFT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
- privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+ privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION
}
init {
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
new file mode 100644
index 000000000000..6f3beac2ac85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.common.coroutine
+
+import android.util.Log
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.onFailure
+
+object ChannelExt {
+
+ /**
+ * Convenience wrapper around [SendChannel.trySend] that also logs on failure. This is the
+ * equivalent of calling:
+ *
+ * ```
+ * sendChannel.trySend(element).onFailure {
+ * Log.e(
+ * loggingTag,
+ * "Failed to send $elementDescription" +
+ * " - downstream canceled or failed.",
+ * it,
+ * )
+ *}
+ * ```
+ */
+ fun <T> SendChannel<T>.trySendWithFailureLogging(
+ element: T,
+ loggingTag: String,
+ elementDescription: String = "updated state",
+ ) {
+ trySend(element).onFailure {
+ Log.e(
+ loggingTag,
+ "Failed to send $elementDescription - downstream canceled or failed.",
+ it,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
new file mode 100644
index 000000000000..d4a1f74234ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.common.coroutine
+
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+object ConflatedCallbackFlow {
+
+ /**
+ * A [callbackFlow] that uses a buffer [Channel] that is "conflated" meaning that, if
+ * backpressure occurs (if the producer that emits new values into the flow is faster than the
+ * consumer(s) of the values in the flow), the values are buffered and, if the buffer fills up,
+ * we drop the oldest values automatically instead of suspending the producer.
+ */
+ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+ @OptIn(ExperimentalTypeInference::class, ExperimentalCoroutinesApi::class)
+ fun <T> conflatedCallbackFlow(
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
+ ): Flow<T> = callbackFlow(block).buffer(capacity = Channel.CONFLATED)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
new file mode 100644
index 000000000000..7c9df102ef1d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.common.data.model
+
+/** Models a two-dimensional position */
+data class Position(
+ val x: Int,
+ val y: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
new file mode 100644
index 000000000000..f697c0ab3b22
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.common.domain.model
+
+import com.android.systemui.common.data.model.Position as DataLayerPosition
+
+/** Models a two-dimensional position */
+data class Position(
+ val x: Int,
+ val y: Int,
+) {
+ companion object {
+ fun DataLayerPosition.toDomainLayer(): Position {
+ return Position(
+ x = x,
+ y = y,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
new file mode 100644
index 000000000000..d6a059da3afa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.containeddrawable
+
+import android.graphics.drawable.Drawable
+import androidx.annotation.DrawableRes
+
+/** Convenience container for [Drawable] or a way to load it later. */
+sealed class ContainedDrawable {
+ data class WithDrawable(val drawable: Drawable) : ContainedDrawable()
+ data class WithResource(@DrawableRes val resourceId: Int) : ContainedDrawable()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 2fd373105745..9e4a364562e5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -21,20 +21,22 @@ import android.content.Context
import android.database.ContentObserver
import android.os.UserHandle
import android.provider.Settings
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
+import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.SecureSettings
-import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
-import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
/**
* Pseudo-component to inject into classes outside `com.android.systemui.controls`.
@@ -59,7 +61,8 @@ class ControlsComponent @Inject constructor(
private val contentResolver: ContentResolver
get() = context.contentResolver
- private var canShowWhileLockedSetting = false
+ private val _canShowWhileLockedSetting = MutableStateFlow(false)
+ val canShowWhileLockedSetting = _canShowWhileLockedSetting.asStateFlow()
private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
optionalControlsTileResourceConfiguration.orElse(
@@ -117,7 +120,7 @@ class ControlsComponent @Inject constructor(
== STRONG_AUTH_REQUIRED_AFTER_BOOT) {
return Visibility.AVAILABLE_AFTER_UNLOCK
}
- if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) {
+ if (!canShowWhileLockedSetting.value && !keyguardStateController.isUnlocked()) {
return Visibility.AVAILABLE_AFTER_UNLOCK
}
@@ -125,7 +128,7 @@ class ControlsComponent @Inject constructor(
}
private fun updateShowWhileLocked() {
- canShowWhileLockedSetting = secureSettings.getIntForUser(
+ _canShowWhileLockedSetting.value = secureSettings.getIntForUser(
Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 718befadd2b5..029cabb0bc0b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -31,7 +31,6 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.people.PeopleProvider;
-import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.FoldStateLogger;
import com.android.systemui.unfold.FoldStateLoggingProvider;
@@ -131,7 +130,6 @@ public interface SysUIComponent {
getMediaTttCommandLineHelper();
getMediaMuteAwaitConnectionCli();
getNearbyMediaDevicesManager();
- getConnectivityInfoProcessor();
getUnfoldLatencyTracker().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
@@ -214,9 +212,6 @@ public interface SysUIComponent {
/** */
Optional<NearbyMediaDevicesManager> getNearbyMediaDevicesManager();
- /** */
- Optional<ConnectivityInfoProcessor> getConnectivityInfoProcessor();
-
/**
* Returns {@link CoreStartable}s that should be started with the application.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
new file mode 100644
index 000000000000..d853e04fed90
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.doze.util
+
+import javax.inject.Inject
+
+/** Injectable wrapper around `BurnInHelper` functions */
+class BurnInHelperWrapper @Inject constructor() {
+
+ fun burnInOffset(amplitude: Int, xAxis: Boolean): Int {
+ return getBurnInOffset(amplitude, xAxis)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 51bd31100ad0..54571448c981 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -162,7 +162,8 @@ public interface Complication {
COMPLICATION_TYPE_DATE,
COMPLICATION_TYPE_WEATHER,
COMPLICATION_TYPE_AIR_QUALITY,
- COMPLICATION_TYPE_CAST_INFO
+ COMPLICATION_TYPE_CAST_INFO,
+ COMPLICATION_TYPE_HOME_CONTROLS
})
@Retention(RetentionPolicy.SOURCE)
@interface ComplicationType {}
@@ -173,6 +174,7 @@ public interface Complication {
int COMPLICATION_TYPE_WEATHER = 1 << 2;
int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3;
int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
+ int COMPLICATION_TYPE_HOME_CONTROLS = 1 << 5;
/**
* The {@link Host} interface specifies a way a {@link Complication} to communicate with its
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
index a4a0075e742a..dcab90fe7ab9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
@@ -19,6 +19,7 @@ package com.android.systemui.dreams.complication;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
@@ -48,6 +49,8 @@ public class ComplicationUtils {
return COMPLICATION_TYPE_AIR_QUALITY;
case DreamBackend.COMPLICATION_TYPE_CAST_INFO:
return COMPLICATION_TYPE_CAST_INFO;
+ case DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS:
+ return COMPLICATION_TYPE_HOME_CONTROLS;
default:
return COMPLICATION_TYPE_NONE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
deleted file mode 100644
index 1ca06b25aa9f..000000000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication;
-
-import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS;
-import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_VIEW;
-
-import android.content.Context;
-import android.view.View;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.dreams.DreamOverlayStateController;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-
-/**
- * Clock Date Complication that produce Clock Date view holder.
- */
-public class DreamClockDateComplication implements Complication {
- private final Provider<DreamClockDateViewHolder> mDreamClockDateViewHolderProvider;
-
- /**
- * Default constructor for {@link DreamClockDateComplication}.
- */
- @Inject
- public DreamClockDateComplication(
- Provider<DreamClockDateViewHolder> dreamClockDateViewHolderProvider) {
- mDreamClockDateViewHolderProvider = dreamClockDateViewHolderProvider;
- }
-
- @Override
- public int getRequiredTypeAvailability() {
- return COMPLICATION_TYPE_DATE;
- }
-
- /**
- * Create {@link DreamClockDateViewHolder}.
- */
- @Override
- public ViewHolder createView(ComplicationViewModel model) {
- return mDreamClockDateViewHolderProvider.get();
- }
-
- /**
- * {@link CoreStartable} responsible for registering {@link DreamClockDateComplication} with
- * SystemUI.
- */
- public static class Registrant extends CoreStartable {
- private final DreamOverlayStateController mDreamOverlayStateController;
- private final DreamClockDateComplication mComplication;
-
- /**
- * Default constructor to register {@link DreamClockDateComplication}.
- */
- @Inject
- public Registrant(Context context,
- DreamOverlayStateController dreamOverlayStateController,
- DreamClockDateComplication dreamClockDateComplication) {
- super(context);
- mDreamOverlayStateController = dreamOverlayStateController;
- mComplication = dreamClockDateComplication;
- }
-
- @Override
- public void start() {
- mDreamOverlayStateController.addComplication(mComplication);
- }
- }
-
- /**
- * {@link ViewHolder} to contain value/logic associated with {@link DreamClockDateComplication}.
- */
- public static class DreamClockDateViewHolder implements ViewHolder {
- private final View mView;
- private final ComplicationLayoutParams mLayoutParams;
-
- @Inject
- DreamClockDateViewHolder(@Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW) View view,
- @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
- ComplicationLayoutParams layoutParams) {
- mView = view;
- mLayoutParams = layoutParams;
- }
-
- @Override
- public View getView() {
- return mView;
- }
-
- @Override
- public ComplicationLayoutParams getLayoutParams() {
- return mLayoutParams;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
index 7f67ecd19175..675a2f46d310 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
@@ -16,8 +16,8 @@
package com.android.systemui.dreams.complication;
-import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
import android.content.Context;
import android.view.View;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 1a9d9b50104b..1c72e49eb06a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -19,8 +19,8 @@ package com.android.systemui.dreams.complication;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE;
-import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS;
import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW;
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS;
import android.content.Context;
import android.content.Intent;
@@ -62,7 +62,7 @@ public class DreamHomeControlsComplication implements Complication {
@Override
public int getRequiredTypeAvailability() {
- return COMPLICATION_TYPE_NONE;
+ return COMPLICATION_TYPE_HOME_CONTROLS;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
index be94e5031917..ac6edba6b3fa 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dreams;
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_SMARTSPACE_LAYOUT_PARAMS;
import android.content.Context;
import android.os.Parcelable;
@@ -23,21 +25,33 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.android.systemui.CoreStartable;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
-import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import java.util.List;
import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
/**
* {@link SmartSpaceComplication} embodies the SmartSpace view found on the lockscreen as a
* {@link Complication}
*/
public class SmartSpaceComplication implements Complication {
+ private final Provider<SmartSpaceComplicationViewHolder> mViewHolderProvider;
+
+ @Inject
+ public SmartSpaceComplication(Provider<SmartSpaceComplicationViewHolder> viewHolderProvider) {
+ mViewHolderProvider = viewHolderProvider;
+ }
+
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mViewHolderProvider.get();
+ }
+
/**
* {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with
* SystemUI.
@@ -89,17 +103,20 @@ public class SmartSpaceComplication implements Complication {
}
}
- private static class SmartSpaceComplicationViewHolder implements ViewHolder {
+ static class SmartSpaceComplicationViewHolder implements ViewHolder {
private View mView = null;
- private static final int SMARTSPACE_COMPLICATION_WEIGHT = 10;
private final DreamSmartspaceController mSmartSpaceController;
private final Context mContext;
+ private final ComplicationLayoutParams mLayoutParams;
+ @Inject
protected SmartSpaceComplicationViewHolder(
Context context,
- DreamSmartspaceController smartSpaceController) {
+ DreamSmartspaceController smartSpaceController,
+ @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams) {
mSmartSpaceController = smartSpaceController;
mContext = context;
+ mLayoutParams = layoutParams;
}
@Override
@@ -119,25 +136,7 @@ public class SmartSpaceComplication implements Complication {
@Override
public ComplicationLayoutParams getLayoutParams() {
- return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
- SMARTSPACE_COMPLICATION_WEIGHT, true);
+ return mLayoutParams;
}
}
-
- private final DreamSmartspaceController mSmartSpaceController;
- private final Context mContext;
-
- @Inject
- public SmartSpaceComplication(Context context,
- DreamSmartspaceController smartSpaceController) {
- mContext = context;
- mSmartSpaceController = smartSpaceController;
- }
-
- @Override
- public ViewHolder createView(ComplicationViewModel model) {
- return new SmartSpaceComplicationViewHolder(mContext, mSmartSpaceController);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java
deleted file mode 100644
index 3ab26ceeb076..000000000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication.dagger;
-
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.internal.util.Preconditions;
-import com.android.systemui.R;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
-import com.android.systemui.dreams.complication.DreamClockDateComplication;
-
-import javax.inject.Named;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Module for providing {@link DreamClockDateComplication}.
- */
-@Module
-public interface DreamClockDateComplicationModule {
- String DREAM_CLOCK_DATE_COMPLICATION_VIEW = "clock_date_complication_view";
- String DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS =
- "clock_date_complication_layout_params";
- // Order weight of insert into parent container
- //TODO(b/217199227): move to a single location.
- int INSERT_ORDER_WEIGHT = 3;
-
- /**
- * Provides the complication view.
- */
- @Provides
- @Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW)
- static View provideComplicationView(LayoutInflater layoutInflater) {
- return Preconditions.checkNotNull(
- layoutInflater.inflate(R.layout.dream_overlay_complication_clock_date,
- null, false),
- "R.layout.dream_overlay_complication_clock_date did not properly inflated");
- }
-
- /**
- * Provides the layout parameters for the complication view.
- */
- @Provides
- @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideLayoutParams() {
- return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_END,
- INSERT_ORDER_WEIGHT, /* snapToGuide= */ true);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
index 3ad7d3ded749..5250d44761b9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
@@ -19,12 +19,10 @@ package com.android.systemui.dreams.complication.dagger;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.TextClock;
import com.android.internal.util.Preconditions;
import com.android.systemui.R;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
import com.android.systemui.dreams.complication.DreamClockTimeComplication;
import javax.inject.Named;
@@ -38,11 +36,6 @@ import dagger.Provides;
@Module
public interface DreamClockTimeComplicationModule {
String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view";
- String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS =
- "clock_time_complication_layout_params";
- // Order weight of insert into parent container
- //TODO(b/217199227): move to a single location.
- int INSERT_ORDER_WEIGHT = 0;
String TAG_WEIGHT = "'wght' ";
int WEIGHT = 200;
@@ -59,18 +52,4 @@ public interface DreamClockTimeComplicationModule {
view.setFontVariationSettings(TAG_WEIGHT + WEIGHT);
return view;
}
-
- /**
- * Provides the layout parameters for the complication view.
- */
- @Provides
- @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideLayoutParams() {
- return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_UP,
- INSERT_ORDER_WEIGHT);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
index 033ce392220a..cf05d2d9cda0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -18,13 +18,10 @@ package com.android.systemui.dreams.complication.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import android.content.res.Resources;
import android.view.LayoutInflater;
import android.widget.ImageView;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
import com.android.systemui.dreams.complication.DreamHomeControlsComplication;
import java.lang.annotation.Documented;
@@ -70,12 +67,6 @@ public interface DreamHomeControlsComplicationComponent {
@Module
interface DreamHomeControlsModule {
String DREAM_HOME_CONTROLS_CHIP_VIEW = "dream_home_controls_chip_view";
- String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params";
-
- // TODO(b/217199227): move to a single location.
- // Weight of order in the parent container. The home controls complication should have low
- // weight and be placed at the end.
- int INSERT_ORDER_WEIGHT = 0;
/**
* Provides the dream home controls chip view.
@@ -87,22 +78,5 @@ public interface DreamHomeControlsComplicationComponent {
return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
null, false);
}
-
- /**
- * Provides the layout parameters for the dream home controls complication.
- */
- @Provides
- @DreamHomeControlsComplicationScope
- @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideLayoutParams(@Main Resources res) {
- return new ComplicationLayoutParams(
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_END,
- INSERT_ORDER_WEIGHT);
- }
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 4a515f09b5a7..eb07238ce752 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -16,20 +16,79 @@
package com.android.systemui.dreams.complication.dagger;
+import android.content.res.Resources;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
import com.android.systemui.dagger.SystemUIBinder;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+
+import javax.inject.Named;
import dagger.Module;
+import dagger.Provides;
/**
* Module for all components with corresponding dream layer complications registered in
* {@link SystemUIBinder}.
*/
@Module(includes = {
- DreamClockDateComplicationModule.class,
DreamClockTimeComplicationModule.class,
},
subcomponents = {
DreamHomeControlsComplicationComponent.class,
})
public interface RegisteredComplicationsModule {
+ String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS = "time_complication_layout_params";
+ String DREAM_SMARTSPACE_LAYOUT_PARAMS = "smartspace_layout_params";
+ String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params";
+
+ int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1;
+ int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 0;
+ int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 1;
+
+ /**
+ * Provides layout parameters for the clock time complication.
+ */
+ @Provides
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideClockTimeLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
+ }
+
+ /**
+ * Provides layout parameters for the home controls complication.
+ */
+ @Provides
+ @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideHomeControlsChipLayoutParams(@Main Resources res) {
+ return new ComplicationLayoutParams(
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
+ }
+
+ /**
+ * Provides layout parameters for the smartspace complication.
+ */
+ @Provides
+ @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideSmartspaceLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
+ true /*snapToGuide*/);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 958a2192d159..733b1b694851 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -87,11 +87,17 @@ public class Flags {
public static final ResourceBooleanFlag FACE_SCANNING_ANIM =
new ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation);
+
/**
* Whether the KeyguardBottomArea(View|Controller) should use the modern architecture or the old
* one.
*/
- public static final BooleanFlag MODERN_BOTTOM_AREA = new BooleanFlag(206, false);
+ public static final BooleanFlag MODERN_BOTTOM_AREA = new BooleanFlag(
+ 206,
+ /* default= */ false,
+ /* teamfood= */ true);
+
+ public static final BooleanFlag LOCKSCREEN_CUSTOM_CLOCKS = new BooleanFlag(207, false);
/***************************************/
// 300 - power menu
@@ -196,6 +202,10 @@ public class Flags {
new DeviceConfigBooleanFlag(1102, "record_task_content",
NAMESPACE_WINDOW_MANAGER, false, true);
+ @Keep
+ public static final SysPropBooleanFlag HIDE_NAVBAR_WINDOW =
+ new SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false);
+
// 1200 - predictive back
@Keep
public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index ab30db297fce..ca65d12e87e6 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -947,7 +947,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true,
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 165af135a792..4ff008ffb33a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,6 +42,8 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.usecase.KeyguardUseCaseModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -66,7 +68,11 @@ import dagger.Provides;
KeyguardStatusBarViewComponent.class,
KeyguardStatusViewComponent.class,
KeyguardUserSwitcherComponent.class},
- includes = {FalsingModule.class})
+ includes = {
+ FalsingModule.class,
+ KeyguardRepositoryModule.class,
+ KeyguardUseCaseModule.class,
+ })
public class KeyguardModule {
/**
* Provides our instance of KeyguardViewMediator which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 000000000000..3202ecb9a287
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.content.Intent
+import androidx.annotation.DrawableRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.kotlin.getOrNull
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Home controls quick affordance data source. */
+@SysUISingleton
+class HomeControlsKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ @Application context: Context,
+ private val component: ControlsComponent,
+) : KeyguardQuickAffordanceConfig {
+
+ private val appContext = context.applicationContext
+
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> =
+ stateInternal(component.getControlsListingController().getOrNull())
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): KeyguardQuickAffordanceConfig.OnClickedResult {
+ return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent =
+ Intent(appContext, ControlsActivity::class.java)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(
+ ControlsUiController.EXTRA_ANIMATE,
+ true,
+ ),
+ canShowWhileLocked = component.canShowWhileLockedSetting.value,
+ )
+ }
+
+ private fun stateInternal(
+ listingController: ControlsListingController?,
+ ): Flow<KeyguardQuickAffordanceConfig.State> {
+ if (listingController == null) {
+ return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ }
+
+ return conflatedCallbackFlow {
+ val callback =
+ object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+ val favorites: List<StructureInfo>? =
+ component.getControlsController().getOrNull()?.getFavorites()
+
+ trySendWithFailureLogging(
+ state(
+ isFeatureEnabled = component.isEnabled(),
+ hasFavorites = favorites?.isNotEmpty() == true,
+ hasServiceInfos = serviceInfos.isNotEmpty(),
+ iconResourceId = component.getTileImageId(),
+ ),
+ TAG,
+ )
+ }
+ }
+
+ listingController.addCallback(callback)
+
+ awaitClose { listingController.removeCallback(callback) }
+ }
+ }
+
+ private fun state(
+ isFeatureEnabled: Boolean,
+ hasFavorites: Boolean,
+ hasServiceInfos: Boolean,
+ @DrawableRes iconResourceId: Int?,
+ ): KeyguardQuickAffordanceConfig.State {
+ return if (isFeatureEnabled && hasFavorites && hasServiceInfos && iconResourceId != null) {
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ContainedDrawable.WithResource(iconResourceId),
+ contentDescriptionResourceId = component.getTileTitleId(),
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "HomeControlsKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
new file mode 100644
index 000000000000..67a776eddccb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import android.content.Intent
+import androidx.annotation.StringRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface that can act as data source for a single quick affordance model. */
+interface KeyguardQuickAffordanceConfig {
+
+ val state: Flow<State>
+
+ fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?
+ ): OnClickedResult
+
+ /**
+ * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
+ * button on the lock-screen).
+ */
+ sealed class State {
+
+ /** No affordance should show up. */
+ object Hidden : State()
+
+ /** An affordance is visible. */
+ data class Visible(
+ /** An icon for the affordance. */
+ val icon: ContainedDrawable,
+ /**
+ * Resource ID for a string to use for the accessibility content description text of the
+ * affordance.
+ */
+ @StringRes val contentDescriptionResourceId: Int,
+ ) : State()
+ }
+
+ sealed class OnClickedResult {
+ /**
+ * Returning this as a result from the [onQuickAffordanceClicked] method means that the
+ * implementation has taken care of the click, the system will do nothing.
+ */
+ object Handled : OnClickedResult()
+
+ /**
+ * Returning this as a result from the [onQuickAffordanceClicked] method means that the
+ * implementation has _not_ taken care of the click and the system should start an activity
+ * using the given [Intent].
+ */
+ data class StartActivity(
+ val intent: Intent,
+ val canShowWhileLocked: Boolean,
+ ) : OnClickedResult()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 000000000000..ea6497ebcf3a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** QR code scanner quick affordance data source. */
+@SysUISingleton
+class QrCodeScannerKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ private val controller: QRCodeScannerController,
+) : KeyguardQuickAffordanceConfig {
+
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
+ val callback =
+ object : QRCodeScannerController.Callback {
+ override fun onQRCodeScannerActivityChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ override fun onQRCodeScannerPreferenceChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ }
+
+ controller.addCallback(callback)
+ controller.registerQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ // Registering does not push an initial update.
+ trySendWithFailureLogging(state(), "initial state", TAG)
+
+ awaitClose {
+ controller.unregisterQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ controller.removeCallback(callback)
+ }
+ }
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): KeyguardQuickAffordanceConfig.OnClickedResult {
+ return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent = controller.intent,
+ canShowWhileLocked = true,
+ )
+ }
+
+ private fun state(): KeyguardQuickAffordanceConfig.State {
+ return if (controller.isEnabledForLockScreenButton) {
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ContainedDrawable.WithResource(R.drawable.ic_qr_code_scanner),
+ contentDescriptionResourceId = R.string.accessibility_qr_code_scanner_button,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 000000000000..cc5a9972af93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import android.graphics.drawable.Drawable
+import android.service.quickaccesswallet.GetWalletCardsError
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.wallet.controller.QuickAccessWalletController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Quick access wallet quick affordance data source. */
+@SysUISingleton
+class QuickAccessWalletKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ private val walletController: QuickAccessWalletController,
+ private val activityStarter: ActivityStarter,
+) : KeyguardQuickAffordanceConfig {
+
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
+ val callback =
+ object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ trySendWithFailureLogging(
+ state(
+ isFeatureEnabled = walletController.isWalletEnabled,
+ hasCard = response?.walletCards?.isNotEmpty() == true,
+ tileIcon = walletController.walletClient.tileIcon,
+ ),
+ TAG,
+ )
+ }
+
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
+ trySendWithFailureLogging(
+ KeyguardQuickAffordanceConfig.State.Hidden,
+ TAG,
+ )
+ }
+ }
+
+ walletController.setupWalletChangeObservers(
+ callback,
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ walletController.updateWalletPreference()
+ walletController.queryWalletCards(callback)
+
+ awaitClose {
+ walletController.unregisterWalletChangeObservers(
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ }
+ }
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): KeyguardQuickAffordanceConfig.OnClickedResult {
+ walletController.startQuickAccessUiIntent(
+ activityStarter,
+ animationController,
+ /* hasCard= */ true,
+ )
+ return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+ }
+
+ private fun state(
+ isFeatureEnabled: Boolean,
+ hasCard: Boolean,
+ tileIcon: Drawable?,
+ ): KeyguardQuickAffordanceConfig.State {
+ return if (isFeatureEnabled && hasCard && tileIcon != null) {
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ContainedDrawable.WithDrawable(tileIcon),
+ contentDescriptionResourceId = R.string.accessibility_wallet_button,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
new file mode 100644
index 000000000000..7164215eb2ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.keyguard.data.config
+
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlin.reflect.KClass
+
+/** Injectable provider of the positioning of the known quick affordance configs. */
+interface KeyguardQuickAffordanceConfigs {
+ fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig>
+ fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig
+}
+
+class KeyguardQuickAffordanceConfigsImpl
+@Inject
+constructor(
+ homeControls: HomeControlsKeyguardQuickAffordanceConfig,
+ quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+ qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+) : KeyguardQuickAffordanceConfigs {
+ private val configsByPosition =
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControls,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ quickAccessWallet,
+ qrCodeScanner,
+ ),
+ )
+ private val configByClass =
+ configsByPosition.values.flatten().associateBy { config -> config::class }
+
+ override fun getAll(
+ position: KeyguardQuickAffordancePosition,
+ ): List<KeyguardQuickAffordanceConfig> {
+ return configsByPosition.getValue(position)
+ }
+
+ override fun get(
+ configClass: KClass<out KeyguardQuickAffordanceConfig>
+ ): KeyguardQuickAffordanceConfig {
+ return configByClass.getValue(configClass)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
new file mode 100644
index 000000000000..43c4fa06367b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.State
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Defines interface for classes that encapsulate quick affordance state for the keyguard. */
+interface KeyguardQuickAffordanceRepository {
+ fun affordance(position: KeyguardQuickAffordancePosition): Flow<KeyguardQuickAffordanceModel>
+}
+
+/** Real implementation of [KeyguardQuickAffordanceRepository] */
+@SysUISingleton
+class KeyguardQuickAffordanceRepositoryImpl
+@Inject
+constructor(
+ private val configs: KeyguardQuickAffordanceConfigs,
+) : KeyguardQuickAffordanceRepository {
+
+ /** Returns an observable for the quick affordance model in the given position. */
+ override fun affordance(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ val configs = configs.getAll(position)
+ return combine(configs.map { config -> config.state }) { states ->
+ val index = states.indexOfFirst { state -> state is State.Visible }
+ val visibleState =
+ if (index != -1) {
+ states[index] as State.Visible
+ } else {
+ null
+ }
+ if (visibleState != null) {
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configs[index]::class,
+ icon = visibleState.icon,
+ contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
+ )
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
new file mode 100644
index 000000000000..62cf1a624d9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -0,0 +1,181 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.data.model.Position
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Defines interface for classes that encapsulate application state for the keyguard. */
+interface KeyguardRepository {
+ /**
+ * Observable for whether the bottom area UI should animate the transition out of doze state.
+ *
+ * To learn more about doze state, please see [isDozing].
+ */
+ val animateBottomAreaDozingTransitions: StateFlow<Boolean>
+
+ /**
+ * Observable for the current amount of alpha that should be used for rendering the bottom area.
+ * UI.
+ */
+ val bottomAreaAlpha: StateFlow<Float>
+
+ /**
+ * Observable of the relative offset of the lock-screen clock from its natural position on the
+ * screen.
+ */
+ val clockPosition: StateFlow<Position>
+
+ /**
+ * Observable for whether the keyguard is showing.
+ *
+ * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
+ * the z-order (which is not really above the system UI window, but rather - the lock-screen
+ * becomes invisible to reveal the "occluding activity").
+ */
+ val isKeyguardShowing: Flow<Boolean>
+
+ /**
+ * Observable for whether we are in doze state.
+ *
+ * Doze state is the same as "Always on Display" or "AOD". It is the state that the device can
+ * enter to conserve battery when the device is locked and inactive.
+ *
+ * Note that it is possible for the system to be transitioning into doze while this flow still
+ * returns `false`. In order to account for that, observers should also use the [dozeAmount]
+ * flow to check if it's greater than `0`
+ */
+ val isDozing: Flow<Boolean>
+
+ /**
+ * Observable for the amount of doze we are currently in.
+ *
+ * While in doze state, this amount can change - driving a cycle of animations designed to avoid
+ * pixel burn-in, etc.
+ *
+ * Also note that the value here may be greater than `0` while [isDozing] is still `false`, this
+ * happens during an animation/transition into doze mode. An observer would be wise to account
+ * for both flows if needed.
+ */
+ val dozeAmount: Flow<Float>
+
+ /** Sets whether the bottom area UI should animate the transition out of doze state. */
+ fun setAnimateDozingTransitions(animate: Boolean)
+
+ /** Sets the current amount of alpha that should be used for rendering the bottom area. */
+ fun setBottomAreaAlpha(alpha: Float)
+
+ /**
+ * Sets the relative offset of the lock-screen clock from its natural position on the screen.
+ */
+ fun setClockPosition(x: Int, y: Int)
+}
+
+/** Encapsulates application state for the keyguard. */
+@SysUISingleton
+class KeyguardRepositoryImpl
+@Inject
+constructor(
+ statusBarStateController: StatusBarStateController,
+ keyguardStateController: KeyguardStateController,
+) : KeyguardRepository {
+ private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
+ override val animateBottomAreaDozingTransitions =
+ _animateBottomAreaDozingTransitions.asStateFlow()
+
+ private val _bottomAreaAlpha = MutableStateFlow(1f)
+ override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow()
+
+ private val _clockPosition = MutableStateFlow(Position(0, 0))
+ override val clockPosition = _clockPosition.asStateFlow()
+
+ override val isKeyguardShowing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onKeyguardShowingChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isShowing,
+ TAG,
+ "updated isKeyguardShowing"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isShowing,
+ TAG,
+ "initial isKeyguardShowing"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+
+ override val isDozing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onDozingChanged(isDozing: Boolean) {
+ trySendWithFailureLogging(isDozing, TAG, "updated isDozing")
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ trySendWithFailureLogging(statusBarStateController.isDozing, TAG, "initial isDozing")
+
+ awaitClose { statusBarStateController.removeCallback(callback) }
+ }
+ override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ trySendWithFailureLogging(eased, TAG, "updated dozeAmount")
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ trySendWithFailureLogging(statusBarStateController.dozeAmount, TAG, "initial dozeAmount")
+
+ awaitClose { statusBarStateController.removeCallback(callback) }
+ }
+
+ override fun setAnimateDozingTransitions(animate: Boolean) {
+ _animateBottomAreaDozingTransitions.value = animate
+ }
+
+ override fun setBottomAreaAlpha(alpha: Float) {
+ _bottomAreaAlpha.value = alpha
+ }
+
+ override fun setClockPosition(x: Int, y: Int) {
+ _clockPosition.value = Position(x, y)
+ }
+
+ companion object {
+ private const val TAG = "KeyguardRepositoryImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
new file mode 100644
index 000000000000..1a5670c7e807
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigsImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardRepositoryModule {
+ @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
+
+ @Binds
+ fun keyguardQuickAffordanceRepository(
+ impl: KeyguardQuickAffordanceRepositoryImpl
+ ): KeyguardQuickAffordanceRepository
+
+ @Binds
+ fun keyguardQuickAffordanceConfigs(
+ impl: KeyguardQuickAffordanceConfigsImpl
+ ): KeyguardQuickAffordanceConfigs
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
new file mode 100644
index 000000000000..c44c2c9901a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardUseCaseModule {
+
+ @Binds
+ fun launchQuickAffordance(
+ impl: LaunchKeyguardQuickAffordanceUseCaseImpl
+ ): LaunchKeyguardQuickAffordanceUseCase
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 000000000000..3d60399cf522
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import android.content.Intent
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+
+/** Defines interface for classes that can launch a quick affordance. */
+interface LaunchKeyguardQuickAffordanceUseCase {
+ operator fun invoke(
+ intent: Intent,
+ canShowWhileLocked: Boolean,
+ animationController: ActivityLaunchAnimator.Controller?,
+ )
+}
+
+/** Real implementation of [LaunchKeyguardQuickAffordanceUseCase] */
+class LaunchKeyguardQuickAffordanceUseCaseImpl
+@Inject
+constructor(
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardStateController: KeyguardStateController,
+ private val userTracker: UserTracker,
+ private val activityStarter: ActivityStarter,
+) : LaunchKeyguardQuickAffordanceUseCase {
+ override operator fun invoke(
+ intent: Intent,
+ canShowWhileLocked: Boolean,
+ animationController: ActivityLaunchAnimator.Controller?,
+ ) {
+ @StrongAuthFlags
+ val strongAuthFlags =
+ lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
+ val needsToUnlockFirst =
+ when {
+ strongAuthFlags ==
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
+ !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
+ else -> false
+ }
+ if (needsToUnlockFirst) {
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0 /* delay */,
+ animationController
+ )
+ } else {
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ animationController,
+ true /* showOverLockscreenWhenLocked */,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
new file mode 100644
index 000000000000..ca37727072f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing whether doze state transitions should animate the bottom area */
+class ObserveAnimateBottomAreaTransitionsUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Boolean> {
+ return repository.animateBottomAreaDozingTransitions
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
new file mode 100644
index 000000000000..151b704a017b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing the alpha of the bottom area */
+class ObserveBottomAreaAlphaUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Float> {
+ return repository.bottomAreaAlpha
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
new file mode 100644
index 000000000000..02c573726db0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.common.domain.model.Position
+import com.android.systemui.common.domain.model.Position.Companion.toDomainLayer
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Use-case for observing the position of the clock. */
+class ObserveClockPositionUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Position> {
+ return repository.clockPosition.map { it.toDomainLayer() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
new file mode 100644
index 000000000000..56d61822a121
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing the amount of doze the system is in. */
+class ObserveDozeAmountUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Float> {
+ return repository.dozeAmount
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
new file mode 100644
index 000000000000..1d241d90ba5f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing whether we are dozing. */
+class ObserveIsDozingUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Boolean> {
+ return repository.isDozing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
new file mode 100644
index 000000000000..11af123c1650
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Use-case for observing whether the keyguard is currently being shown.
+ *
+ * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in the
+ * z-order (which is not really above the system UI window, but rather - the lock-screen becomes
+ * invisible to reveal the "occluding activity").
+ */
+class ObserveIsKeyguardShowingUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Boolean> {
+ return repository.isKeyguardShowing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 000000000000..eef8ec3e68f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Use-case for observing the model of a quick affordance in the keyguard. */
+class ObserveKeyguardQuickAffordanceUseCase
+@Inject
+constructor(
+ private val repository: KeyguardQuickAffordanceRepository,
+ private val isDozingUseCase: ObserveIsDozingUseCase,
+ private val isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase,
+) {
+ operator fun invoke(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ return combine(
+ repository.affordance(position),
+ isDozingUseCase(),
+ isKeyguardShowingUseCase(),
+ ) { affordance, isDozing, isKeyguardShowing ->
+ if (!isDozing && isKeyguardShowing) {
+ affordance
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
new file mode 100644
index 000000000000..f8db90f02540
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import javax.inject.Inject
+import kotlin.reflect.KClass
+
+/** Use-case for handling a click on a keyguard quick affordance (e.g. bottom button). */
+class OnKeyguardQuickAffordanceClickedUseCase
+@Inject
+constructor(
+ private val configs: KeyguardQuickAffordanceConfigs,
+ private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase,
+) {
+ operator fun invoke(
+ configKey: KClass<*>,
+ animationController: ActivityLaunchAnimator.Controller?,
+ ) {
+ @Suppress("UNCHECKED_CAST")
+ val config = configs.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
+ when (val result = config.onQuickAffordanceClicked(animationController)) {
+ is OnClickedResult.StartActivity ->
+ launchAffordanceUseCase(
+ intent = result.intent,
+ canShowWhileLocked = result.canShowWhileLocked,
+ animationController = animationController
+ )
+ is OnClickedResult.Handled -> Unit
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
new file mode 100644
index 000000000000..8f746e5765af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/** Use-case for setting the updated clock position. */
+class SetClockPositionUseCase
+@Inject
+constructor(
+ private val keyguardRepository: KeyguardRepository,
+) {
+ operator fun invoke(x: Int, y: Int) {
+ keyguardRepository.setClockPosition(x, y)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
new file mode 100644
index 000000000000..90be1ecded50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/** Use-case for setting the alpha that the keyguard bottom area should use */
+class SetKeyguardBottomAreaAlphaUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(alpha: Float) {
+ repository.setBottomAreaAlpha(alpha)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
new file mode 100644
index 000000000000..007780a6860a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/**
+ * Use-case for setting whether the keyguard bottom area should animate the next doze transitions
+ */
+class SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(animate: Boolean) {
+ repository.setAnimateDozingTransitions(animate)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
new file mode 100644
index 000000000000..09785dfa3c03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.keyguard.shared.model
+
+import androidx.annotation.StringRes
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import kotlin.reflect.KClass
+
+/**
+ * Models a "quick affordance" in the keyguard bottom area (for example, a button on the
+ * lock-screen).
+ */
+sealed class KeyguardQuickAffordanceModel {
+
+ /** No affordance should show up. */
+ object Hidden : KeyguardQuickAffordanceModel()
+
+ /** A affordance is visible. */
+ data class Visible(
+ /** Identifier for the affordance this is modeling. */
+ val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ /** An icon for the affordance. */
+ val icon: ContainedDrawable,
+ /**
+ * Resource ID for a string to use for the accessibility content description text of the
+ * affordance.
+ */
+ @StringRes val contentDescriptionResourceId: Int,
+ ) : KeyguardQuickAffordanceModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
new file mode 100644
index 000000000000..b71e15d34afe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.keyguard.shared.model
+
+/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
+enum class KeyguardQuickAffordancePosition {
+ BOTTOM_START,
+ BOTTOM_END,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
new file mode 100644
index 000000000000..04d30bfb00f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -0,0 +1,307 @@
+/*
+ * 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.keyguard.ui.binder
+
+import android.util.Size
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewPropertyAnimator
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Binds a keyguard bottom area view to its view-model.
+ *
+ * To use this properly, users should maintain a one-to-one relationship between the [View] and the
+ * view-binding, binding each view only once. It is okay and expected for the same instance of the
+ * view-model to be reused for multiple view/view-binder bindings.
+ */
+object KeyguardBottomAreaViewBinder {
+
+ private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
+
+ /**
+ * Defines interface for an object that acts as the binding between the view and its view-model.
+ *
+ * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
+ * it is bound.
+ */
+ interface Binding {
+ /**
+ * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
+ * indication areas.
+ */
+ fun getIndicationAreaAnimators(): List<ViewPropertyAnimator>
+
+ /** Notifies that device configuration has changed. */
+ fun onConfigurationChanged()
+ }
+
+ /** Binds the view to the view-model, continuing to update the former based on the latter. */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ viewModel: KeyguardBottomAreaViewModel,
+ falsingManager: FalsingManager,
+ ): Binding {
+ val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
+ val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
+ val startButton: ImageView = view.requireViewById(R.id.start_button)
+ val endButton: ImageView = view.requireViewById(R.id.end_button)
+ val overlayContainer: View = view.requireViewById(R.id.overlay_container)
+ val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
+ val indicationTextBottom: TextView =
+ view.requireViewById(R.id.keyguard_indication_text_bottom)
+
+ view.clipChildren = false
+ view.clipToPadding = false
+
+ val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ combine(viewModel.startButton, viewModel.animateButtonReveal) {
+ buttonModel,
+ animateReveal ->
+ Pair(buttonModel, animateReveal)
+ }
+ .collect { (buttonModel, animateReveal) ->
+ updateButton(
+ view = startButton,
+ viewModel = buttonModel,
+ animateReveal = animateReveal,
+ falsingManager = falsingManager,
+ )
+ }
+ }
+
+ launch {
+ combine(viewModel.endButton, viewModel.animateButtonReveal) {
+ buttonModel,
+ animateReveal ->
+ Pair(buttonModel, animateReveal)
+ }
+ .collect { (buttonModel, animateReveal) ->
+ updateButton(
+ view = endButton,
+ viewModel = buttonModel,
+ animateReveal = animateReveal,
+ falsingManager = falsingManager,
+ )
+ }
+ }
+
+ launch {
+ viewModel.isOverlayContainerVisible.collect { isVisible ->
+ overlayContainer.visibility =
+ if (isVisible) {
+ View.VISIBLE
+ } else {
+ View.INVISIBLE
+ }
+ }
+ }
+
+ launch {
+ viewModel.alpha.collect { alpha ->
+ view.importantForAccessibility =
+ if (alpha == 0f) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+
+ ambientIndicationArea?.alpha = alpha
+ indicationArea.alpha = alpha
+ startButton.alpha = alpha
+ endButton.alpha = alpha
+ }
+ }
+
+ launch {
+ viewModel.indicationAreaTranslationX.collect { translationX ->
+ indicationArea.translationX = translationX
+ ambientIndicationArea?.translationX = translationX
+ }
+ }
+
+ launch {
+ combine(
+ viewModel.isIndicationAreaPadded,
+ configurationBasedDimensions.map { it.indicationAreaPaddingPx },
+ ) { isPadded, paddingIfPaddedPx ->
+ if (isPadded) {
+ paddingIfPaddedPx
+ } else {
+ 0
+ }
+ }
+ .collect { paddingPx ->
+ indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
+ }
+ }
+
+ launch {
+ configurationBasedDimensions
+ .map { it.defaultBurnInPreventionYOffsetPx }
+ .flatMapLatest { defaultBurnInOffsetY ->
+ viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
+ }
+ .collect { translationY ->
+ indicationArea.translationY = translationY
+ ambientIndicationArea?.translationY = translationY
+ }
+ }
+
+ launch {
+ configurationBasedDimensions.collect { dimensions ->
+ indicationText.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ dimensions.indicationTextSizePx.toFloat(),
+ )
+ indicationTextBottom.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ dimensions.indicationTextSizePx.toFloat(),
+ )
+
+ startButton.updateLayoutParams<ViewGroup.LayoutParams> {
+ width = dimensions.buttonSizePx.width
+ height = dimensions.buttonSizePx.height
+ }
+ endButton.updateLayoutParams<ViewGroup.LayoutParams> {
+ width = dimensions.buttonSizePx.width
+ height = dimensions.buttonSizePx.height
+ }
+ }
+ }
+ }
+ }
+
+ return object : Binding {
+ override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
+ return listOf(indicationArea, ambientIndicationArea).mapNotNull { it?.animate() }
+ }
+
+ override fun onConfigurationChanged() {
+ configurationBasedDimensions.value = loadFromResources(view)
+ }
+ }
+ }
+
+ private fun updateButton(
+ view: ImageView,
+ viewModel: KeyguardQuickAffordanceViewModel,
+ animateReveal: Boolean,
+ falsingManager: FalsingManager,
+ ) {
+ if (!viewModel.isVisible) {
+ view.isVisible = false
+ return
+ }
+
+ if (!view.isVisible) {
+ view.isVisible = true
+ if (animateReveal) {
+ view.alpha = 0f
+ view.translationY = view.height / 2f
+ view
+ .animate()
+ .alpha(1f)
+ .translationY(0f)
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .setDuration(EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS)
+ .start()
+ }
+ }
+
+ when (viewModel.icon) {
+ is ContainedDrawable.WithDrawable -> view.setImageDrawable(viewModel.icon.drawable)
+ is ContainedDrawable.WithResource -> view.setImageResource(viewModel.icon.resourceId)
+ }
+
+ view.drawable.setTint(
+ Utils.getColorAttrDefaultColor(
+ view.context,
+ com.android.internal.R.attr.textColorPrimary
+ )
+ )
+ view.backgroundTintList =
+ Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
+
+ view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId)
+ view.setOnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return@setOnClickListener
+ }
+
+ if (viewModel.configKey != null) {
+ viewModel.onClicked(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = viewModel.configKey,
+ animationController = ActivityLaunchAnimator.Controller.fromView(view),
+ )
+ )
+ }
+ }
+ }
+
+ private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+ return ConfigurationBasedDimensions(
+ defaultBurnInPreventionYOffsetPx =
+ view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
+ indicationAreaPaddingPx =
+ view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding),
+ indicationTextSizePx =
+ view.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.text_size_small_material,
+ ),
+ buttonSizePx =
+ Size(
+ view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+ view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+ ),
+ )
+ }
+
+ private data class ConfigurationBasedDimensions(
+ val defaultBurnInPreventionYOffsetPx: Int,
+ val indicationAreaPaddingPx: Int,
+ val indicationTextSizePx: Int,
+ val buttonSizePx: Size,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
new file mode 100644
index 000000000000..4b69a81c8996
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View-model for the keyguard bottom area view */
+class KeyguardBottomAreaViewModel
+@Inject
+constructor(
+ private val observeQuickAffordanceUseCase: ObserveKeyguardQuickAffordanceUseCase,
+ private val onQuickAffordanceClickedUseCase: OnKeyguardQuickAffordanceClickedUseCase,
+ observeBottomAreaAlphaUseCase: ObserveBottomAreaAlphaUseCase,
+ observeIsDozingUseCase: ObserveIsDozingUseCase,
+ observeAnimateBottomAreaTransitionsUseCase: ObserveAnimateBottomAreaTransitionsUseCase,
+ private val observeDozeAmountUseCase: ObserveDozeAmountUseCase,
+ observeClockPositionUseCase: ObserveClockPositionUseCase,
+ private val burnInHelperWrapper: BurnInHelperWrapper,
+) {
+ /** An observable for the view-model of the "start button" quick affordance. */
+ val startButton: Flow<KeyguardQuickAffordanceViewModel> =
+ button(KeyguardQuickAffordancePosition.BOTTOM_START)
+ /** An observable for the view-model of the "end button" quick affordance. */
+ val endButton: Flow<KeyguardQuickAffordanceViewModel> =
+ button(KeyguardQuickAffordancePosition.BOTTOM_END)
+ /**
+ * An observable for whether the next time a quick action button becomes visible, it should
+ * animate.
+ */
+ val animateButtonReveal: Flow<Boolean> =
+ observeAnimateBottomAreaTransitionsUseCase().distinctUntilChanged()
+ /** An observable for whether the overlay container should be visible. */
+ val isOverlayContainerVisible: Flow<Boolean> =
+ observeIsDozingUseCase().map { !it }.distinctUntilChanged()
+ /** An observable for the alpha level for the entire bottom area. */
+ val alpha: Flow<Float> = observeBottomAreaAlphaUseCase().distinctUntilChanged()
+ /** An observable for whether the indication area should be padded. */
+ val isIndicationAreaPadded: Flow<Boolean> =
+ combine(startButton, endButton) { startButtonModel, endButtonModel ->
+ startButtonModel.isVisible || endButtonModel.isVisible
+ }
+ .distinctUntilChanged()
+ /** An observable for the x-offset by which the indication area should be translated. */
+ val indicationAreaTranslationX: Flow<Float> =
+ observeClockPositionUseCase().map { it.x.toFloat() }.distinctUntilChanged()
+
+ /** Returns an observable for the y-offset by which the indication area should be translated. */
+ fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
+ return observeDozeAmountUseCase()
+ .map { dozeAmount ->
+ dozeAmount *
+ (burnInHelperWrapper.burnInOffset(
+ /* amplitude = */ defaultBurnInOffset * 2,
+ /* xAxis= */ false,
+ ) - defaultBurnInOffset)
+ }
+ .distinctUntilChanged()
+ }
+
+ private fun button(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceViewModel> {
+ return observeQuickAffordanceUseCase(position)
+ .map { model -> model.toViewModel() }
+ .distinctUntilChanged()
+ }
+
+ private fun KeyguardQuickAffordanceModel.toViewModel(): KeyguardQuickAffordanceViewModel {
+ return when (this) {
+ is KeyguardQuickAffordanceModel.Visible ->
+ KeyguardQuickAffordanceViewModel(
+ configKey = configKey,
+ isVisible = true,
+ icon = icon,
+ contentDescriptionResourceId = contentDescriptionResourceId,
+ onClicked = { parameters ->
+ onQuickAffordanceClickedUseCase(
+ configKey = parameters.configKey,
+ animationController = parameters.animationController,
+ )
+ },
+ )
+ is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
new file mode 100644
index 000000000000..2417998784e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.annotation.StringRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import kotlin.reflect.KClass
+
+/** Models the UI state of a keyguard quick affordance button. */
+data class KeyguardQuickAffordanceViewModel(
+ val configKey: KClass<*>? = null,
+ val isVisible: Boolean = false,
+ val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
+ @StringRes val contentDescriptionResourceId: Int = 0,
+ val onClicked: (OnClickedParameters) -> Unit = {},
+) {
+ data class OnClickedParameters(
+ val configKey: KClass<*>,
+ val animationController: ActivityLaunchAnimator.Controller?,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index db446c39767a..dc23684dd517 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -20,14 +20,19 @@ import android.os.Trace
import android.util.Log
import com.android.systemui.log.dagger.LogModule
import com.android.systemui.util.collection.RingBuffer
+import com.google.errorprone.annotations.CompileTimeConstant
import java.io.PrintWriter
import java.text.SimpleDateFormat
+import java.util.Arrays.stream
import java.util.Locale
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.BlockingQueue
import kotlin.concurrent.thread
import kotlin.math.max
+const val UNBOUNDED_STACK_TRACE = -1
+const val NESTED_TRACE_DEPTH = 10
+
/**
* A simple ring buffer of recyclable log messages
*
@@ -69,12 +74,18 @@ import kotlin.math.max
* @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
* out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
* the maximum, it behaves like a ring buffer.
+ * @param rootStackTraceDepth The number of stack trace elements to be logged for an exception when
+ * the logBuffer is dumped. Defaulted to -1 [UNBOUNDED_STACK_TRACE] to print the entire stack trace.
+ * @param nestedStackTraceDepth The number of stack trace elements to be logged for any nested
+ * exceptions present in [Throwable.cause] or [Throwable.suppressedExceptions].
*/
class LogBuffer @JvmOverloads constructor(
private val name: String,
private val maxSize: Int,
private val logcatEchoTracker: LogcatEchoTracker,
- private val systrace: Boolean = true
+ private val systrace: Boolean = true,
+ private val rootStackTraceDepth: Int = UNBOUNDED_STACK_TRACE,
+ private val nestedStackTraceDepth: Int = NESTED_TRACE_DEPTH,
) {
private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
@@ -107,11 +118,11 @@ class LogBuffer @JvmOverloads constructor(
* May also log the message to logcat if echoing is enabled for this buffer or tag.
*
* The actual string of the log message is not constructed until it is needed. To accomplish
- * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is
- * obtained and is passed to the [initializer]. The initializer stores any relevant data on the
- * message's fields. The message is then inserted into the buffer where it waits until it is
- * either pushed out by newer messages or it needs to printed. If and when this latter moment
- * occurs, the [printer] function is called on the message. It reads whatever data the
+ * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is
+ * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data
+ * on the message's fields. The message is then inserted into the buffer where it waits until it
+ * is either pushed out by newer messages or it needs to printed. If and when this latter moment
+ * occurs, the [messagePrinter] function is called on the message. It reads whatever data the
* initializer stored and converts it to a human-readable log message.
*
* @param tag A string of at most 23 characters, used for grouping logs into categories or
@@ -120,27 +131,49 @@ class LogBuffer @JvmOverloads constructor(
* echoed. In general, a module should split most of its logs into either INFO or DEBUG level.
* INFO level should be reserved for information that other parts of the system might care
* about, leaving the specifics of code's day-to-day operations to DEBUG.
- * @param initializer A function that will be called immediately to store relevant data on the
- * log message. The value of `this` will be the LogMessage to be initialized.
- * @param printer A function that will be called if and when the message needs to be dumped to
- * logcat or a bug report. It should read the data stored by the initializer and convert it to
- * a human-readable string. The value of `this` will be the LogMessage to be printed.
- * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
- * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
- * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
+ * @param messageInitializer A function that will be called immediately to store relevant data
+ * on the log message. The value of `this` will be the LogMessage to be initialized.
+ * @param messagePrinter A function that will be called if and when the message needs to be
+ * dumped to logcat or a bug report. It should read the data stored by the initializer and
+ * convert it to a human-readable string. The value of `this` will be the LogMessage to be
+ * printed. **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and
+ * NEVER any variables in its enclosing scope. Otherwise, the runtime will need to allocate a
+ * new instance of the printer for each call, thwarting our attempts at avoiding any sort of
+ * allocation.
+ * @param exception Provide any exception that need to be logged. This is saved as
+ * [LogMessage.exception]
*/
+ @JvmOverloads
inline fun log(
- tag: String,
- level: LogLevel,
- initializer: LogMessage.() -> Unit,
- noinline printer: LogMessage.() -> String
+ tag: String,
+ level: LogLevel,
+ messageInitializer: MessageInitializer,
+ noinline messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
) {
- val message = obtain(tag, level, printer)
- initializer(message)
+ val message = obtain(tag, level, messagePrinter, exception)
+ messageInitializer(message)
commit(message)
}
/**
+ * Logs a compile-time string constant [message] to the log buffer. Use sparingly.
+ *
+ * May also log the message to logcat if echoing is enabled for this buffer or tag. This is for
+ * simpler use-cases where [message] is a compile time string constant. For use-cases where the
+ * log message is built during runtime, use the [LogBuffer.log] overloaded method that takes in
+ * an initializer and a message printer.
+ *
+ * Log buffers are limited by the number of entries, so logging more frequently
+ * will limit the time window that the LogBuffer covers in a bug report. Richer logs, on the
+ * other hand, make a bug report more actionable, so using the [log] with a messagePrinter to
+ * add more detail to every log may do more to improve overall logging than adding more logs
+ * with this method.
+ */
+ fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) =
+ log(tag, level, {str1 = message}, { str1!! })
+
+ /**
* You should call [log] instead of this method.
*
* Obtains the next [LogMessage] from the ring buffer. If the buffer is not yet at max size,
@@ -151,15 +184,16 @@ class LogBuffer @JvmOverloads constructor(
*/
@Synchronized
fun obtain(
- tag: String,
- level: LogLevel,
- printer: (LogMessage) -> String
- ): LogMessageImpl {
+ tag: String,
+ level: LogLevel,
+ messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
+ ): LogMessage {
if (!mutable) {
return FROZEN_MESSAGE
}
val message = buffer.advance()
- message.reset(tag, level, System.currentTimeMillis(), printer)
+ message.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception)
return message
}
@@ -230,19 +264,79 @@ class LogBuffer @JvmOverloads constructor(
}
}
- private fun dumpMessage(message: LogMessage, pw: PrintWriter) {
- pw.print(DATE_FORMAT.format(message.timestamp))
+ private fun dumpMessage(
+ message: LogMessage,
+ pw: PrintWriter
+ ) {
+ val formattedTimestamp = DATE_FORMAT.format(message.timestamp)
+ val shortLevel = message.level.shortString
+ val messageToPrint = message.messagePrinter(message)
+ val tag = message.tag
+ printLikeLogcat(pw, formattedTimestamp, shortLevel, tag, messageToPrint)
+ message.exception?.let { ex ->
+ printException(
+ pw,
+ formattedTimestamp,
+ shortLevel,
+ ex,
+ tag,
+ stackTraceDepth = rootStackTraceDepth)
+ }
+ }
+
+ private fun printException(
+ pw: PrintWriter,
+ timestamp: String,
+ level: String,
+ exception: Throwable,
+ tag: String,
+ exceptionMessagePrefix: String = "",
+ stackTraceDepth: Int = UNBOUNDED_STACK_TRACE
+ ) {
+ val message = "$exceptionMessagePrefix$exception"
+ printLikeLogcat(pw, timestamp, level, tag, message)
+ var stacktraceStream = stream(exception.stackTrace)
+ if (stackTraceDepth != UNBOUNDED_STACK_TRACE) {
+ stacktraceStream = stacktraceStream.limit(stackTraceDepth.toLong())
+ }
+ stacktraceStream.forEach { line ->
+ printLikeLogcat(pw, timestamp, level, tag, "\tat $line")
+ }
+ exception.cause?.let { cause ->
+ printException(pw, timestamp, level, cause, tag, "Caused by: ", nestedStackTraceDepth)
+ }
+ exception.suppressedExceptions.forEach { suppressed ->
+ printException(
+ pw,
+ timestamp,
+ level,
+ suppressed,
+ tag,
+ "Suppressed: ",
+ nestedStackTraceDepth
+ )
+ }
+ }
+
+ private fun printLikeLogcat(
+ pw: PrintWriter,
+ formattedTimestamp: String,
+ shortLogLevel: String,
+ tag: String,
+ message: String
+ ) {
+ pw.print(formattedTimestamp)
pw.print(" ")
- pw.print(message.level.shortString)
+ pw.print(shortLogLevel)
pw.print(" ")
- pw.print(message.tag)
+ pw.print(tag)
pw.print(": ")
- pw.println(message.printer(message))
+ pw.println(message)
}
private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) {
if (toLogcat || toSystrace) {
- val strMessage = message.printer(message)
+ val strMessage = message.messagePrinter(message)
if (toSystrace) {
echoToSystrace(message, strMessage)
}
@@ -259,16 +353,22 @@ class LogBuffer @JvmOverloads constructor(
private fun echoToLogcat(message: LogMessage, strMessage: String) {
when (message.level) {
- LogLevel.VERBOSE -> Log.v(message.tag, strMessage)
- LogLevel.DEBUG -> Log.d(message.tag, strMessage)
- LogLevel.INFO -> Log.i(message.tag, strMessage)
- LogLevel.WARNING -> Log.w(message.tag, strMessage)
- LogLevel.ERROR -> Log.e(message.tag, strMessage)
- LogLevel.WTF -> Log.wtf(message.tag, strMessage)
+ LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception)
+ LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception)
+ LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception)
+ LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception)
+ LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception)
+ LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
}
}
}
+/**
+ * A function that will be called immediately to store relevant data on the log message. The value
+ * of `this` will be the LogMessage to be initialized.
+ */
+typealias MessageInitializer = LogMessage.() -> Unit
+
private const val TAG = "LogBuffer"
private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
-private val FROZEN_MESSAGE = LogMessageImpl.create() \ No newline at end of file
+private val FROZEN_MESSAGE = LogMessageImpl.create()
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
index 2a0a2aa6fb38..987aea8bff08 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
@@ -25,7 +25,7 @@ package com.android.systemui.log
*
* When a message is logged, the code doing the logging stores data in one or more of the generic
* fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the
- * [printer] function reads the data stored in the generic fields and converts that to a human-
+ * [messagePrinter] function reads the data stored in the generic fields and converts that to a human-
* readable string. Thus, for every log type there must be a specialized initializer function that
* stores data specific to that log type and a specialized printer function that prints that data.
*
@@ -35,7 +35,8 @@ interface LogMessage {
val level: LogLevel
val tag: String
val timestamp: Long
- val printer: LogMessage.() -> String
+ val messagePrinter: MessagePrinter
+ val exception: Throwable?
var str1: String?
var str2: String?
@@ -50,3 +51,13 @@ interface LogMessage {
var bool3: Boolean
var bool4: Boolean
}
+
+/**
+ * A function that will be called if and when the message needs to be dumped to
+ * logcat or a bug report. It should read the data stored by the initializer and convert it to
+ * a human-readable string. The value of `this` will be the LogMessage to be printed.
+ * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
+ * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
+ * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
+ */
+typealias MessagePrinter = LogMessage.() -> String
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
index d33ac4b4a80b..4dd6f652d1c7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
@@ -23,7 +23,8 @@ data class LogMessageImpl(
override var level: LogLevel,
override var tag: String,
override var timestamp: Long,
- override var printer: LogMessage.() -> String,
+ override var messagePrinter: MessagePrinter,
+ override var exception: Throwable?,
override var str1: String?,
override var str2: String?,
override var str3: String?,
@@ -35,19 +36,21 @@ data class LogMessageImpl(
override var bool1: Boolean,
override var bool2: Boolean,
override var bool3: Boolean,
- override var bool4: Boolean
+ override var bool4: Boolean,
) : LogMessage {
fun reset(
tag: String,
level: LogLevel,
timestamp: Long,
- renderer: LogMessage.() -> String
+ renderer: MessagePrinter,
+ exception: Throwable? = null,
) {
this.level = level
this.tag = tag
this.timestamp = timestamp
- this.printer = renderer
+ this.messagePrinter = renderer
+ this.exception = exception
str1 = null
str2 = null
str3 = null
@@ -68,7 +71,8 @@ data class LogMessageImpl(
LogLevel.DEBUG,
DEFAULT_TAG,
0,
- DEFAULT_RENDERER,
+ DEFAULT_PRINTER,
+ null,
null,
null,
null,
@@ -86,4 +90,4 @@ data class LogMessageImpl(
}
private const val DEFAULT_TAG = "UnknownTag"
-private val DEFAULT_RENDERER: LogMessage.() -> String = { "Unknown message: $this" }
+private val DEFAULT_PRINTER: MessagePrinter = { "Unknown message: $this" }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
new file mode 100644
index 000000000000..323ee21953ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.log.dagger
+
+/** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */
+annotation class KeyguardUpdateMonitorLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index d0da18aaba05..c858bc3e2c81 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -255,6 +255,16 @@ public class LogModule {
return factory.create("MediaCarouselCtlrLog", 20);
}
+ /**
+ * Provides a {@link LogBuffer} for use in the status bar connectivity pipeline
+ */
+ @Provides
+ @SysUISingleton
+ @StatusBarConnectivityLog
+ public static LogBuffer provideStatusBarConnectivityBuffer(LogBufferFactory factory) {
+ return factory.create("StatusBarConnectivityLog", 64);
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
@@ -277,4 +287,14 @@ public class LogModule {
public static LogBuffer provideStatusBarNetworkControllerBuffer(LogBufferFactory factory) {
return factory.create("StatusBarNetworkControllerLog", 20);
}
+
+ /**
+ * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardUpdateMonitorLog
+ public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
+ return factory.create("KeyguardUpdateMonitorLog", 200);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
new file mode 100644
index 000000000000..f03fbcba41b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
@@ -0,0 +1,36 @@
+/*
+ * 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for events processed by {@link ConnectivityInfoProcessor}
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface StatusBarConnectivityLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
index e95976f555f8..a29c5883118c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
@@ -27,4 +27,4 @@ interface ChipInfoCommon {
fun getTimeoutMs(): Long
}
-const val DEFAULT_TIMEOUT_MILLIS = 4000L
+const val DEFAULT_TIMEOUT_MILLIS = 10000L
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index c9fce794f57f..5f478ce32590 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -26,18 +26,18 @@ import android.os.PowerManager
import android.os.SystemClock
import android.util.Log
import android.view.LayoutInflater
-import android.view.MotionEvent
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
+import androidx.annotation.CallSuper
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
@@ -52,17 +52,16 @@ import com.android.systemui.util.view.ViewUtil
* display the chip in a certain state, since they receive <T> in [updateChipView].
*/
abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
- internal val context: Context,
- internal val logger: MediaTttLogger,
- internal val windowManager: WindowManager,
- private val viewUtil: ViewUtil,
- @Main private val mainExecutor: DelayableExecutor,
- private val accessibilityManager: AccessibilityManager,
- private val tapGestureDetector: TapGestureDetector,
- private val powerManager: PowerManager,
- @LayoutRes private val chipLayoutRes: Int
+ internal val context: Context,
+ internal val logger: MediaTttLogger,
+ internal val windowManager: WindowManager,
+ private val viewUtil: ViewUtil,
+ @Main private val mainExecutor: DelayableExecutor,
+ private val accessibilityManager: AccessibilityManager,
+ private val configurationController: ConfigurationController,
+ private val powerManager: PowerManager,
+ @LayoutRes private val chipLayoutRes: Int,
) {
-
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
* all subclasses.
@@ -89,42 +88,43 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
/** The chip view currently being displayed. Null if the chip is not being displayed. */
private var chipView: ViewGroup? = null
+ /** The chip info currently being displayed. Null if the chip is not being displayed. */
+ internal var chipInfo: T? = null
+
/** A [Runnable] that, when run, will cancel the pending timeout of the chip. */
private var cancelChipViewTimeout: Runnable? = null
/**
- * Displays the chip with the current state.
+ * Displays the chip with the provided [newChipInfo].
*
* This method handles inflating and attaching the view, then delegates to [updateChipView] to
* display the correct information in the chip.
*/
- fun displayChip(chipInfo: T) {
- val oldChipView = chipView
- if (chipView == null) {
- chipView = LayoutInflater
- .from(context)
- .inflate(chipLayoutRes, null) as ViewGroup
- }
- val currentChipView = chipView!!
-
- updateChipView(chipInfo, currentChipView)
-
- // Add view if necessary
- if (oldChipView == null) {
- tapGestureDetector.addOnGestureDetectedCallback(TAG, this::onScreenTapped)
- windowManager.addView(chipView, windowLayoutParams)
- // Wake the screen so the user will see the chip
- powerManager.wakeUp(
- SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_APPLICATION,
- "com.android.systemui:media_tap_to_transfer_activated"
- )
- animateChipIn(currentChipView)
+ fun displayChip(newChipInfo: T) {
+ val currentChipView = chipView
+
+ if (currentChipView != null) {
+ updateChipView(newChipInfo, currentChipView)
+ } else {
+ // The chip is new, so set up all our callbacks and inflate the view
+ configurationController.addCallback(displayScaleListener)
+ // Wake the screen if necessary so the user will see the chip. (Per b/239426653, we want
+ // the chip to show over the dream state, so we should only wake up if the screen is
+ // completely off.)
+ if (!powerManager.isScreenOn) {
+ powerManager.wakeUp(
+ SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_APPLICATION,
+ "com.android.systemui:media_tap_to_transfer_activated"
+ )
+ }
+
+ inflateAndUpdateChip(newChipInfo)
}
// Cancel and re-set the chip timeout each time we get a new state.
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
- chipInfo.getTimeoutMs().toInt(),
+ newChipInfo.getTimeoutMs().toInt(),
// Not all chips have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
// include it just to be safe.
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
@@ -136,6 +136,32 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
)
}
+ /** Inflates a new chip view, updates it with [newChipInfo], and adds the view to the window. */
+ private fun inflateAndUpdateChip(newChipInfo: T) {
+ val newChipView = LayoutInflater
+ .from(context)
+ .inflate(chipLayoutRes, null) as ViewGroup
+ chipView = newChipView
+ updateChipView(newChipInfo, newChipView)
+ windowManager.addView(newChipView, windowLayoutParams)
+ animateChipIn(newChipView)
+ }
+
+ /** Removes then re-inflates the chip. */
+ private fun reinflateChip() {
+ val currentChipInfo = chipInfo
+ if (chipView == null || currentChipInfo == null) { return }
+
+ windowManager.removeView(chipView)
+ inflateAndUpdateChip(currentChipInfo)
+ }
+
+ private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
+ override fun onDensityOrFontScaleChanged() {
+ reinflateChip()
+ }
+ }
+
/**
* Hides the chip.
*
@@ -145,17 +171,21 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
open fun removeChip(removalReason: String) {
if (chipView == null) { return }
logger.logChipRemoval(removalReason)
- tapGestureDetector.removeOnGestureDetectedCallback(TAG)
+ configurationController.removeCallback(displayScaleListener)
windowManager.removeView(chipView)
chipView = null
+ chipInfo = null
// No need to time the chip out since it's already gone
cancelChipViewTimeout?.run()
}
/**
- * A method implemented by subclasses to update [currentChipView] based on [chipInfo].
+ * A method implemented by subclasses to update [currentChipView] based on [newChipInfo].
*/
- abstract fun updateChipView(chipInfo: T, currentChipView: ViewGroup)
+ @CallSuper
+ open fun updateChipView(newChipInfo: T, currentChipView: ViewGroup) {
+ chipInfo = newChipInfo
+ }
/**
* A method that can be implemented by subclcasses to do custom animations for when the chip
@@ -226,15 +256,6 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
isAppIcon = false
)
}
-
- private fun onScreenTapped(e: MotionEvent) {
- val view = chipView ?: return
- // If the tap is within the chip bounds, we shouldn't hide the chip (in case users think the
- // chip is tappable).
- if (!viewUtil.touchIsWithinView(view, e.x, e.y)) {
- removeChip(MediaTttRemovalReason.REASON_SCREEN_TAP)
- }
- }
}
// Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 99a5b8b2d450..495f697f57e6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -41,7 +41,7 @@ import com.android.systemui.media.taptotransfer.common.DEFAULT_TIMEOUT_MILLIS
import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
@@ -54,27 +54,27 @@ import javax.inject.Inject
*/
@SysUISingleton
class MediaTttChipControllerReceiver @Inject constructor(
- commandQueue: CommandQueue,
- context: Context,
- @MediaTttReceiverLogger logger: MediaTttLogger,
- windowManager: WindowManager,
- viewUtil: ViewUtil,
- mainExecutor: DelayableExecutor,
- accessibilityManager: AccessibilityManager,
- tapGestureDetector: TapGestureDetector,
- powerManager: PowerManager,
- @Main private val mainHandler: Handler,
- private val uiEventLogger: MediaTttReceiverUiEventLogger,
+ commandQueue: CommandQueue,
+ context: Context,
+ @MediaTttReceiverLogger logger: MediaTttLogger,
+ windowManager: WindowManager,
+ viewUtil: ViewUtil,
+ mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ @Main private val mainHandler: Handler,
+ private val uiEventLogger: MediaTttReceiverUiEventLogger,
) : MediaTttChipControllerCommon<ChipReceiverInfo>(
- context,
- logger,
- windowManager,
- viewUtil,
- mainExecutor,
- accessibilityManager,
- tapGestureDetector,
- powerManager,
- R.layout.media_ttt_chip_receiver
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ R.layout.media_ttt_chip_receiver,
) {
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -140,12 +140,13 @@ class MediaTttChipControllerReceiver @Inject constructor(
)
}
- override fun updateChipView(chipInfo: ChipReceiverInfo, currentChipView: ViewGroup) {
+ override fun updateChipView(newChipInfo: ChipReceiverInfo, currentChipView: ViewGroup) {
+ super.updateChipView(newChipInfo, currentChipView)
setIcon(
currentChipView,
- chipInfo.routeInfo.packageName,
- chipInfo.appIconDrawableOverride,
- chipInfo.appNameOverride
+ newChipInfo.routeInfo.packageName,
+ newChipInfo.appIconDrawableOverride,
+ newChipInfo.appNameOverride
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index cd86fff1c6f8..a153cb6c0d31 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -243,6 +243,6 @@ enum class ChipStateSender(
// Give the Transfer*Triggered states a longer timeout since those states represent an active
// process and we should keep the user informed about it as long as possible (but don't allow it to
// continue indefinitely).
-private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 15000L
+private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000L
private const val TAG = "ChipStateSender"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 797a7701413b..3ea11b8aa4dd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -38,7 +38,7 @@ import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCom
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttRemovalReason
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
@@ -49,33 +49,31 @@ import javax.inject.Inject
*/
@SysUISingleton
class MediaTttChipControllerSender @Inject constructor(
- commandQueue: CommandQueue,
- context: Context,
- @MediaTttSenderLogger logger: MediaTttLogger,
- windowManager: WindowManager,
- viewUtil: ViewUtil,
- @Main mainExecutor: DelayableExecutor,
- accessibilityManager: AccessibilityManager,
- tapGestureDetector: TapGestureDetector,
- powerManager: PowerManager,
- private val uiEventLogger: MediaTttSenderUiEventLogger
+ commandQueue: CommandQueue,
+ context: Context,
+ @MediaTttSenderLogger logger: MediaTttLogger,
+ windowManager: WindowManager,
+ viewUtil: ViewUtil,
+ @Main mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ private val uiEventLogger: MediaTttSenderUiEventLogger
) : MediaTttChipControllerCommon<ChipSenderInfo>(
- context,
- logger,
- windowManager,
- viewUtil,
- mainExecutor,
- accessibilityManager,
- tapGestureDetector,
- powerManager,
- R.layout.media_ttt_chip
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ R.layout.media_ttt_chip,
) {
override val windowLayoutParams = commonWindowLayoutParams.apply {
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
}
- private var currentlyDisplayedChipState: ChipStateSender? = null
-
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferSenderDisplay(
@StatusBarManager.MediaTransferSenderState displayState: Int,
@@ -116,16 +114,18 @@ class MediaTttChipControllerSender @Inject constructor(
/** Displays the chip view for the given state. */
override fun updateChipView(
- chipInfo: ChipSenderInfo,
- currentChipView: ViewGroup) {
- val chipState = chipInfo.state
- currentlyDisplayedChipState = chipState
+ newChipInfo: ChipSenderInfo,
+ currentChipView: ViewGroup
+ ) {
+ super.updateChipView(newChipInfo, currentChipView)
+
+ val chipState = newChipInfo.state
// App icon
- setIcon(currentChipView, chipInfo.routeInfo.packageName)
+ setIcon(currentChipView, newChipInfo.routeInfo.packageName)
// Text
- val otherDeviceName = chipInfo.routeInfo.name.toString()
+ val otherDeviceName = newChipInfo.routeInfo.name.toString()
currentChipView.requireViewById<TextView>(R.id.text).apply {
text = chipState.getChipTextString(context, otherDeviceName)
}
@@ -137,7 +137,7 @@ class MediaTttChipControllerSender @Inject constructor(
// Undo
val undoView = currentChipView.requireViewById<View>(R.id.undo)
val undoClickListener = chipState.undoClickListener(
- this, chipInfo.routeInfo, chipInfo.undoCallback, uiEventLogger
+ this, newChipInfo.routeInfo, newChipInfo.undoCallback, uiEventLogger
)
undoView.setOnClickListener(undoClickListener)
undoView.visibility = (undoClickListener != null).visibleIfTrue()
@@ -161,12 +161,11 @@ class MediaTttChipControllerSender @Inject constructor(
override fun removeChip(removalReason: String) {
// Don't remove the chip if we're mid-transfer since the user should still be able to
// see the status of the transfer. (But do remove it if it's finally timed out.)
- if (currentlyDisplayedChipState?.isMidTransfer == true
- && removalReason != MediaTttRemovalReason.REASON_TIMEOUT) {
+ if (chipInfo?.state?.isMidTransfer == true &&
+ removalReason != MediaTttRemovalReason.REASON_TIMEOUT) {
return
}
super.removeChip(removalReason)
- currentlyDisplayedChipState = null
}
private fun Boolean.visibleIfTrue(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 2a7c8716f036..2d7a809644c0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -51,6 +51,8 @@ import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.system.QuickStepContract;
@@ -82,6 +84,7 @@ public class NavigationBarController implements
private final Context mContext;
private final Handler mHandler;
private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
+ private FeatureFlags mFeatureFlags;
private final DisplayManager mDisplayManager;
private final TaskbarDelegate mTaskbarDelegate;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -113,10 +116,12 @@ public class NavigationBarController implements
AutoHideController autoHideController,
LightBarController lightBarController,
Optional<Pip> pipOptional,
- Optional<BackAnimation> backAnimation) {
+ Optional<BackAnimation> backAnimation,
+ FeatureFlags featureFlags) {
mContext = context;
mHandler = mainHandler;
mNavigationBarComponentFactory = navigationBarComponentFactory;
+ mFeatureFlags = featureFlags;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
commandQueue.addCallback(this);
configurationController.addCallback(this);
@@ -217,7 +222,10 @@ public class NavigationBarController implements
/** @return {@code true} if taskbar is enabled, false otherwise */
private boolean initializeTaskbarIfNecessary() {
- if (mIsTablet) {
+ // Enable for tablet or (phone AND flag is set); assuming phone = !mIsTablet
+ boolean taskbarEnabled = mIsTablet || mFeatureFlags.isEnabled(Flags.HIDE_NAVBAR_WINDOW);
+
+ if (taskbarEnabled) {
Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
// Remove navigation bar when taskbar is showing
removeNavigationBar(mContext.getDisplayId());
@@ -226,7 +234,7 @@ public class NavigationBarController implements
} else {
mTaskbarDelegate.destroy();
}
- return mIsTablet;
+ return taskbarEnabled;
}
@Override
@@ -294,6 +302,10 @@ public class NavigationBarController implements
*/
@VisibleForTesting
void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
+ if (initializeTaskbarIfNecessary()) {
+ return;
+ }
+
if (display == null) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index 6908e5ab49e6..209d09d0f907 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -243,7 +243,7 @@ public class QSCarrierGroupController {
mActivityStarter.postStartActivityDismissingKeyguard(
new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0);
};
- view.setOnClickListener(onClickListener);
+
mNoSimTextView = view.getNoSimTextView();
mNoSimTextView.setOnClickListener(onClickListener);
mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 99d0fe97971a..58e9bb85503a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -128,6 +128,10 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
@@ -290,11 +294,6 @@ public final class NotificationPanelViewController extends PanelViewController {
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final NotificationIconAreaController mNotificationIconAreaController;
- // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
- // changed.
- private static final int CAP_HEIGHT = 1456;
- private static final int FONT_HEIGHT = 2163;
-
/**
* Maximum time before which we will expand the panel even for slow motions when getting a
* touch passed over from launcher.
@@ -702,6 +701,12 @@ public final class NotificationPanelViewController extends PanelViewController {
};
private final CameraGestureHelper mCameraGestureHelper;
+ private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
+ private final Provider<SetClockPositionUseCase> mSetClockPositionUseCaseProvider;
+ private final Provider<SetKeyguardBottomAreaAlphaUseCase>
+ mSetKeyguardBottomAreaAlphaUseCaseProvider;
+ private final Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@@ -772,7 +777,12 @@ public final class NotificationPanelViewController extends PanelViewController {
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
SystemClock systemClock,
- CameraGestureHelper cameraGestureHelper) {
+ CameraGestureHelper cameraGestureHelper,
+ Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
+ Provider<SetClockPositionUseCase> setClockPositionUseCaseProvider,
+ Provider<SetKeyguardBottomAreaAlphaUseCase> setKeyguardBottomAreaAlphaUseCaseProvider,
+ Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
+ setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider) {
super(view,
falsingManager,
dozeLog,
@@ -902,6 +912,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
+ mKeyguardBottomAreaViewModelProvider = keyguardBottomAreaViewModelProvider;
onFinishInflate();
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -955,6 +966,10 @@ public final class NotificationPanelViewController extends PanelViewController {
}
});
mCameraGestureHelper = cameraGestureHelper;
+ mSetClockPositionUseCaseProvider = setClockPositionUseCaseProvider;
+ mSetKeyguardBottomAreaAlphaUseCaseProvider = setKeyguardBottomAreaAlphaUseCaseProvider;
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider =
+ setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
}
@VisibleForTesting
@@ -1107,13 +1122,6 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- /**
- * Returns if there's a custom clock being presented.
- */
- public boolean hasCustomClock() {
- return mKeyguardStatusViewController.hasCustomClock();
- }
-
private void setCentralSurfaces(CentralSurfaces centralSurfaces) {
// TODO: this can be injected.
mCentralSurfaces = centralSurfaces;
@@ -1286,11 +1294,17 @@ public final class NotificationPanelViewController extends PanelViewController {
}
private void initBottomArea() {
- mKeyguardBottomArea.init(
- mFalsingManager,
- mQuickAccessWalletController,
- mControlsComponent,
- mQRCodeScannerController);
+ if (mFeatureFlags.isEnabled(Flags.MODERN_BOTTOM_AREA)) {
+ mKeyguardBottomArea.init(mKeyguardBottomAreaViewModelProvider.get(), mFalsingManager);
+ } else {
+ // TODO(b/235403546): remove this method call when the new implementation is complete
+ // and these are not needed.
+ mKeyguardBottomArea.init(
+ mFalsingManager,
+ mQuickAccessWalletController,
+ mControlsComponent,
+ mQRCodeScannerController);
+ }
}
@VisibleForTesting
@@ -1466,6 +1480,8 @@ public final class NotificationPanelViewController extends PanelViewController {
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
+ mSetClockPositionUseCaseProvider.get().invoke(
+ mClockPositionResult.clockX, mClockPositionResult.clockY);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
mKeyguardStatusViewController.updatePosition(
@@ -3232,6 +3248,7 @@ public final class NotificationPanelViewController extends PanelViewController {
float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
mKeyguardBottomArea.setComponentAlphas(alpha);
+ mSetKeyguardBottomAreaAlphaUseCaseProvider.get().invoke(alpha);
mLockIconViewController.setAlpha(alpha);
}
@@ -3431,6 +3448,7 @@ public final class NotificationPanelViewController extends PanelViewController {
private void updateDozingVisibilities(boolean animate) {
mKeyguardBottomArea.setDozing(mDozing, animate);
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
if (!mDozing && animate) {
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
}
@@ -3733,6 +3751,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mDozing = dozing;
mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
mKeyguardBottomArea.setDozing(mDozing, animate);
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
mKeyguardStatusBarViewController.setDozing(mDozing);
if (dozing) {
@@ -3862,28 +3881,37 @@ public final class NotificationPanelViewController extends PanelViewController {
}
/**
- * Starts fold to AOD animation
+ * Starts fold to AOD animation.
+ *
+ * @param startAction invoked when the animation starts.
+ * @param endAction invoked when the animation finishes, also if it was cancelled.
+ * @param cancelAction invoked when the animation is cancelled, before endAction.
*/
- public void startFoldToAodAnimation(Runnable endAction) {
+ public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
+ Runnable cancelAction) {
mView.animate()
- .translationX(0)
- .alpha(1f)
- .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
- .setInterpolator(EMPHASIZED_DECELERATE)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- endAction.run();
- }
+ .translationX(0)
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+ .setInterpolator(EMPHASIZED_DECELERATE)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ startAction.run();
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- endAction.run();
- }
- })
- .start();
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelAction.run();
+ }
- mKeyguardStatusViewController.animateFoldToAod();
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endAction.run();
+ }
+ }).setUpdateListener(anim -> {
+ mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+ }).start();
}
/**
@@ -4596,7 +4624,6 @@ public final class NotificationPanelViewController extends PanelViewController {
public void onDozeAmountChanged(float linearAmount, float amount) {
mInterpolatedDarkAmount = amount;
mLinearDarkAmount = linearAmount;
- mKeyguardStatusViewController.setDarkAmount(mInterpolatedDarkAmount);
mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
positionClockAndNotifications();
}
@@ -4706,11 +4733,8 @@ public final class NotificationPanelViewController extends PanelViewController {
updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
- // Update Clock Pivot
- mKeyguardStatusViewController.setPivotX(((float) mView.getWidth()) / 2f);
- mKeyguardStatusViewController.setPivotY(
- (FONT_HEIGHT - CAP_HEIGHT) / 2048f
- * mKeyguardStatusViewController.getClockTextSize());
+ // Update Clock Pivot (used by anti-burnin transformations)
+ mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
// Calculate quick setting heights.
int oldMaxHeight = mQsMaxExpansionHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 3b3b5a2d05f9..cb414ba3550f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -50,16 +50,6 @@ public interface NotificationPresenter extends ExpandableNotificationRow.OnExpan
boolean isDeviceInVrMode();
/**
- * Updates the visual representation of the notifications.
- */
- void updateNotificationViews(String reason);
-
- /**
- * Called when the row states are updated by {@link NotificationViewHierarchyManager}.
- */
- void onUpdateRowStates();
-
- /**
* @return true if the shade is collapsing.
*/
boolean isCollapsing();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
deleted file mode 100644
index 054543c7d2b1..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ /dev/null
@@ -1,631 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
-import com.android.systemui.statusbar.notification.collection.render.NotifStats;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.Assert;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Optional;
-import java.util.Stack;
-
-/**
- * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
- * on their group structure. For example, if a notification becomes bundled with another,
- * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
- * tell NotificationListContainer which notifications to display, and inform it of changes to those
- * notifications that might affect their display.
- */
-public class NotificationViewHierarchyManager implements DynamicPrivacyController.Listener {
- private static final String TAG = "NotificationViewHierarchyManager";
-
- private final Handler mHandler;
-
- /**
- * Re-usable map of top-level notifications to their sorted children if any.
- * If the top-level notification doesn't have children, its key will still exist in this map
- * with its value explicitly set to null.
- */
- private final HashMap<NotificationEntry, List<NotificationEntry>> mTmpChildOrderMap =
- new HashMap<>();
-
- // Dependencies:
- private final DynamicChildBindController mDynamicChildBindController;
- private final FeatureFlags mFeatureFlags;
- protected final NotificationLockscreenUserManager mLockscreenUserManager;
- protected final NotificationGroupManagerLegacy mGroupManager;
- protected final VisualStabilityManager mVisualStabilityManager;
- private final SysuiStatusBarStateController mStatusBarStateController;
- private final NotificationEntryManager mEntryManager;
- private final LowPriorityInflationHelper mLowPriorityInflationHelper;
-
- /**
- * {@code true} if notifications not part of a group should by default be rendered in their
- * expanded state. If {@code false}, then only the first notification will be expanded if
- * possible.
- */
- private final boolean mAlwaysExpandNonGroupedNotification;
- private final Optional<Bubbles> mBubblesOptional;
- private final DynamicPrivacyController mDynamicPrivacyController;
- private final KeyguardBypassController mBypassController;
- private final NotifPipelineFlags mNotifPipelineFlags;
- private AssistantFeedbackController mAssistantFeedbackController;
- private final KeyguardStateController mKeyguardStateController;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final Context mContext;
-
- private NotificationPresenter mPresenter;
- private NotifStackController mStackController;
- private NotificationListContainer mListContainer;
-
- // Used to help track down re-entrant calls to our update methods, which will cause bugs.
- private boolean mPerformingUpdate;
- // Hack to get around re-entrant call in onDynamicPrivacyChanged() until we can track down
- // the problem.
- private boolean mIsHandleDynamicPrivacyChangeScheduled;
-
- /**
- * Injected constructor. See {@link CentralSurfacesModule}.
- */
- public NotificationViewHierarchyManager(
- Context context,
- @Main Handler mainHandler,
- FeatureFlags featureFlags,
- NotificationLockscreenUserManager notificationLockscreenUserManager,
- NotificationGroupManagerLegacy groupManager,
- VisualStabilityManager visualStabilityManager,
- StatusBarStateController statusBarStateController,
- NotificationEntryManager notificationEntryManager,
- KeyguardBypassController bypassController,
- Optional<Bubbles> bubblesOptional,
- DynamicPrivacyController privacyController,
- DynamicChildBindController dynamicChildBindController,
- LowPriorityInflationHelper lowPriorityInflationHelper,
- AssistantFeedbackController assistantFeedbackController,
- NotifPipelineFlags notifPipelineFlags,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardStateController keyguardStateController) {
- mContext = context;
- mHandler = mainHandler;
- mFeatureFlags = featureFlags;
- mLockscreenUserManager = notificationLockscreenUserManager;
- mBypassController = bypassController;
- mGroupManager = groupManager;
- mVisualStabilityManager = visualStabilityManager;
- mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
- mEntryManager = notificationEntryManager;
- mNotifPipelineFlags = notifPipelineFlags;
- Resources res = context.getResources();
- mAlwaysExpandNonGroupedNotification =
- res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
- mBubblesOptional = bubblesOptional;
- mDynamicPrivacyController = privacyController;
- mDynamicChildBindController = dynamicChildBindController;
- mLowPriorityInflationHelper = lowPriorityInflationHelper;
- mAssistantFeedbackController = assistantFeedbackController;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mKeyguardStateController = keyguardStateController;
- }
-
- public void setUpWithPresenter(NotificationPresenter presenter,
- NotifStackController stackController,
- NotificationListContainer listContainer) {
- mPresenter = presenter;
- mStackController = stackController;
- mListContainer = listContainer;
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mDynamicPrivacyController.addListener(this);
- }
- }
-
- /**
- * Updates the visual representation of the notifications.
- */
- //TODO: Rewrite this to focus on Entries, or some other data object instead of views
- public void updateNotificationViews() {
- Assert.isMainThread();
- if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
- return;
- }
- Trace.beginSection("NotificationViewHierarchyManager.updateNotificationViews");
-
- beginUpdate();
-
- boolean dynamicallyUnlocked = mDynamicPrivacyController.isDynamicallyUnlocked()
- && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
- && mKeyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
- KeyguardUpdateMonitor.getCurrentUser()))
- && !mKeyguardStateController.isKeyguardGoingAway();
- List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
- ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
- final int N = activeNotifications.size();
- for (int i = 0; i < N; i++) {
- NotificationEntry ent = activeNotifications.get(i);
- if (shouldSuppressActiveNotification(ent)) {
- continue;
- }
-
- int userId = ent.getSbn().getUserId();
-
- // Display public version of the notification if we need to redact.
- // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
- // We can probably move some of this code there.
- int currentUserId = mLockscreenUserManager.getCurrentUserId();
- boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(currentUserId);
- boolean userPublic = devicePublic
- || mLockscreenUserManager.isLockscreenPublicMode(userId);
- if (userPublic && dynamicallyUnlocked
- && (userId == currentUserId || userId == UserHandle.USER_ALL
- || !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) {
- userPublic = false;
- }
- boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
- boolean sensitive = userPublic && needsRedaction;
- boolean deviceSensitive = devicePublic
- && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
- currentUserId);
- ent.setSensitive(sensitive, deviceSensitive);
- ent.getRow().setNeedsRedaction(needsRedaction);
- mLowPriorityInflationHelper.recheckLowPriorityViewAndInflate(ent, ent.getRow());
- boolean isChildInGroup = mGroupManager.isChildInGroup(ent);
-
- boolean groupChangesAllowed =
- mVisualStabilityManager.areGroupChangesAllowed() // user isn't looking at notifs
- || !ent.hasFinishedInitialization(); // notif recently added
-
- NotificationEntry parent = mGroupManager.getGroupSummary(ent);
- if (!groupChangesAllowed) {
- // We don't to change groups while the user is looking at them
- boolean wasChildInGroup = ent.isChildInGroup();
- if (isChildInGroup && !wasChildInGroup) {
- isChildInGroup = wasChildInGroup;
- mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
- false /* persistent */);
- } else if (!isChildInGroup && wasChildInGroup) {
- // We allow grouping changes if the group was collapsed
- if (mGroupManager.isLogicalGroupExpanded(ent.getSbn())) {
- isChildInGroup = wasChildInGroup;
- parent = ent.getRow().getNotificationParent().getEntry();
- mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
- false /* persistent */);
- }
- }
- }
-
- if (isChildInGroup) {
- List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent);
- if (orderedChildren == null) {
- orderedChildren = new ArrayList<>();
- mTmpChildOrderMap.put(parent, orderedChildren);
- }
- orderedChildren.add(ent);
- } else {
- // Top-level notif (either a summary or single notification)
-
- // A child may have already added its summary to mTmpChildOrderMap with a
- // list of children. This can happen since there's no guarantee summaries are
- // sorted before its children.
- if (!mTmpChildOrderMap.containsKey(ent)) {
- // mTmpChildOrderMap's keyset is used to iterate through all entries, so it's
- // necessary to add each top-level notif as a key
- mTmpChildOrderMap.put(ent, null);
- }
- toShow.add(ent.getRow());
- }
-
- }
-
- ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>();
- for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
- View child = mListContainer.getContainerChildAt(i);
- if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-
- // Blocking helper is effectively a detached view. Don't bother removing it from the
- // layout.
- if (!row.isBlockingHelperShowing()) {
- viewsToRemove.add((ExpandableNotificationRow) child);
- }
- }
- }
-
- for (ExpandableNotificationRow viewToRemove : viewsToRemove) {
- NotificationEntry entry = viewToRemove.getEntry();
- if (mEntryManager.getPendingOrActiveNotif(entry.getKey()) != null
- && !shouldSuppressActiveNotification(entry)) {
- // we are only transferring this notification to its parent, don't generate an
- // animation. If the notification is suppressed, this isn't a transfer.
- mListContainer.setChildTransferInProgress(true);
- }
- if (viewToRemove.isSummaryWithChildren()) {
- viewToRemove.removeAllChildren();
- }
- mListContainer.removeContainerView(viewToRemove);
- mListContainer.setChildTransferInProgress(false);
- }
-
- removeNotificationChildren();
-
- for (int i = 0; i < toShow.size(); i++) {
- View v = toShow.get(i);
- if (v.getParent() == null) {
- mVisualStabilityManager.notifyViewAddition(v);
- mListContainer.addContainerView(v);
- } else if (!mListContainer.containsView(v)) {
- // the view is added somewhere else. Let's make sure
- // the ordering works properly below, by excluding these
- toShow.remove(v);
- i--;
- }
- }
-
- addNotificationChildrenAndSort();
-
- // So after all this work notifications still aren't sorted correctly.
- // Let's do that now by advancing through toShow and mListContainer in
- // lock-step, making sure mListContainer matches what we see in toShow.
- int j = 0;
- for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
- View child = mListContainer.getContainerChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
- if (((ExpandableNotificationRow) child).isBlockingHelperShowing()) {
- // Don't count/reorder notifications that are showing the blocking helper!
- continue;
- }
-
- ExpandableNotificationRow targetChild = toShow.get(j);
- if (child != targetChild) {
- // Oops, wrong notification at this position. Put the right one
- // here and advance both lists.
- if (mVisualStabilityManager.canReorderNotification(targetChild)) {
- mListContainer.changeViewPosition(targetChild, i);
- } else {
- mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager,
- false /* persistent */);
- }
- }
- j++;
-
- }
-
- mDynamicChildBindController.updateContentViews(mTmpChildOrderMap);
- mVisualStabilityManager.onReorderingFinished();
- // clear the map again for the next usage
- mTmpChildOrderMap.clear();
-
- updateRowStatesInternal();
- updateNotifStats();
-
- mListContainer.onNotificationViewUpdateFinished();
-
- endUpdate();
- Trace.endSection();
- }
-
- /**
- * In the spirit of unidirectional data flow, calculate this information when the notification
- * views are updated, and set it once, speeding up lookups later.
- * This is analogous to logic in the
- * {@link com.android.systemui.statusbar.notification.collection.coordinator.StackCoordinator}
- */
- private void updateNotifStats() {
- Trace.beginSection("NotificationViewHierarchyManager.updateNotifStats");
- boolean hasNonClearableAlertingNotifs = false;
- boolean hasClearableAlertingNotifs = false;
- boolean hasNonClearableSilentNotifs = false;
- boolean hasClearableSilentNotifs = false;
- final int childCount = mListContainer.getContainerChildCount();
- int visibleTopLevelEntries = 0;
- for (int i = 0; i < childCount; i++) {
- View child = mListContainer.getContainerChildAt(i);
- if (child == null || child.getVisibility() == View.GONE) {
- continue;
- }
- if (!(child instanceof ExpandableNotificationRow)) {
- continue;
- }
- final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- boolean isSilent = row.getEntry().getBucket() == BUCKET_SILENT;
- // NOTE: NotificationEntry.isClearable() will internally check group children to ensure
- // the group itself definitively clearable.
- boolean isClearable = row.getEntry().isClearable();
- visibleTopLevelEntries++;
- if (isSilent) {
- if (isClearable) {
- hasClearableSilentNotifs = true;
- } else { // !isClearable
- hasNonClearableSilentNotifs = true;
- }
- } else { // !isSilent
- if (isClearable) {
- hasClearableAlertingNotifs = true;
- } else { // !isClearable
- hasNonClearableAlertingNotifs = true;
- }
- }
- }
- mStackController.setNotifStats(new NotifStats(
- visibleTopLevelEntries /* numActiveNotifs */,
- hasNonClearableAlertingNotifs /* hasNonClearableAlertingNotifs */,
- hasClearableAlertingNotifs /* hasClearableAlertingNotifs */,
- hasNonClearableSilentNotifs /* hasNonClearableSilentNotifs */,
- hasClearableSilentNotifs /* hasClearableSilentNotifs */
- ));
- Trace.endSection();
- }
-
- /**
- * Should a notification entry from the active list be suppressed and not show?
- */
- private boolean shouldSuppressActiveNotification(NotificationEntry ent) {
- final boolean isBubbleNotificationSuppressedFromShade = mBubblesOptional.isPresent()
- && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(
- ent.getKey(), ent.getSbn().getGroupKey());
- if (ent.isRowDismissed() || ent.isRowRemoved()
- || isBubbleNotificationSuppressedFromShade) {
- // we want to suppress removed notifications because they could
- // temporarily become children if they were isolated before.
- return true;
- }
- return false;
- }
-
- private void addNotificationChildrenAndSort() {
- // Let's now add all notification children which are missing
- boolean orderChanged = false;
- ArrayList<ExpandableNotificationRow> orderedRows = new ArrayList<>();
- for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
- View view = mListContainer.getContainerChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getAttachedChildren();
- List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
- if (orderedChildren == null) {
- // Not a group
- continue;
- }
- parent.setUntruncatedChildCount(orderedChildren.size());
- for (int childIndex = 0; childIndex < orderedChildren.size(); childIndex++) {
- ExpandableNotificationRow childView = orderedChildren.get(childIndex).getRow();
- if (children == null || !children.contains(childView)) {
- if (childView.getParent() != null) {
- Log.wtf(TAG, "trying to add a notification child that already has "
- + "a parent. class:" + childView.getParent().getClass()
- + "\n child: " + childView);
- // This shouldn't happen. We can recover by removing it though.
- ((ViewGroup) childView.getParent()).removeView(childView);
- }
- mVisualStabilityManager.notifyViewAddition(childView);
- parent.addChildNotification(childView, childIndex);
- mListContainer.notifyGroupChildAdded(childView);
- }
- orderedRows.add(childView);
- }
-
- // Finally after removing and adding has been performed we can apply the order.
- orderChanged |= parent.applyChildOrder(orderedRows, mVisualStabilityManager,
- mEntryManager);
- orderedRows.clear();
- }
- if (orderChanged) {
- mListContainer.generateChildOrderChangedEvent();
- }
- }
-
- private void removeNotificationChildren() {
- // First let's remove all children which don't belong in the parents
- ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
- for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
- View view = mListContainer.getContainerChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getAttachedChildren();
- List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
-
- if (children != null) {
- toRemove.clear();
- for (ExpandableNotificationRow childRow : children) {
- if ((orderedChildren == null
- || !orderedChildren.contains(childRow.getEntry()))
- && !childRow.keepInParent()) {
- toRemove.add(childRow);
- }
- }
- for (ExpandableNotificationRow remove : toRemove) {
- parent.removeChildNotification(remove);
- if (mEntryManager.getActiveNotificationUnfiltered(
- remove.getEntry().getSbn().getKey()) == null) {
- // We only want to add an animation if the view is completely removed
- // otherwise it's just a transfer
- mListContainer.notifyGroupChildRemoved(remove,
- parent.getChildrenContainer());
- }
- }
- }
- }
- }
-
- /**
- * Updates expanded, dimmed and locked states of notification rows.
- */
- public void updateRowStates() {
- Assert.isMainThread();
- if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
- return;
- }
-
- beginUpdate();
- updateRowStatesInternal();
- endUpdate();
- }
-
- private void updateRowStatesInternal() {
- Trace.beginSection("NotificationViewHierarchyManager.updateRowStates");
- final int N = mListContainer.getContainerChildCount();
-
- int visibleNotifications = 0;
- boolean onKeyguard =
- mStatusBarStateController.getCurrentOrUpcomingState() == StatusBarState.KEYGUARD;
- Stack<ExpandableNotificationRow> stack = new Stack<>();
- for (int i = N - 1; i >= 0; i--) {
- View child = mListContainer.getContainerChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- continue;
- }
- stack.push((ExpandableNotificationRow) child);
- }
- while(!stack.isEmpty()) {
- ExpandableNotificationRow row = stack.pop();
- NotificationEntry entry = row.getEntry();
- boolean isChildNotification = mGroupManager.isChildInGroup(entry);
-
- if (!onKeyguard) {
- // If mAlwaysExpandNonGroupedNotification is false, then only expand the
- // very first notification and if it's not a child of grouped notifications.
- row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
- || (visibleNotifications == 0 && !isChildNotification
- && !row.isLowPriority()));
- }
-
- int userId = entry.getSbn().getUserId();
- boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
- entry.getSbn()) && !entry.isRowRemoved();
- boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry);
- if (!showOnKeyguard) {
- // min priority notifications should show if their summary is showing
- if (mGroupManager.isChildInGroup(entry)) {
- NotificationEntry summary = mGroupManager.getLogicalGroupSummary(entry);
- if (summary != null && mLockscreenUserManager.shouldShowOnKeyguard(summary)) {
- showOnKeyguard = true;
- }
- }
- }
- if (suppressedSummary
- || mLockscreenUserManager.shouldHideNotifications(userId)
- || (onKeyguard && !showOnKeyguard)) {
- entry.getRow().setVisibility(View.GONE);
- } else {
- boolean wasGone = entry.getRow().getVisibility() == View.GONE;
- if (wasGone) {
- entry.getRow().setVisibility(View.VISIBLE);
- }
- if (!isChildNotification && !entry.getRow().isRemoved()) {
- if (wasGone) {
- // notify the scroller of a child addition
- mListContainer.generateAddAnimation(entry.getRow(),
- !showOnKeyguard /* fromMoreCard */);
- }
- visibleNotifications++;
- }
- }
- if (row.isSummaryWithChildren()) {
- List<ExpandableNotificationRow> notificationChildren =
- row.getAttachedChildren();
- int size = notificationChildren.size();
- for (int i = size - 1; i >= 0; i--) {
- stack.push(notificationChildren.get(i));
- }
- }
- row.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry));
- row.setLastAudiblyAlertedMs(entry.getLastAudiblyAlertedMs());
- }
-
- Trace.beginSection("NotificationPresenter#onUpdateRowStates");
- mPresenter.onUpdateRowStates();
- Trace.endSection();
- Trace.endSection();
- }
-
- @Override
- public void onDynamicPrivacyChanged() {
- mNotifPipelineFlags.assertLegacyPipelineEnabled();
- if (mPerformingUpdate) {
- Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call");
- }
- // This listener can be called from updateNotificationViews() via a convoluted listener
- // chain, so we post here to prevent a re-entrant call. See b/136186188
- // TODO: Refactor away the need for this
- if (!mIsHandleDynamicPrivacyChangeScheduled) {
- mIsHandleDynamicPrivacyChangeScheduled = true;
- mHandler.post(this::onHandleDynamicPrivacyChanged);
- }
- }
-
- private void onHandleDynamicPrivacyChanged() {
- mIsHandleDynamicPrivacyChangeScheduled = false;
- updateNotificationViews();
- }
-
- private void beginUpdate() {
- if (mPerformingUpdate) {
- Log.wtf(TAG, "Re-entrant code during update", new Exception());
- }
- mPerformingUpdate = true;
- }
-
- private void endUpdate() {
- if (!mPerformingUpdate) {
- Log.wtf(TAG, "Manager state has become desynced", new Exception());
- }
- mPerformingUpdate = false;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 9e77dbc08c7a..48e34501ef59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -23,13 +23,11 @@ import android.service.dreams.IDreamManager;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -42,23 +40,16 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.RemoteInputNotificationRebuilder;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -73,13 +64,11 @@ import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
-import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -182,47 +171,6 @@ public interface CentralSurfacesDependenciesModule {
NotificationRemoteInputManager.Callback provideNotificationRemoteInputManagerCallback(
StatusBarRemoteInputCallback callbackImpl);
- /** */
- @SysUISingleton
- @Provides
- static NotificationViewHierarchyManager provideNotificationViewHierarchyManager(
- Context context,
- @Main Handler mainHandler,
- FeatureFlags featureFlags,
- NotificationLockscreenUserManager notificationLockscreenUserManager,
- NotificationGroupManagerLegacy groupManager,
- VisualStabilityManager visualStabilityManager,
- StatusBarStateController statusBarStateController,
- NotificationEntryManager notificationEntryManager,
- KeyguardBypassController bypassController,
- Optional<Bubbles> bubblesOptional,
- DynamicPrivacyController privacyController,
- DynamicChildBindController dynamicChildBindController,
- LowPriorityInflationHelper lowPriorityInflationHelper,
- AssistantFeedbackController assistantFeedbackController,
- NotifPipelineFlags notifPipelineFlags,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardStateController keyguardStateController) {
- return new NotificationViewHierarchyManager(
- context,
- mainHandler,
- featureFlags,
- notificationLockscreenUserManager,
- groupManager,
- visualStabilityManager,
- statusBarStateController,
- notificationEntryManager,
- bypassController,
- bubblesOptional,
- privacyController,
- dynamicChildBindController,
- lowPriorityInflationHelper,
- assistantFeedbackController,
- notifPipelineFlags,
- keyguardUpdateMonitor,
- keyguardStateController);
- }
-
/**
* Provides our instance of CommandQueue which is considered optional.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 0c6a91fe61f1..7fbdd35796c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -29,10 +29,6 @@ class NotifPipelineFlags @Inject constructor(
val featureFlags: FeatureFlags
) {
fun checkLegacyPipelineEnabled(): Boolean {
- if (!isNewPipelineEnabled()) {
- return true
- }
-
if (Compile.IS_DEBUG) {
Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show()
}
@@ -45,11 +41,6 @@ class NotifPipelineFlags @Inject constructor(
return false
}
- fun assertLegacyPipelineEnabled(): Unit =
- check(!isNewPipelineEnabled()) { "Old pipeline code running w/ new pipeline enabled" }
-
- fun isNewPipelineEnabled(): Boolean = true
-
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 852d5f7a16e6..7583a98df308 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -46,11 +46,9 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
-import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.NotificationUiAdjustment;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker;
@@ -113,7 +111,6 @@ public class NotificationEntryManager implements
private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
private final LeakDetector mLeakDetector;
private final IStatusBarService mStatusBarService;
- private final NotifLiveDataStoreImpl mNotifLiveDataStore;
private final DumpManager mDumpManager;
private final Executor mBgExecutor;
@@ -141,7 +138,6 @@ public class NotificationEntryManager implements
private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
private LegacyNotificationRanker mRanker = new LegacyNotificationRankerStub();
- private NotificationPresenter mPresenter;
private RankingMap mLatestRankingMap;
@VisibleForTesting
@@ -161,7 +157,6 @@ public class NotificationEntryManager implements
Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
LeakDetector leakDetector,
IStatusBarService statusBarService,
- NotifLiveDataStoreImpl notifLiveDataStore,
DumpManager dumpManager,
@Background Executor bgExecutor
) {
@@ -172,7 +167,6 @@ public class NotificationEntryManager implements
mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
mLeakDetector = leakDetector;
mStatusBarService = statusBarService;
- mNotifLiveDataStore = notifLiveDataStore;
mDumpManager = dumpManager;
mBgExecutor = bgExecutor;
}
@@ -250,10 +244,6 @@ public class NotificationEntryManager implements
mRemoveInterceptors.remove(interceptor);
}
- public void setUpWithPresenter(NotificationPresenter presenter) {
- mPresenter = presenter;
- }
-
/** Adds multiple {@link NotificationLifetimeExtender}s. */
public void addNotificationLifetimeExtenders(List<NotificationLifetimeExtender> extenders) {
for (NotificationLifetimeExtender extender : extenders) {
@@ -663,11 +653,6 @@ public class NotificationEntryManager implements
listener.onEntryBind(entry, notification);
}
- // Construct the expanded view.
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mNotificationRowBinderLazy.get().inflateViews(entry, null, mInflationCallback);
- }
-
mPendingNotifications.put(key, entry);
mLogger.logNotifAdded(entry.getKey());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
@@ -724,10 +709,6 @@ public class NotificationEntryManager implements
listener.onEntryUpdated(entry, fromSystem);
}
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mNotificationRowBinderLazy.get().inflateViews(entry, null, mInflationCallback);
- }
-
updateNotifications("updateNotificationInternal");
for (NotificationEntryListener listener : mNotificationEntryListeners) {
@@ -752,17 +733,7 @@ public class NotificationEntryManager implements
* @param reason why the notifications are updating
*/
public void updateNotifications(String reason) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
- return;
- }
- Trace.beginSection("NotificationEntryManager.updateNotifications");
- reapplyFilterAndSort(reason);
- if (mPresenter != null) {
- mPresenter.updateNotificationViews(reason);
- }
- mNotifLiveDataStore.setActiveNotifList(getVisibleNotifications());
- Trace.endSection();
+ mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
}
public void updateNotificationRanking(RankingMap rankingMap) {
@@ -939,26 +910,12 @@ public class NotificationEntryManager implements
/** Resorts / filters the current notification set with the current RankingMap */
public void reapplyFilterAndSort(String reason) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
- return;
- }
- Trace.beginSection("NotificationEntryManager.reapplyFilterAndSort");
- updateRankingAndSort(mRanker.getRankingMap(), reason);
- Trace.endSection();
+ mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
}
/** Calls to NotificationRankingManager and updates mSortedAndFiltered */
private void updateRankingAndSort(RankingMap rankingMap, String reason) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
- return;
- }
- Trace.beginSection("NotificationEntryManager.updateRankingAndSort");
- mSortedAndFiltered.clear();
- mSortedAndFiltered.addAll(mRanker.updateRanking(
- rankingMap, mActiveNotifications.values(), reason));
- Trace.endSection();
+ mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
}
/** dump the current active notification list. Called from CentralSurfaces */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 945bc72e3e81..9e5dab1152ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -68,9 +68,6 @@ data class ListAttachState private constructor(
*/
var stableIndex: Int = -1
- /** Access the index of the [section] or -1 if the entry does not have one */
- val sectionIndex: Int get() = section?.index ?: -1
-
/** Copies the state of another instance. */
fun clone(other: ListAttachState) {
parent = other.parent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 5198d82a86c7..075a0dc7555e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1039,25 +1039,22 @@ public class ShadeListBuilder implements Dumpable {
NotifSection currentSection = requireNonNull(notifList.get(0).getSection());
int sectionMemberIndex = 0;
for (int i = 0; i < notifList.size(); i++) {
- final ListEntry entry = notifList.get(i);
+ ListEntry entry = notifList.get(i);
NotifSection section = requireNonNull(entry.getSection());
if (section.getIndex() != currentSection.getIndex()) {
sectionMemberIndex = 0;
currentSection = section;
}
- entry.getAttachState().setStableIndex(sectionMemberIndex++);
+ entry.getAttachState().setStableIndex(sectionMemberIndex);
if (entry instanceof GroupEntry) {
- final GroupEntry parent = (GroupEntry) entry;
- final NotificationEntry summary = parent.getSummary();
- if (summary != null) {
- summary.getAttachState().setStableIndex(sectionMemberIndex++);
- }
- final List<NotificationEntry> children = parent.getChildren();
- for (int j = 0; j < children.size(); j++) {
- final NotificationEntry child = children.get(j);
- child.getAttachState().setStableIndex(sectionMemberIndex++);
+ GroupEntry parent = (GroupEntry) entry;
+ for (int j = 0; j < parent.getChildren().size(); j++) {
+ entry = parent.getChildren().get(j);
+ entry.getAttachState().setStableIndex(sectionMemberIndex);
+ sectionMemberIndex++;
}
}
+ sectionMemberIndex++;
}
}
@@ -1197,9 +1194,9 @@ public class ShadeListBuilder implements Dumpable {
o2.getSectionIndex());
if (cmp != 0) return cmp;
- cmp = Integer.compare(
- getStableOrderIndex(o1),
- getStableOrderIndex(o2));
+ int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
+ int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
+ cmp = Integer.compare(index1, index2);
if (cmp != 0) return cmp;
NotifComparator sectionComparator = getSectionComparator(o1, o2);
@@ -1213,32 +1210,31 @@ public class ShadeListBuilder implements Dumpable {
if (cmp != 0) return cmp;
}
- cmp = Integer.compare(
- o1.getRepresentativeEntry().getRanking().getRank(),
- o2.getRepresentativeEntry().getRanking().getRank());
+ final NotificationEntry rep1 = o1.getRepresentativeEntry();
+ final NotificationEntry rep2 = o2.getRepresentativeEntry();
+ cmp = rep1.getRanking().getRank() - rep2.getRanking().getRank();
if (cmp != 0) return cmp;
- cmp = -1 * Long.compare(
- o1.getRepresentativeEntry().getSbn().getNotification().when,
- o2.getRepresentativeEntry().getSbn().getNotification().when);
+ cmp = Long.compare(
+ rep2.getSbn().getNotification().when,
+ rep1.getSbn().getNotification().when);
return cmp;
};
private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
- int cmp = Integer.compare(
- getStableOrderIndex(o1),
- getStableOrderIndex(o2));
+ int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
+ int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
+ int cmp = Integer.compare(index1, index2);
if (cmp != 0) return cmp;
- cmp = Integer.compare(
- o1.getRepresentativeEntry().getRanking().getRank(),
- o2.getRepresentativeEntry().getRanking().getRank());
+ cmp = o1.getRepresentativeEntry().getRanking().getRank()
+ - o2.getRepresentativeEntry().getRanking().getRank();
if (cmp != 0) return cmp;
- cmp = -1 * Long.compare(
- o1.getRepresentativeEntry().getSbn().getNotification().when,
- o2.getRepresentativeEntry().getSbn().getNotification().when);
+ cmp = Long.compare(
+ o2.getRepresentativeEntry().getSbn().getNotification().when,
+ o1.getRepresentativeEntry().getSbn().getNotification().when);
return cmp;
};
@@ -1248,21 +1244,8 @@ public class ShadeListBuilder implements Dumpable {
*/
private boolean mForceReorderable = false;
- private int getStableOrderIndex(ListEntry entry) {
- if (mForceReorderable) {
- // this is used to determine if the list is correctly sorted
- return -1;
- }
- if (getStabilityManager().isEntryReorderingAllowed(entry)) {
- // let the stability manager constrain or allow reordering
- return -1;
- }
- if (entry.getAttachState().getSectionIndex()
- != entry.getPreviousAttachState().getSectionIndex()) {
- // stable index is only valid within the same section; otherwise we allow reordering
- return -1;
- }
- return entry.getPreviousAttachState().getStableIndex();
+ private boolean canReorder(ListEntry entry) {
+ return mForceReorderable || getStabilityManager().isEntryReorderingAllowed(entry);
}
private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 4c406e3ba0b4..d8dae5d23f42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -336,7 +336,7 @@ class ShadeListBuilderLogger @Inject constructor(
}
fun logPipelineRunSuppressed() =
- buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
+ buffer.log(TAG, INFO, {}, { "Suppressing pipeline run during animation." })
}
private const val TAG = "ShadeListBuilder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index fcf35bf66e67..fac234c58850 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -127,7 +127,6 @@ public interface NotificationsModule {
Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
LeakDetector leakDetector,
IStatusBarService statusBarService,
- NotifLiveDataStoreImpl notifLiveDataStore,
DumpManager dumpManager,
@Background Executor bgExecutor) {
return new NotificationEntryManager(
@@ -138,7 +137,6 @@ public interface NotificationsModule {
notificationRemoteInputManagerLazy,
leakDetector,
statusBarService,
- notifLiveDataStore,
dumpManager,
bgExecutor);
}
@@ -174,7 +172,6 @@ public interface NotificationsModule {
accessibilityManager,
highPriorityProvider,
notificationManager,
- notificationEntryManager,
peopleSpaceWidgetManager,
launcherApps,
shortcutManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index a72b3814d5ad..558fd62c78bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -37,7 +37,6 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -60,13 +59,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>();
private final StatusBarStateController mStatusBarStateController;
private final KeyguardStateController mKeyguardStateController;
- private final NotificationFilter mNotificationFilter;
private final ContentResolver mContentResolver;
private final PowerManager mPowerManager;
private final IDreamManager mDreamManager;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final BatteryController mBatteryController;
- private final ContentObserver mHeadsUpObserver;
private final HeadsUpManager mHeadsUpManager;
private final NotificationInterruptLogger mLogger;
private final NotifPipelineFlags mFlags;
@@ -81,7 +78,6 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
PowerManager powerManager,
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
- NotificationFilter notificationFilter,
BatteryController batteryController,
StatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
@@ -95,14 +91,13 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
mDreamManager = dreamManager;
mBatteryController = batteryController;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
- mNotificationFilter = notificationFilter;
mStatusBarStateController = statusBarStateController;
mKeyguardStateController = keyguardStateController;
mHeadsUpManager = headsUpManager;
mLogger = logger;
mFlags = flags;
mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
- mHeadsUpObserver = new ContentObserver(mainHandler) {
+ ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
@Override
public void onChange(boolean selfChange) {
boolean wasUsing = mUseHeadsUp;
@@ -125,12 +120,12 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
mContentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
true,
- mHeadsUpObserver);
+ headsUpObserver);
mContentResolver.registerContentObserver(
Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
- mHeadsUpObserver);
+ headsUpObserver);
}
- mHeadsUpObserver.onChange(true); // set up
+ headsUpObserver.onChange(true); // set up
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index c228af47aa0d..c4ff2599c2ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -21,7 +21,6 @@ import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import android.app.INotificationManager;
import android.app.NotificationChannel;
-import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
@@ -63,13 +62,11 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
-import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -114,7 +111,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
private NotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private NotificationListContainer mListContainer;
- private CheckSaveListener mCheckSaveListener;
private OnSettingsClickListener mOnSettingsClickListener;
@VisibleForTesting
protected String mKeyToRemoveOnGutsClosed;
@@ -131,7 +127,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
private final UserContextProvider mContextTracker;
private final UiEventLogger mUiEventLogger;
private final ShadeController mShadeController;
- private final AppWidgetManager mAppWidgetManager;
private NotifGutsViewListener mGutsListener;
/**
@@ -144,7 +139,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
- NotificationEntryManager notificationEntryManager,
PeopleSpaceWidgetManager peopleSpaceWidgetManager,
LauncherApps launcherApps,
ShortcutManager shortcutManager,
@@ -173,17 +167,15 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
mShadeController = shadeController;
- mAppWidgetManager = AppWidgetManager.getInstance(context);
dumpManager.registerDumpable(this);
}
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationListContainer listContainer,
- CheckSaveListener checkSave, OnSettingsClickListener onSettingsClick) {
+ OnSettingsClickListener onSettingsClick) {
mPresenter = presenter;
mListContainer = listContainer;
- mCheckSaveListener = checkSave;
mOnSettingsClickListener = onSettingsClick;
}
@@ -218,14 +210,11 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
}
private void startAppDetailsSettingsActivity(String packageName, final int appUid,
- final NotificationChannel channel, ExpandableNotificationRow row) {
+ ExpandableNotificationRow row) {
final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", packageName, null));
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
intent.putExtra(Settings.EXTRA_APP_UID, appUid);
- if (channel != null) {
- intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
- }
mNotificationActivityStarter.startNotificationGutsIntent(intent, appUid, row);
}
@@ -233,7 +222,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
ExpandableNotificationRow row) {
if (ops.contains(OP_SYSTEM_ALERT_WINDOW)) {
if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) {
- startAppDetailsSettingsActivity(pkg, uid, null, row);
+ startAppDetailsSettingsActivity(pkg, uid, row);
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION);
intent.setData(Uri.fromParts("package", pkg, null));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 1d3b7feb81c6..333f6b9cadb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -196,14 +196,12 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationShelfController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.PowerButtonReveal;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -515,7 +513,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
protected final NotificationEntryManager mEntryManager;
private final NotificationGutsManager mGutsManager;
private final NotificationLogger mNotificationLogger;
- private final NotificationViewHierarchyManager mViewHierarchyManager;
private final PanelExpansionStateManager mPanelExpansionStateManager;
private final KeyguardViewMediator mKeyguardViewMediator;
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
@@ -651,7 +648,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
private final Optional<Bubbles> mBubblesOptional;
private final Bubbles.BubbleExpandListener mBubbleExpandListener;
private final Optional<StartingSurface> mStartingSurfaceOptional;
- private final NotifPipelineFlags mNotifPipelineFlags;
private final ActivityIntentHelper mActivityIntentHelper;
private NotificationStackScrollLayoutController mStackScrollerController;
@@ -693,7 +689,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
NotificationGutsManager notificationGutsManager,
NotificationLogger notificationLogger,
NotificationInterruptStateProvider notificationInterruptStateProvider,
- NotificationViewHierarchyManager notificationViewHierarchyManager,
PanelExpansionStateManager panelExpansionStateManager,
KeyguardViewMediator keyguardViewMediator,
DisplayMetrics displayMetrics,
@@ -756,7 +751,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
WallpaperManager wallpaperManager,
Optional<StartingSurface> startingSurfaceOptional,
ActivityLaunchAnimator activityLaunchAnimator,
- NotifPipelineFlags notifPipelineFlags,
InteractionJankMonitor jankMonitor,
DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController,
@@ -783,7 +777,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
mGutsManager = notificationGutsManager;
mNotificationLogger = notificationLogger;
mNotificationInterruptStateProvider = notificationInterruptStateProvider;
- mViewHierarchyManager = notificationViewHierarchyManager;
mPanelExpansionStateManager = panelExpansionStateManager;
mKeyguardViewMediator = keyguardViewMediator;
mDisplayMetrics = displayMetrics;
@@ -844,7 +837,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
- mNotifPipelineFlags = notifPipelineFlags;
mDreamManager = dreamManager;
lockscreenShadeTransitionController.setCentralSurfaces(this);
statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 43a5451f4bb6..dc77d10486a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
@@ -57,6 +58,8 @@ import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsActivity;
import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
@@ -125,6 +128,9 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
};
+ @Nullable private KeyguardBottomAreaViewBinder.Binding mBinding;
+ private boolean mUsesBinder;
+
public KeyguardBottomAreaView(Context context) {
this(context, null);
}
@@ -142,13 +148,36 @@ public class KeyguardBottomAreaView extends FrameLayout {
super(context, attrs, defStyleAttr, defStyleRes);
}
- /** Initializes the {@link KeyguardBottomAreaView} with the given dependencies */
+ /**
+ * Initializes the view.
+ */
+ public void init(
+ final KeyguardBottomAreaViewModel viewModel,
+ final FalsingManager falsingManager) {
+ Log.i(TAG, System.identityHashCode(this) + " initialized with a binder");
+ mUsesBinder = true;
+ mBinding = KeyguardBottomAreaViewBinder.bind(this, viewModel, falsingManager);
+ }
+
+ /**
+ * Initializes the {@link KeyguardBottomAreaView} with the given dependencies
+ *
+ * @deprecated Use
+ * {@link #init(KeyguardBottomAreaViewModel, FalsingManager)} instead
+ */
+ @Deprecated
public void init(
FalsingManager falsingManager,
QuickAccessWalletController controller,
ControlsComponent controlsComponent,
QRCodeScannerController qrCodeScannerController) {
+ if (mUsesBinder) {
+ return;
+ }
+
+ Log.i(TAG, "initialized without a binder");
mFalsingManager = falsingManager;
+
mQuickAccessWalletController = controller;
mQuickAccessWalletController.setupWalletChangeObservers(
mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
@@ -174,6 +203,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
* another {@link KeyguardBottomAreaView}
*/
public void initFrom(KeyguardBottomAreaView oldBottomArea) {
+ if (mUsesBinder) {
+ return;
+ }
+
// if it exists, continue to use the original ambient indication container
// instead of the newly inflated one
if (mAmbientIndicationArea != null) {
@@ -201,6 +234,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ if (mUsesBinder) {
+ return;
+ }
+
mOverlayContainer = findViewById(R.id.overlay_container);
mWalletButton = findViewById(R.id.wallet_button);
mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button);
@@ -229,6 +266,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ if (mUsesBinder) {
+ return;
+ }
+
final IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
@@ -237,6 +278,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ if (mUsesBinder) {
+ return;
+ }
+
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
if (mQuickAccessWalletController != null) {
@@ -259,6 +304,13 @@ public class KeyguardBottomAreaView extends FrameLayout {
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ if (mUsesBinder) {
+ if (mBinding != null) {
+ mBinding.onConfigurationChanged();
+ }
+ return;
+ }
+
mIndicationBottomMargin = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_margin_bottom);
mBurnInYOffset = getResources().getDimensionPixelSize(
@@ -301,6 +353,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void updateWalletVisibility() {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mDozing
|| mQuickAccessWalletController == null
|| !mQuickAccessWalletController.isWalletEnabled()
@@ -318,6 +374,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void updateControlsVisibility() {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mControlsComponent == null) return;
mControlsButton.setImageResource(mControlsComponent.getTileImageId());
@@ -344,6 +404,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
public void setDarkAmount(float darkAmount) {
+ if (mUsesBinder) {
+ return;
+ }
+
if (darkAmount == mDarkAmount) {
return;
}
@@ -355,6 +419,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
* Returns a list of animators to use to animate the indication areas.
*/
public List<ViewPropertyAnimator> getIndicationAreaAnimators() {
+ if (mUsesBinder) {
+ return checkNotNull(mBinding).getIndicationAreaAnimators();
+ }
+
List<ViewPropertyAnimator> animators =
new ArrayList<>(mAmbientIndicationArea != null ? 2 : 1);
animators.add(mIndicationArea.animate());
@@ -394,6 +462,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
public void setDozing(boolean dozing, boolean animate) {
+ if (mUsesBinder) {
+ return;
+ }
+
mDozing = dozing;
updateWalletVisibility();
@@ -411,6 +483,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
public void dozeTimeTick() {
+ if (mUsesBinder) {
+ return;
+ }
+
int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */)
- mBurnInYOffset;
mIndicationArea.setTranslationY(burnInYOffset * mDarkAmount);
@@ -420,6 +496,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
public void setAntiBurnInOffsetX(int burnInXOffset) {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mBurnInXOffset == burnInXOffset) {
return;
}
@@ -435,6 +515,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
* action buttons. Does not set the alpha of the lock icon.
*/
public void setComponentAlphas(float alpha) {
+ if (mUsesBinder) {
+ return;
+ }
+
setImportantForAccessibility(
alpha == 0f
? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
@@ -461,6 +545,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void updateQRCodeButtonVisibility() {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mQuickAccessWalletController != null
&& mQuickAccessWalletController.isWalletEnabled()) {
// Don't enable if quick access wallet is enabled
@@ -481,6 +569,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void onQRCodeScannerClicked(View view) {
+ if (mUsesBinder) {
+ return;
+ }
+
Intent intent = mQRCodeScannerController.getIntent();
if (intent != null) {
try {
@@ -500,6 +592,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void updateAffordanceColors() {
+ if (mUsesBinder) {
+ return;
+ }
+
int iconColor = Utils.getColorAttrDefaultColor(
mContext,
com.android.internal.R.attr.textColorPrimary);
@@ -516,6 +612,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void onWalletClick(View v) {
+ if (mUsesBinder) {
+ return;
+ }
+
// More coming here; need to inform the user about how to proceed
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
@@ -531,6 +631,10 @@ public class KeyguardBottomAreaView extends FrameLayout {
}
private void onControlsClick(View v) {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index 70ec13b14abd..4496607a7db2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone
import android.annotation.ColorInt
-import android.graphics.Color
import android.graphics.Rect
import android.view.InsetsFlags
import android.view.ViewDebug
@@ -39,7 +38,13 @@ import javax.inject.Inject
class LetterboxAppearance(
@Appearance val appearance: Int,
val appearanceRegions: Array<AppearanceRegion>
-)
+) {
+ override fun toString(): String {
+ val appearanceString =
+ ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
+ return "LetterboxAppearance{$appearanceString, ${appearanceRegions.contentToString()}}"
+ }
+}
/**
* Responsible for calculating the [Appearance] and [AppearanceRegion] for the status bar when apps
@@ -51,6 +56,7 @@ class LetterboxAppearanceCalculator
constructor(
private val lightBarController: LightBarController,
private val dumpManager: DumpManager,
+ private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
) : OnStatusBarViewInitializedListener, CentralSurfacesComponent.Startable {
private var statusBarBoundsProvider: StatusBarBoundsProvider? = null
@@ -184,13 +190,11 @@ constructor(
@ColorInt
private fun outerLetterboxBackgroundColor(): Int {
- // TODO(b/238607453): retrieve this information from WindowManager.
- return Color.BLACK
+ return letterboxBackgroundProvider.letterboxBackgroundColor
}
private fun isOuterLetterboxMultiColored(): Boolean {
- // TODO(b/238607453): retrieve this information from WindowManager.
- return false
+ return letterboxBackgroundProvider.isLetterboxBackgroundMultiColored
}
private fun getEndSideIconsBounds(): Rect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
new file mode 100644
index 000000000000..96b9aca9c64c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.statusbar.phone
+
+import android.annotation.ColorInt
+import android.graphics.Color
+import android.os.RemoteException
+import android.view.IWindowManager
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/** Responsible for providing information about the background of letterboxed apps. */
+@CentralSurfacesScope
+class LetterboxBackgroundProvider
+@Inject
+constructor(
+ private val windowManager: IWindowManager,
+ @Background private val backgroundExecutor: Executor,
+ private val dumpManager: DumpManager,
+) : CentralSurfacesComponent.Startable, Dumpable {
+
+ @ColorInt
+ var letterboxBackgroundColor: Int = Color.BLACK
+ private set
+
+ var isLetterboxBackgroundMultiColored: Boolean = false
+ private set
+
+ override fun start() {
+ dumpManager.registerDumpable(javaClass.simpleName, this)
+
+ // Using a background executor, as binder calls to IWindowManager are blocking
+ backgroundExecutor.execute {
+ try {
+ isLetterboxBackgroundMultiColored = windowManager.isLetterboxBackgroundMultiColored
+ letterboxBackgroundColor = windowManager.letterboxBackgroundColorInArgb
+ } catch (e: RemoteException) {
+ e.rethrowFromSystemServer()
+ }
+ }
+ }
+
+ override fun stop() {
+ dumpManager.unregisterDumpable(javaClass.simpleName)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println(
+ """
+ letterboxBackgroundColor: ${Color.valueOf(letterboxBackgroundColor)}
+ isLetterboxBackgroundMultiColored: $isLetterboxBackgroundMultiColored
+ """.trimIndent())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index db8f4ee9b71d..6996ee75ef30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -41,7 +41,6 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -50,7 +49,6 @@ import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
@@ -82,7 +80,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
private final ActivityStarter mActivityStarter;
private final KeyguardStateController mKeyguardStateController;
- private final NotificationViewHierarchyManager mViewHierarchyManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final SysuiStatusBarStateController mStatusBarStateController;
private final NotifShadeEventSource mNotifShadeEventSource;
@@ -94,10 +91,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
private final HeadsUpManagerPhone mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
- private final ScrimController mScrimController;
private final KeyguardIndicationController mKeyguardIndicationController;
private final CentralSurfaces mCentralSurfaces;
- private final com.android.systemui.shade.ShadeController mShadeController;
private final LockscreenShadeTransitionController mShadeTransitionController;
private final CommandQueue mCommandQueue;
@@ -112,23 +107,21 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
protected boolean mVrMode;
@Inject
- StatusBarNotificationPresenter(Context context,
+ StatusBarNotificationPresenter(
+ Context context,
NotificationPanelViewController panel,
HeadsUpManagerPhone headsUp,
NotificationShadeWindowView statusBarWindow,
ActivityStarter activityStarter,
NotificationStackScrollLayoutController stackScrollerController,
DozeScrimController dozeScrimController,
- ScrimController scrimController,
NotificationShadeWindowController notificationShadeWindowController,
DynamicPrivacyController dynamicPrivacyController,
KeyguardStateController keyguardStateController,
KeyguardIndicationController keyguardIndicationController,
CentralSurfaces centralSurfaces,
- ShadeController shadeController,
LockscreenShadeTransitionController shadeTransitionController,
CommandQueue commandQueue,
- NotificationViewHierarchyManager notificationViewHierarchyManager,
NotificationLockscreenUserManager lockscreenUserManager,
SysuiStatusBarStateController sysuiStatusBarStateController,
NotifShadeEventSource notifShadeEventSource,
@@ -149,10 +142,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
mKeyguardIndicationController = keyguardIndicationController;
// TODO: use KeyguardStateController#isOccluded to remove this dependency
mCentralSurfaces = centralSurfaces;
- mShadeController = shadeController;
mShadeTransitionController = shadeTransitionController;
mCommandQueue = commandQueue;
- mViewHierarchyManager = notificationViewHierarchyManager;
mLockscreenUserManager = lockscreenUserManager;
mStatusBarStateController = sysuiStatusBarStateController;
mNotifShadeEventSource = notifShadeEventSource;
@@ -166,7 +157,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
R.id.notification_container_parent));
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mDozeScrimController = dozeScrimController;
- mScrimController = scrimController;
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -187,16 +177,13 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
initController.addPostInitTask(() -> {
mKeyguardIndicationController.init();
- mViewHierarchyManager.setUpWithPresenter(this,
- stackScrollerController.getNotifStackController(),
- mNotifListContainer);
mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
mLockscreenUserManager.setUpWithPresenter(this);
mMediaManager.setUpWithPresenter(this);
mGutsManager.setUpWithPresenter(
- this, mNotifListContainer, mCheckSaveListener, mOnSettingsClickListener);
+ this, mNotifListContainer, mOnSettingsClickListener);
// ForegroundServiceNotificationListener adds its listener in its constructor
// but we need to request it here in order for it to be instantiated.
// TODO: figure out how to do this correctly once Dependency.get() is gone.
@@ -233,24 +220,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
}
@Override
- public void updateNotificationViews(final String reason) {
- if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
- return;
- }
- // The function updateRowStates depends on both of these being non-null, so check them here.
- // We may be called before they are set from DeviceProvisionedController's callback.
- if (mScrimController == null) return;
-
- // Do not modify the notifications during collapse.
- if (isCollapsing()) {
- mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
- return;
- }
- mViewHierarchyManager.updateNotificationViews();
- mNotificationPanel.updateNotificationViews(reason);
- }
-
- @Override
public void onUserSwitched(int newUserId) {
// Begin old BaseStatusBar.userSwitched
mHeadsUpManager.setUser(newUserId);
@@ -304,11 +273,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
}
@Override
- public void onUpdateRowStates() {
- mNotificationPanel.onUpdateRowStates();
- }
-
- @Override
public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
boolean nowExpanded) {
mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
index d57e6a791489..b0532d773f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone.dagger;
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
+import com.android.systemui.statusbar.phone.LetterboxBackgroundProvider;
import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
import java.util.Set;
@@ -40,4 +41,9 @@ interface CentralSurfacesStartableModule {
@IntoSet
CentralSurfacesComponent.Startable sysBarAttrsListener(
SystemBarAttributesListener systemBarAttributesListener);
+
+ @Binds
+ @IntoSet
+ CentralSurfacesComponent.Startable letterboxBgProvider(
+ LetterboxBackgroundProvider letterboxBackgroundProvider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 0848729781bd..fb26600f8256 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -439,7 +439,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
state |= DISABLE_CLOCK;
}
-
if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) {
if (mNetworkController.hasEmergencyCryptKeeperText()) {
state |= DISABLE_NOTIFICATION_ICONS;
@@ -449,13 +448,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
}
- // The shelf will be hidden when dozing with a custom clock, we must show notification
- // icons in this occasion.
- if (mStatusBarStateController.isDozing()
- && mNotificationPanelViewController.hasCustomClock()) {
- state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
- }
-
if (mOngoingCallController.hasOngoingCall()) {
state &= ~DISABLE_ONGOING_CALL_CHIP;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
index bd6cf9a1d101..a8419145d6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline
+import android.content.Context
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -27,4 +29,18 @@ import javax.inject.Inject
* displayed in the RHS of the status bar.
*/
@SysUISingleton
-class ConnectivityInfoProcessor @Inject constructor()
+class ConnectivityInfoProcessor @Inject constructor(
+ context: Context,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
+) : CoreStartable(context) {
+ override fun start() {
+ if (statusBarPipelineFlags.isNewPipelineEnabled()) {
+ init()
+ }
+ }
+
+ /** Initializes this processor and everything it depends on. */
+ private fun init() {
+ // TODO(b/238425913): Register all the connectivity callbacks here.
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
new file mode 100644
index 000000000000..f88e9d67d25d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.statusbar.pipeline
+
+import android.net.Network
+import android.net.NetworkCapabilities
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import javax.inject.Inject
+
+@SysUISingleton
+class ConnectivityPipelineLogger @Inject constructor(
+ @StatusBarConnectivityLog private val buffer: LogBuffer,
+) {
+ fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ int1 = network.getNetId()
+ str1 = networkCapabilities.toString()
+ },
+ {
+ "onCapabilitiesChanged: net=$int1 capabilities=$str1"
+ }
+ )
+ }
+
+ fun logOnLost(network: Network) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ int1 = network.getNetId()
+ },
+ {
+ "onLost: net=$int1"
+ }
+ )
+ }
+}
+
+private const val TAG = "SbConnectivityPipeline"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
new file mode 100644
index 000000000000..589cdb8182cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.statusbar.pipeline
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+/** All flagging methods related to the new status bar pipeline (see b/238425913). */
+@SysUISingleton
+class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+ /**
+ * Returns true if we should run the new pipeline.
+ *
+ * TODO(b/238425913): We may want to split this out into:
+ * (1) isNewPipelineLoggingEnabled(), where the new pipeline runs and logs its decisions but
+ * doesn't change the UI at all.
+ * (2) isNewPipelineEnabled(), where the new pipeline runs and does change the UI (and the old
+ * pipeline doesn't change the UI).
+ */
+ fun isNewPipelineEnabled(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 734bd2d8e5b3..771bb0c11fb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,27 +16,18 @@
package com.android.systemui.statusbar.pipeline.dagger
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
-import dagger.Lazy
+import dagger.Binds
import dagger.Module
-import dagger.Provides
-import java.util.Optional
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
@Module
-class StatusBarPipelineModule {
- @Provides
- @SysUISingleton
- fun provideConnectivityInfoProcessor(
- featureFlags: FeatureFlags,
- processorLazy: Lazy<ConnectivityInfoProcessor>
- ): Optional<ConnectivityInfoProcessor> {
- return if (featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)) {
- Optional.of(processorLazy.get())
- } else {
- Optional.empty()
- }
- }
+abstract class StatusBarPipelineModule {
+ /** Inject into ConnectivityInfoProcessor. */
+ @Binds
+ @IntoMap
+ @ClassKey(ConnectivityInfoProcessor::class)
+ abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
new file mode 100644
index 000000000000..e5980c3693ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.pipeline.repository
+
+import android.annotation.SuppressLint
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository that contains all relevant [NetworkCapabilites] for the current networks */
+@SysUISingleton
+class NetworkCapabilitiesRepo @Inject constructor(
+ connectivityManager: ConnectivityManager,
+ @Application scope: CoroutineScope,
+ logger: ConnectivityPipelineLogger,
+) {
+ @SuppressLint("MissingPermission")
+ val dataStream: StateFlow<Map<Int, NetworkCapabilityInfo>> = run {
+ var state = emptyMap<Int, NetworkCapabilityInfo>()
+ callbackFlow {
+ val networkRequest: NetworkRequest =
+ NetworkRequest.Builder()
+ .clearCapabilities()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build()
+ val callback =
+ // TODO (b/240569788): log these using [LogBuffer]
+ object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ logger.logOnCapabilitiesChanged(network, networkCapabilities)
+ state =
+ state.toMutableMap().also {
+ it[network.getNetId()] =
+ NetworkCapabilityInfo(network, networkCapabilities)
+ }
+ trySend(state)
+ }
+
+ override fun onLost(network: Network) {
+ logger.logOnLost(network)
+ state = state.toMutableMap().also { it.remove(network.getNetId()) }
+ trySend(state)
+ }
+ }
+ connectivityManager.registerNetworkCallback(networkRequest, callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .stateIn(scope, started = Lazily, initialValue = state)
+ }
+}
+
+/** contains info about network capabilities. */
+data class NetworkCapabilityInfo(
+ val network: Network,
+ val capabilities: NetworkCapabilities,
+)
+
+private const val TAG = "ConnectivityRepository"
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index d6dfceac7a63..8f127fd1fcb4 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -22,11 +22,12 @@ import android.os.Handler
import android.os.PowerManager
import android.provider.Settings
import androidx.core.view.OneShotPreDrawListener
+import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.statusbar.phone.ScreenOffAnimation
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ScreenOffAnimation
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
import com.android.systemui.util.settings.GlobalSettings
@@ -47,7 +48,8 @@ constructor(
private val context: Context,
private val deviceStateManager: DeviceStateManager,
private val wakefulnessLifecycle: WakefulnessLifecycle,
- private val globalSettings: GlobalSettings
+ private val globalSettings: GlobalSettings,
+ private val latencyTracker: LatencyTracker,
) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
private lateinit var mCentralSurfaces: CentralSurfaces
@@ -64,12 +66,14 @@ constructor(
private var isAnimationPlaying = false
private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
+ private val foldToAodLatencyTracker = FoldToAodLatencyTracker()
private val startAnimationRunnable = Runnable {
- mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation {
- // End action
- setAnimationState(playing = false)
- }
+ mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation(
+ /* startAction= */ { foldToAodLatencyTracker.onAnimationStarted() },
+ /* endAction= */ { setAnimationState(playing = false) },
+ /* cancelAction= */ { setAnimationState(playing = false) },
+ )
}
override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
@@ -82,11 +86,13 @@ constructor(
/** Returns true if we should run fold to AOD animation */
override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation
- override fun startAnimation(): Boolean =
- if (alwaysOnEnabled &&
+ private fun shouldStartAnimation(): Boolean =
+ alwaysOnEnabled &&
wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
- ) {
+
+ override fun startAnimation(): Boolean =
+ if (shouldStartAnimation()) {
setAnimationState(playing = true)
mCentralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
true
@@ -97,6 +103,7 @@ constructor(
override fun onStartedWakingUp() {
if (isAnimationPlaying) {
+ foldToAodLatencyTracker.cancel()
handler.removeCallbacks(startAnimationRunnable)
mCentralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation()
}
@@ -137,7 +144,8 @@ constructor(
// but we should wait for the initial animation preparations to be drawn
// (setting initial alpha/translation)
OneShotPreDrawListener.add(
- mCentralSurfaces.notificationPanelViewController.view, onReady
+ mCentralSurfaces.notificationPanelViewController.view,
+ onReady
)
} else {
// No animation, call ready callback immediately
@@ -209,5 +217,41 @@ constructor(
isFoldHandled = false
}
this.isFolded = isFolded
- })
+ if (isFolded) {
+ foldToAodLatencyTracker.onFolded()
+ }
+ }
+ )
+
+ /**
+ * Tracks the latency of fold to AOD using [LatencyTracker].
+ *
+ * Events that trigger start and end are:
+ *
+ * - Start: Once [DeviceStateManager] sends the folded signal [FoldToAodLatencyTracker.onFolded]
+ * is called and latency tracking starts.
+ * - End: Once the fold -> AOD animation starts, [FoldToAodLatencyTracker.onAnimationStarted] is
+ * called, and latency tracking stops.
+ */
+ private inner class FoldToAodLatencyTracker {
+
+ /** Triggers the latency logging, if needed. */
+ fun onFolded() {
+ if (shouldStartAnimation()) {
+ latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD)
+ }
+ }
+ /**
+ * Called once the Fold -> AOD animation is started.
+ *
+ * For latency tracking, this determines the end of the fold to aod action.
+ */
+ fun onAnimationStarted() {
+ latencyTracker.onActionEnd(LatencyTracker.ACTION_FOLD_TO_AOD)
+ }
+
+ fun cancel() {
+ latencyTracker.onActionCancel(LatencyTracker.ACTION_FOLD_TO_AOD)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 3e07144dce74..e22a896227ef 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -40,7 +40,6 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.ZenModeConfig;
-import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -83,6 +82,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -262,7 +262,7 @@ public class BubblesManager implements Dumpable {
}
@Override
- public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+ public void getShouldRestoredEntries(Set<String> savedBubbleKeys,
Consumer<List<BubbleEntry>> callback) {
sysuiMainExecutor.execute(() -> {
List<BubbleEntry> result = new ArrayList<>();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
new file mode 100644
index 000000000000..4f3995252b54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -0,0 +1,259 @@
+/*
+ * 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.keyguard
+
+import android.content.BroadcastReceiver
+import android.testing.AndroidTestingRunner
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockAnimations
+import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.any
+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.mock
+import java.util.TimeZone
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+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
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ClockEventControllerTest : SysuiTestCase() {
+
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var batteryController: BatteryController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var animations: ClockAnimations
+ @Mock private lateinit var events: ClockEvents
+ @Mock private lateinit var clock: Clock
+
+ private lateinit var clockEventController: ClockEventController
+
+ @Before
+ fun setUp() {
+ whenever(clock.smallClock).thenReturn(TextView(context))
+ whenever(clock.largeClock).thenReturn(TextView(context))
+ whenever(clock.events).thenReturn(events)
+ whenever(clock.animations).thenReturn(animations)
+
+ clockEventController = ClockEventController(
+ statusBarStateController,
+ broadcastDispatcher,
+ batteryController,
+ keyguardUpdateMonitor,
+ configurationController,
+ context.resources,
+ context
+ )
+ }
+
+ @Test
+ fun clockSet_validateInitialization() {
+ clockEventController.clock = clock
+
+ verify(clock).initialize(any(), anyFloat(), anyFloat())
+ }
+
+ @Test
+ fun clockUnset_validateState() {
+ clockEventController.clock = clock
+ clockEventController.clock = null
+
+ assertEquals(clockEventController.clock, null)
+ }
+
+ @Test
+ fun themeChanged_verifyClockPaletteUpdated() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+ verify(configurationController).addCallback(capture(captor))
+ captor.value.onThemeChanged()
+
+ verify(events).onColorPaletteChanged(any())
+ }
+
+ @Test
+ fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+
+ verify(animations).charge()
+ }
+
+ @Test
+ fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+
+ verify(animations, times(1)).charge()
+ }
+
+ @Test
+ fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(false)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+
+ verify(animations, never()).charge()
+ }
+
+ @Test
+ fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, false)
+
+ verify(animations, never()).charge()
+ }
+
+ @Test
+ fun localeCallback_verifyClockNotified() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(broadcastDispatcher).registerReceiver(
+ capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
+ )
+ captor.value.onReceive(context, mock())
+
+ verify(events).onLocaleChanged(any())
+ }
+
+ @Test
+ fun keyguardCallback_visibilityChanged_clockDozeCalled() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+
+ captor.value.onKeyguardVisibilityChanged(true)
+ verify(animations, never()).doze(0f)
+
+ captor.value.onKeyguardVisibilityChanged(false)
+ verify(animations, times(1)).doze(0f)
+ }
+
+ @Test
+ fun keyguardCallback_timeFormat_clockNotified() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+ captor.value.onTimeFormatChanged("12h")
+
+ verify(events).onTimeFormatChanged(false)
+ }
+
+ @Test
+ fun keyguardCallback_timezoneChanged_clockNotified() {
+ val mockTimeZone = mock<TimeZone>()
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+ captor.value.onTimeZoneChanged(mockTimeZone)
+
+ verify(events).onTimeZoneChanged(mockTimeZone)
+ }
+
+ @Test
+ fun keyguardCallback_userSwitched_clockNotified() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+ captor.value.onUserSwitchComplete(10)
+
+ verify(events).onTimeFormatChanged(false)
+ }
+
+ @Test
+ fun keyguardCallback_verifyKeyguardChanged() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ verify(statusBarStateController).addCallback(capture(captor))
+ captor.value.onDozeAmountChanged(0.4f, 0.6f)
+
+ verify(animations).doze(0.4f)
+ }
+
+ @Test
+ fun unregisterListeners_validate() {
+ clockEventController.unregisterListeners()
+ verify(broadcastDispatcher).unregisterReceiver(any())
+ verify(configurationController).removeCallback(any())
+ verify(batteryController).removeCallback(any())
+ verify(keyguardUpdateMonitor).removeCallback(any())
+ verify(statusBarStateController).removeCallback(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 8b9a1e022d26..635ee9ea1a2f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,7 +19,6 @@ package com.android.keyguard;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -41,23 +40,19 @@ import android.widget.RelativeLayout;
import androidx.test.filters.SmallTest;
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.clock.ClockManager;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.plugins.Clock;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -79,22 +74,12 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
- private SysuiColorExtractor mColorExtractor;
- @Mock
- private ClockManager mClockManager;
+ private ClockRegistry mClockRegistry;
@Mock
KeyguardSliceViewController mKeyguardSliceViewController;
@Mock
NotificationIconAreaController mNotificationIconAreaController;
@Mock
- BroadcastDispatcher mBroadcastDispatcher;
- @Mock
- BatteryController mBatteryController;
- @Mock
- KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- KeyguardBypassController mBypassController;
- @Mock
LockscreenSmartspaceController mSmartspaceController;
@Mock
@@ -102,11 +87,11 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
@Mock
KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
- private ClockPlugin mClockPlugin;
- @Mock
- ColorExtractor.GradientColors mGradientColors;
+ private Clock mClock;
@Mock
DumpManager mDumpManager;
+ @Mock
+ ClockEventController mClockEventController;
@Mock
private NotificationIconContainer mNotificationIcons;
@@ -118,6 +103,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
private FrameLayout mLargeClockFrame;
@Mock
private SecureSettings mSecureSettings;
+ @Mock
+ private FeatureFlags mFeatureFlags;
private final View mFakeSmartspaceView = new View(mContext);
@@ -136,8 +123,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mView.getContext()).thenReturn(getContext());
when(mView.getResources()).thenReturn(mResources);
- when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
- when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
when(mClockView.getContext()).thenReturn(getContext());
when(mLargeClockView.getContext()).thenReturn(getContext());
@@ -148,23 +133,20 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
mController = new KeyguardClockSwitchController(
mView,
mStatusBarStateController,
- mColorExtractor,
- mClockManager,
+ mClockRegistry,
mKeyguardSliceViewController,
mNotificationIconAreaController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
mSmartspaceController,
mKeyguardUnlockAnimationController,
mSecureSettings,
mExecutor,
- mResources,
- mDumpManager
+ mDumpManager,
+ mClockEventController,
+ mFeatureFlags
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
+ when(mClockRegistry.createCurrentClock()).thenReturn(mClock);
mSliceView = new View(getContext());
when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -211,20 +193,20 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
verifyAttachment(times(1));
listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mColorExtractor).removeOnColorsChangedListener(
- any(ColorExtractor.OnColorsChangedListener.class));
+ verify(mClockEventController).unregisterListeners();
}
@Test
public void testPluginPassesStatusBarState() {
- ArgumentCaptor<ClockManager.ClockChangedListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(ClockManager.ClockChangedListener.class);
+ ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
mController.init();
- verify(mClockManager).addOnClockChangedListener(listenerArgumentCaptor.capture());
+ verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
- listenerArgumentCaptor.getValue().onClockChanged(mClockPlugin);
- verify(mView).setClockPlugin(mClockPlugin, StatusBarState.SHADE);
+ listenerArgumentCaptor.getValue().onClockChanged();
+ verify(mView, times(2)).setClock(mClock, StatusBarState.SHADE);
+ verify(mClockEventController, times(2)).setClock(mClock);
}
@Test
@@ -281,10 +263,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
}
private void verifyAttachment(VerificationMode times) {
- verify(mClockManager, times).addOnClockChangedListener(
- any(ClockManager.ClockChangedListener.class));
- verify(mColorExtractor, times).addOnColorsChangedListener(
- any(ColorExtractor.OnColorsChangedListener.class));
- verify(mView, times).updateColors(mGradientColors);
+ verify(mClockRegistry, times).registerClockChangeListener(
+ any(ClockRegistry.ClockChangeListener.class));
+ verify(mClockEventController, times).registerListeners();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 6c6f0acd7085..a0295d09826f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -24,56 +23,61 @@ import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.TestCase.assertEquals;
+
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Paint.Style;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
-import android.widget.TextClock;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.plugins.Clock;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
// Need to run on the main thread because KeyguardSliceView$Row init checks for
// the main thread before acquiring a wake lock. This class is constructed when
-// the keyguard_clcok_switch layout is inflated.
+// the keyguard_clock_switch layout is inflated.
@RunWithLooper(setAsMainLooper = true)
public class KeyguardClockSwitchTest extends SysuiTestCase {
- private FrameLayout mClockFrame;
+ @Mock
+ ViewGroup mMockKeyguardSliceView;
+
+ @Mock
+ Clock mClock;
+
+ private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
- private TextClock mBigClock;
- private AnimatableClockView mClockView;
- private AnimatableClockView mLargeClockView;
- View mMockKeyguardSliceView;
KeyguardClockSwitch mKeyguardClockSwitch;
@Before
public void setUp() {
- mMockKeyguardSliceView = mock(KeyguardSliceView.class);
+ MockitoAnnotations.initMocks(this);
when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
.thenReturn(mMockKeyguardSliceView);
+ when(mClock.getSmallClock()).thenReturn(new TextView(getContext()));
+ when(mClock.getLargeClock()).thenReturn(new TextView(getContext()));
+
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
@@ -93,164 +97,68 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
});
mKeyguardClockSwitch =
(KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
- mClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
- mClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view);
+ mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large);
- mLargeClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view_large);
- mBigClock = new TextClock(getContext());
mKeyguardClockSwitch.mChildrenAreLaidOut = true;
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void onPluginConnected_showPluginClock() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
-
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
- assertThat(mClockView.getVisibility()).isEqualTo(GONE);
- assertThat(plugin.getView().getParent()).isEqualTo(mClockFrame);
}
@Test
- public void onPluginConnected_showPluginBigClock() {
- // GIVEN the plugin returns a view for the big clock
- ClockPlugin plugin = mock(ClockPlugin.class);
- when(plugin.getBigClockView()).thenReturn(mBigClock);
- // WHEN the plugin is connected
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- // THEN the big clock container is visible and it is the parent of the
- // big clock view.
- assertThat(mLargeClockView.getVisibility()).isEqualTo(View.GONE);
- assertThat(mBigClock.getParent()).isEqualTo(mLargeClockFrame);
+ public void noPluginConnected_showNothing() {
+ mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+ assertEquals(mLargeClockFrame.getChildCount(), 0);
+ assertEquals(mSmallClockFrame.getChildCount(), 0);
}
@Test
- public void onPluginConnected_nullView() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
- }
-
- @Test
- public void onPluginConnected_showSecondPluginClock() {
- // GIVEN a plugin has already connected
- ClockPlugin plugin1 = mock(ClockPlugin.class);
- when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD);
- // WHEN a second plugin is connected
- ClockPlugin plugin2 = mock(ClockPlugin.class);
- when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD);
- // THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
- assertThat(plugin2.getView().getParent()).isEqualTo(mClockFrame);
- assertThat(plugin1.getView().getParent()).isNull();
- }
+ public void pluginConnectedThenDisconnected_showNothing() {
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
+ assertEquals(mLargeClockFrame.getChildCount(), 1);
+ assertEquals(mSmallClockFrame.getChildCount(), 1);
- @Test
- public void onPluginConnected_darkAmountInitialized() {
- // GIVEN that the dark amount has already been set
- mKeyguardClockSwitch.setDarkAmount(0.5f);
- // WHEN a plugin is connected
- ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- // THEN dark amount should be initalized on the plugin.
- verify(plugin).setDarkAmount(0.5f);
+ mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+ assertEquals(mLargeClockFrame.getChildCount(), 0);
+ assertEquals(mSmallClockFrame.getChildCount(), 0);
}
@Test
- public void onPluginDisconnected_showDefaultClock() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
-
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(GONE);
+ public void onPluginConnected_showClock() {
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
-
- assertThat(plugin.getView().getParent()).isNull();
+ assertEquals(mClock.getSmallClock().getParent(), mSmallClockFrame);
+ assertEquals(mClock.getLargeClock().getParent(), mLargeClockFrame);
}
@Test
- public void onPluginDisconnected_hidePluginBigClock() {
- // GIVEN the plugin returns a view for the big clock
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getBigClockView()).thenReturn(pluginView);
- // WHEN the plugin is connected and then disconnected
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- // THEN the big lock container is GONE and the big clock view doesn't have
- // a parent.
- assertThat(mLargeClockView.getVisibility()).isEqualTo(VISIBLE);
- assertThat(pluginView.getParent()).isNull();
- }
+ public void onPluginConnected_showSecondPluginClock() {
+ // GIVEN a plugin has already connected
+ Clock otherClock = mock(Clock.class);
+ when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
+ when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
+ mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
- @Test
- public void onPluginDisconnected_nullView() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
+ // THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
+ assertThat(otherClock.getSmallClock().getParent()).isEqualTo(mSmallClockFrame);
+ assertThat(otherClock.getLargeClock().getParent()).isEqualTo(mLargeClockFrame);
+ assertThat(mClock.getSmallClock().getParent()).isNull();
+ assertThat(mClock.getLargeClock().getParent()).isNull();
}
@Test
public void onPluginDisconnected_secondOfTwoDisconnected() {
// GIVEN two plugins are connected
- ClockPlugin plugin1 = mock(ClockPlugin.class);
- when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD);
- ClockPlugin plugin2 = mock(ClockPlugin.class);
- when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD);
+ Clock otherClock = mock(Clock.class);
+ when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
+ when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
// WHEN the second plugin is disconnected
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- // THEN the default clock should be shown.
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
- assertThat(plugin1.getView().getParent()).isNull();
- assertThat(plugin2.getView().getParent()).isNull();
- }
-
- @Test
- public void onPluginDisconnected_onDestroyView() {
- // GIVEN a plugin is connected
- ClockPlugin clockPlugin = mock(ClockPlugin.class);
- when(clockPlugin.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(clockPlugin, StatusBarState.KEYGUARD);
- // WHEN the plugin is disconnected
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- // THEN onDestroyView is called on the plugin
- verify(clockPlugin).onDestroyView();
- }
-
- @Test
- public void setTextColor_pluginClockSetTextColor() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
- mKeyguardClockSwitch.setTextColor(Color.WHITE);
-
- verify(plugin).setTextColor(Color.WHITE);
- }
-
-
- @Test
- public void setStyle_pluginClockSetStyle() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
- Style style = mock(Style.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
- mKeyguardClockSwitch.setStyle(style);
-
- verify(plugin).setStyle(style);
+ mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+ // THEN nothing should be shown
+ assertThat(otherClock.getSmallClock().getParent()).isNull();
+ assertThat(otherClock.getLargeClock().getParent()).isNull();
+ assertThat(mClock.getSmallClock().getParent()).isNull();
+ assertThat(mClock.getLargeClock().getParent()).isNull();
}
@Test
@@ -262,7 +170,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
- assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
}
@Test
@@ -271,7 +179,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
- assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
}
@Test
@@ -281,8 +189,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
mKeyguardClockSwitch.mClockInAnim.end();
mKeyguardClockSwitch.mClockOutAnim.end();
- assertThat(mClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
@@ -292,8 +200,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
public void switchingToSmallClockNoAnimation_makesBigClockDisappear() {
mKeyguardClockSwitch.switchToClock(SMALL, false);
- assertThat(mClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 68e49c0a1d4b..dc87a6a45131 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -318,7 +318,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
@Test
public void onBouncerVisibilityChanged_withoutSidedSecurity_sideFpsHintHidden() {
setupConditionsToEnableSideFpsHint();
- setSidedSecurityMode(false);
+ setSideFpsHintEnabledFromResources(false);
reset(mSidefpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
@@ -383,7 +383,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
private void setupConditionsToEnableSideFpsHint() {
attachView();
- setSidedSecurityMode(true);
+ setSideFpsHintEnabledFromResources(true);
setFingerprintDetectionRunning(true);
setNeedsStrongAuth(false);
}
@@ -399,8 +399,9 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
BiometricSourceType.FINGERPRINT);
}
- private void setSidedSecurityMode(boolean sided) {
- when(mView.isSidedSecurityMode()).thenReturn(sided);
+ private void setSideFpsHintEnabledFromResources(boolean enabled) {
+ when(mResources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)).thenReturn(
+ enabled);
}
private void setNeedsStrongAuth(boolean needed) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 84903d17852f..85ecfd3056ff 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -86,6 +86,7 @@ import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
+import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -179,6 +180,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
private KeyguardUpdateMonitorCallback mTestCallback;
@Mock
private ActiveUnlockConfig mActiveUnlockConfig;
+ @Mock
+ private KeyguardUpdateMonitorLogger mKeyguardUpdateMonitorLogger;
// Direct executor
private Executor mBackgroundExecutor = Runnable::run;
private Executor mMainExecutor = Runnable::run;
@@ -1189,7 +1192,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
- mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig);
+ mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig,
+ mKeyguardUpdateMonitorLogger);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 6157ccbd597a..8d969d0f1045 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -45,6 +45,8 @@ import android.view.View
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
import android.view.WindowMetrics
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
@@ -438,6 +440,44 @@ class SidefpsControllerTest : SysuiTestCase() {
assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
}
+
+ @Test
+ fun testLayoutParams_hasNoMoveAnimationWindowFlag() = testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
+ ) {
+ sideFpsController.overlayOffsets = sensorLocation
+ sideFpsController.updateOverlayParams(
+ windowManager.defaultDisplay,
+ indicatorBounds
+ )
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+ val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+ assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
+ }
+
+ @Test
+ fun testLayoutParams_hasTrustedOverlayWindowFlag() = testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
+ ) {
+ sideFpsController.overlayOffsets = sensorLocation
+ sideFpsController.updateOverlayParams(
+ windowManager.defaultDisplay,
+ indicatorBounds
+ )
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+ val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+ assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
+ }
}
private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
index 365c5291b118..2915f5a504d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.dreams.complication;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
import static com.android.systemui.dreams.complication.ComplicationUtils.convertComplicationType;
@@ -57,6 +58,8 @@ public class ComplicationUtilsTest extends SysuiTestCase {
.isEqualTo(COMPLICATION_TYPE_AIR_QUALITY);
assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_CAST_INFO))
.isEqualTo(COMPLICATION_TYPE_CAST_INFO);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS))
+ .isEqualTo(COMPLICATION_TYPE_HOME_CONTROLS);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
deleted file mode 100644
index 86aa14d7a877..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.dreams.complication;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamOverlayStateController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import javax.inject.Provider;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class DreamClockDateComplicationTest extends SysuiTestCase {
- @SuppressWarnings("HidingField")
- @Mock
- private Context mContext;
-
- @Mock
- private DreamOverlayStateController mDreamOverlayStateController;
-
- @Mock
- private DreamClockDateComplication mComplication;
-
- @Mock
- private Provider<DreamClockDateComplication.DreamClockDateViewHolder>
- mDreamClockDateViewHolderProvider;
-
- @Mock
- private DreamClockDateComplication.DreamClockDateViewHolder
- mDreamClockDateViewHolder;
-
- @Mock
- private ComplicationViewModel mComplicationViewModel;
-
- @Mock
- private View mView;
-
- @Mock
- private ComplicationLayoutParams mLayoutParams;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- when(mDreamClockDateViewHolderProvider.get()).thenReturn(mDreamClockDateViewHolder);
-
- }
-
- /**
- * Ensures {@link DreamClockDateComplication} is registered.
- */
- @Test
- public void testComplicationAdded() {
- final DreamClockDateComplication.Registrant registrant =
- new DreamClockDateComplication.Registrant(
- mContext,
- mDreamOverlayStateController,
- mComplication);
- registrant.start();
- verify(mDreamOverlayStateController).addComplication(eq(mComplication));
- }
-
- /**
- * Verifies {@link DreamClockDateComplication} has the required type.
- */
- @Test
- public void testComplicationRequiredTypeAvailability() {
- final DreamClockDateComplication complication =
- new DreamClockDateComplication(mDreamClockDateViewHolderProvider);
- assertEquals(Complication.COMPLICATION_TYPE_DATE,
- complication.getRequiredTypeAvailability());
- }
-
- /**
- * Verifies {@link DreamClockDateComplication.DreamClockDateViewHolder} is obtainable from its
- * provider when the complication creates view.
- */
- @Test
- public void testComplicationViewHolderProviderOnCreateView() {
- final DreamClockDateComplication complication =
- new DreamClockDateComplication(mDreamClockDateViewHolderProvider);
- final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
- verify(mDreamClockDateViewHolderProvider).get();
- assertThat(viewHolder).isEqualTo(mDreamClockDateViewHolder);
- }
-
- /**
- * Verifies {@link DreamClockDateComplication.DreamClockDateViewHolder} has the intended view
- * and layout parameters from constructor.
- */
- @Test
- public void testComplicationViewHolderContentAccessors() {
- final DreamClockDateComplication.DreamClockDateViewHolder viewHolder =
- new DreamClockDateComplication.DreamClockDateViewHolder(mView, mLayoutParams);
- assertThat(viewHolder.getView()).isEqualTo(mView);
- assertThat(viewHolder.getLayoutParams()).isEqualTo(mLayoutParams);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 5191f635a2cf..04ff7aed480d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -17,6 +17,9 @@
package com.android.systemui.dreams.complication;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
+
+import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
@@ -36,6 +39,7 @@ import com.android.systemui.controls.controller.StructureInfo;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
import org.junit.Before;
import org.junit.Test;
@@ -69,6 +73,9 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
@Mock
private ControlsListingController mControlsListingController;
+ @Mock
+ private DreamHomeControlsComplicationComponent.Factory mComponentFactory;
+
@Captor
private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
@@ -85,6 +92,14 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
}
@Test
+ public void complicationType() {
+ final DreamHomeControlsComplication complication =
+ new DreamHomeControlsComplication(mComponentFactory);
+ assertThat(complication.getRequiredTypeAvailability()).isEqualTo(
+ COMPLICATION_TYPE_HOME_CONTROLS);
+ }
+
+ @Test
public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mContext, mComplication,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
index 964e6d79f0bf..7d54758acee7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.dreams;
+package com.android.systemui.dreams.complication;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,8 +31,7 @@ import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
@@ -183,9 +183,9 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
@Test
public void testGetView_reusesSameView() {
- final SmartSpaceComplication complication = new SmartSpaceComplication(getContext(),
- mSmartspaceController);
- final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
+ final Complication.ViewHolder viewHolder =
+ new SmartSpaceComplication.SmartSpaceComplicationViewHolder(getContext(),
+ mSmartspaceController, mock(ComplicationLayoutParams.class));
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mBcSmartspaceView);
assertEquals(viewHolder.getView(), viewHolder.getView());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 000000000000..592e80b9e7d9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var component: ControlsComponent
+ @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+ private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ HomeControlsKeyguardQuickAffordanceConfig(
+ context = context,
+ component = component,
+ )
+ }
+
+ @Test
+ fun `state - when listing controller is missing - returns None`() = runBlockingTest {
+ whenever(component.isEnabled()).thenReturn(true)
+ whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
+ whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
+ val controlsController = mock<ControlsController>()
+ whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
+ whenever(component.getControlsListingController()).thenReturn(Optional.empty())
+ whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
+
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
+ val job = underTest.state.onEach(values::add).launchIn(this)
+
+ assertThat(values.last())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+ job.cancel()
+ }
+
+ @Test
+ fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest {
+ whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
+
+ val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+
+ assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue()
+ }
+
+ @Test
+ fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest {
+ whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
+
+ val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+
+ assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 000000000000..6fd04de8c9ac
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var controller: QRCodeScannerController
+
+ private lateinit var underTest: QrCodeScannerKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(controller.intent).thenReturn(INTENT_1)
+
+ underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller)
+ }
+
+ @Test
+ fun `affordance - sets up registration and delivers initial model`() = runBlockingTest {
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+ verify(controller)
+ .registerQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ assertVisibleState(latest)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun `affordance - scanner activity changed - delivers model with updated intent`() =
+ runBlockingTest {
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+
+ whenever(controller.intent).thenReturn(INTENT_2)
+ callbackCaptor.value.onQRCodeScannerActivityChanged()
+
+ assertVisibleState(latest)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun `affordance - scanner preference changed - delivers visible model`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+ callbackCaptor.value.onQRCodeScannerPreferenceChanged()
+
+ assertVisibleState(latest)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun `affordance - scanner preference changed - delivers none`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(false)
+ callbackCaptor.value.onQRCodeScannerPreferenceChanged()
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun onQuickAffordanceClicked() {
+ assertThat(underTest.onQuickAffordanceClicked(mock()))
+ .isEqualTo(
+ OnClickedResult.StartActivity(
+ intent = INTENT_1,
+ canShowWhileLocked = true,
+ )
+ )
+ }
+
+ private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.State?) {
+ assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
+ val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
+ assertThat(visibleState.icon).isNotNull()
+ assertThat(visibleState.contentDescriptionResourceId).isNotNull()
+ }
+
+ companion object {
+ private val INTENT_1 = Intent("intent1")
+ private val INTENT_2 = Intent("intent2")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 000000000000..345c51f6f760
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,179 @@
+/*
+ * 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.keyguard.data.quickaffordance
+
+import android.graphics.drawable.Drawable
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.wallet.controller.QuickAccessWalletController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var walletController: QuickAccessWalletController
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ private lateinit var underTest: QuickAccessWalletKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ QuickAccessWalletKeyguardQuickAffordanceConfig(
+ walletController,
+ activityStarter,
+ )
+ }
+
+ @Test
+ fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest {
+ setUpState()
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
+ assertThat(visibleModel.icon).isEqualTo(ContainedDrawable.WithDrawable(ICON))
+ assertThat(visibleModel.contentDescriptionResourceId).isNotNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `affordance - wallet not enabled - model is none`() = runBlockingTest {
+ setUpState(isWalletEnabled = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `affordance - query not successful - model is none`() = runBlockingTest {
+ setUpState(isWalletQuerySuccessful = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `affordance - missing icon - model is none`() = runBlockingTest {
+ setUpState(hasWalletIcon = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `affordance - no selected card - model is none`() = runBlockingTest {
+ setUpState(hasWalletIcon = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ }
+
+ @Test
+ fun onQuickAffordanceClicked() {
+ val animationController: ActivityLaunchAnimator.Controller = mock()
+
+ assertThat(underTest.onQuickAffordanceClicked(animationController))
+ .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled)
+ verify(walletController)
+ .startQuickAccessUiIntent(
+ activityStarter,
+ animationController,
+ /* hasCard= */ true,
+ )
+ }
+
+ private fun setUpState(
+ isWalletEnabled: Boolean = true,
+ isWalletQuerySuccessful: Boolean = true,
+ hasWalletIcon: Boolean = true,
+ hasSelectedCard: Boolean = true,
+ ) {
+ whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
+
+ val walletClient: QuickAccessWalletClient = mock()
+ val icon: Drawable? =
+ if (hasWalletIcon) {
+ ICON
+ } else {
+ null
+ }
+ whenever(walletClient.tileIcon).thenReturn(icon)
+ whenever(walletController.walletClient).thenReturn(walletClient)
+
+ whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
+ with(
+ invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback
+ ) {
+ if (isWalletQuerySuccessful) {
+ onWalletCardsRetrieved(
+ if (hasSelectedCard) {
+ GetWalletCardsResponse(listOf(mock()), 0)
+ } else {
+ GetWalletCardsResponse(emptyList(), 0)
+ }
+ )
+ } else {
+ onWalletCardRetrievalError(mock())
+ }
+ }
+ }
+ }
+
+ companion object {
+ private val ICON: Drawable = mock()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 000000000000..6fff440ec2fa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.yield
+
+/**
+ * Fake implementation of a quick affordance data source.
+ *
+ * This class is abstract to force tests to provide extensions of it as the system that references
+ * these configs uses each implementation's class type to refer to them.
+ */
+abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
+
+ private val _onClickedInvocations = mutableListOf<ActivityLaunchAnimator.Controller?>()
+ val onClickedInvocations: List<ActivityLaunchAnimator.Controller?> = _onClickedInvocations
+
+ var onClickedResult: OnClickedResult = OnClickedResult.Handled
+
+ private val _state =
+ MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
+ KeyguardQuickAffordanceConfig.State.Hidden
+ )
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): OnClickedResult {
+ _onClickedInvocations.add(animationController)
+ return onClickedResult
+ }
+
+ suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
+ _state.value = state
+ // Yield to allow the test's collection coroutine to "catch up" and collect this value
+ // before the test continues to the next line.
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+ yield()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
new file mode 100644
index 000000000000..a24fc93fedc2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import kotlin.reflect.KClass
+
+/** Fake implementation of [KeyguardQuickAffordanceConfigs], for tests. */
+class FakeKeyguardQuickAffordanceConfigs(
+ private val configsByPosition:
+ Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
+) : KeyguardQuickAffordanceConfigs {
+
+ override fun getAll(
+ position: KeyguardQuickAffordancePosition
+ ): List<KeyguardQuickAffordanceConfig> {
+ return configsByPosition.getValue(position)
+ }
+
+ override fun get(
+ configClass: KClass<out KeyguardQuickAffordanceConfig>
+ ): KeyguardQuickAffordanceConfig {
+ return configsByPosition.values
+ .flatten()
+ .associateBy { config -> config::class }
+ .getValue(configClass)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
new file mode 100644
index 000000000000..10d2e4de631d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.yield
+
+/** Fake implementation of [KeyguardQuickAffordanceRepository], for tests. */
+class FakeKeyguardQuickAffordanceRepository : KeyguardQuickAffordanceRepository {
+
+ private val modelByPosition =
+ mutableMapOf<
+ KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
+
+ init {
+ KeyguardQuickAffordancePosition.values().forEach { value ->
+ modelByPosition[value] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
+ }
+ }
+
+ override fun affordance(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ return modelByPosition.getValue(position)
+ }
+
+ suspend fun setModel(
+ position: KeyguardQuickAffordancePosition,
+ model: KeyguardQuickAffordanceModel
+ ) {
+ modelByPosition.getValue(position).value = model
+ // Yield to allow the test's collection coroutine to "catch up" and collect this value
+ // before the test continues to the next line.
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+ yield()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
new file mode 100644
index 000000000000..d40b985f64de
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.common.data.model.Position
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Fake implementation of [KeyguardRepository] */
+class FakeKeyguardRepository : KeyguardRepository {
+
+ private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
+ override val animateBottomAreaDozingTransitions: StateFlow<Boolean> =
+ _animateBottomAreaDozingTransitions
+
+ private val _bottomAreaAlpha = MutableStateFlow(1f)
+ override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
+
+ private val _clockPosition = MutableStateFlow(Position(0, 0))
+ override val clockPosition: StateFlow<Position> = _clockPosition
+
+ private val _isKeyguardShowing = MutableStateFlow(false)
+ override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
+
+ private val _isDozing = MutableStateFlow(false)
+ override val isDozing: Flow<Boolean> = _isDozing
+
+ private val _dozeAmount = MutableStateFlow(0f)
+ override val dozeAmount: Flow<Float> = _dozeAmount
+
+ init {
+ setDozeAmount(0f)
+ setDozing(false)
+ }
+
+ override fun setAnimateDozingTransitions(animate: Boolean) {
+ _animateBottomAreaDozingTransitions.tryEmit(animate)
+ }
+
+ override fun setBottomAreaAlpha(alpha: Float) {
+ _bottomAreaAlpha.value = alpha
+ }
+
+ override fun setClockPosition(x: Int, y: Int) {
+ _clockPosition.value = Position(x, y)
+ }
+
+ fun setKeyguardShowing(isShowing: Boolean) {
+ _isKeyguardShowing.value = isShowing
+ }
+
+ fun setDozing(isDozing: Boolean) {
+ _isDozing.value = isDozing
+ }
+
+ fun setDozeAmount(dozeAmount: Float) {
+ _dozeAmount.value = dozeAmount
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
new file mode 100644
index 000000000000..bcc76abc89ba
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTestCase() {
+
+ companion object {
+ @Parameters(
+ name =
+ "feature enabled = {0}, has favorites = {1}, has service infos = {2} - expected" +
+ " visible = {3}"
+ )
+ @JvmStatic
+ fun data() =
+ (0 until 8)
+ .map { combination ->
+ arrayOf(
+ /* isFeatureEnabled= */ combination and 0b100 != 0,
+ /* hasFavorites= */ combination and 0b010 != 0,
+ /* hasServiceInfos= */ combination and 0b001 != 0,
+ /* isVisible= */ combination == 0b111,
+ )
+ }
+ .toList()
+ }
+
+ @Mock private lateinit var component: ControlsComponent
+ @Mock private lateinit var controlsController: ControlsController
+ @Mock private lateinit var controlsListingController: ControlsListingController
+ @Captor
+ private lateinit var callbackCaptor:
+ ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+
+ private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
+
+ @JvmField @Parameter(0) var isFeatureEnabled: Boolean = false
+ @JvmField @Parameter(1) var hasFavorites: Boolean = false
+ @JvmField @Parameter(2) var hasServiceInfos: Boolean = false
+ @JvmField @Parameter(3) var isVisible: Boolean = false
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
+ whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
+ whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
+ whenever(component.getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+
+ underTest =
+ HomeControlsKeyguardQuickAffordanceConfig(
+ context = context,
+ component = component,
+ )
+ }
+
+ @Test
+ fun state() = runBlockingTest {
+ whenever(component.isEnabled()).thenReturn(isFeatureEnabled)
+ whenever(controlsController.getFavorites())
+ .thenReturn(
+ if (hasFavorites) {
+ listOf(mock())
+ } else {
+ emptyList()
+ }
+ )
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
+ val job = underTest.state.onEach(values::add).launchIn(this)
+
+ verify(controlsListingController).addCallback(callbackCaptor.capture())
+ callbackCaptor.value.onServicesUpdated(
+ if (hasServiceInfos) {
+ listOf(mock())
+ } else {
+ emptyList()
+ }
+ )
+
+ assertThat(values.last())
+ .isInstanceOf(
+ if (isVisible) {
+ KeyguardQuickAffordanceConfig.State.Visible::class.java
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden::class.java
+ }
+ )
+ job.cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
new file mode 100644
index 000000000000..dc0e6f7663ff
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRepositoryImplTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardQuickAffordanceRepository
+
+ private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+ private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
+ private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+ quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
+ qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
+
+ underTest =
+ KeyguardQuickAffordanceRepositoryImpl(
+ configs =
+ FakeKeyguardQuickAffordanceConfigs(
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControls,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ quickAccessWallet,
+ qrCodeScanner,
+ ),
+ ),
+ ),
+ )
+ }
+
+ @Test
+ fun `bottom start affordance - none`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom start affordance - home controls`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val state =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ homeControls.setState(state)
+
+ assertThat(latest).isEqualTo(state.toModel(homeControls::class))
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom end affordance - none`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom end affordance - quick access wallet`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val quickAccessWalletState =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAccessWallet.setState(quickAccessWalletState)
+ val qrCodeScannerState =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ qrCodeScanner.setState(qrCodeScannerState)
+
+ assertThat(latest).isEqualTo(quickAccessWalletState.toModel(quickAccessWallet::class))
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom end affordance - qr code scanner`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val state =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ qrCodeScanner.setState(state)
+
+ assertThat(latest).isEqualTo(state.toModel(qrCodeScanner::class))
+ job.cancel()
+ }
+
+ private fun KeyguardQuickAffordanceConfig.State?.toModel(
+ configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ ): KeyguardQuickAffordanceModel? {
+ return when (this) {
+ is KeyguardQuickAffordanceConfig.State.Visible ->
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = icon,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ is KeyguardQuickAffordanceConfig.State.Hidden -> KeyguardQuickAffordanceModel.Hidden
+ null -> null
+ }
+ }
+
+ companion object {
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
new file mode 100644
index 000000000000..3d2c51a449c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.model.Position
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardRepositoryImplTest : SysuiTestCase() {
+
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+ private lateinit var underTest: KeyguardRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = KeyguardRepositoryImpl(statusBarStateController, keyguardStateController)
+ }
+
+ @Test
+ fun animateBottomAreaDozingTransitions() = runBlockingTest {
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
+
+ underTest.setAnimateDozingTransitions(true)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+
+ underTest.setAnimateDozingTransitions(false)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
+
+ underTest.setAnimateDozingTransitions(true)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+ }
+
+ @Test
+ fun bottomAreaAlpha() = runBlockingTest {
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+
+ underTest.setBottomAreaAlpha(0.1f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
+
+ underTest.setBottomAreaAlpha(0.2f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
+
+ underTest.setBottomAreaAlpha(0.3f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
+
+ underTest.setBottomAreaAlpha(0.5f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
+
+ underTest.setBottomAreaAlpha(1.0f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+ }
+
+ @Test
+ fun clockPosition() = runBlockingTest {
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
+
+ underTest.setClockPosition(0, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
+
+ underTest.setClockPosition(1, 9)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
+
+ underTest.setClockPosition(1, 0)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
+
+ underTest.setClockPosition(3, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
+ }
+
+ @Test
+ fun isKeyguardShowing() = runBlockingTest {
+ whenever(keyguardStateController.isShowing).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
+
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+ captor.value.onKeyguardShowingChanged()
+ assertThat(latest).isTrue()
+
+ whenever(keyguardStateController.isShowing).thenReturn(false)
+ captor.value.onKeyguardShowingChanged()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDozing() = runBlockingTest {
+ var latest: Boolean? = null
+ val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+
+ val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ verify(statusBarStateController).addCallback(captor.capture())
+
+ captor.value.onDozingChanged(true)
+ assertThat(latest).isTrue()
+
+ captor.value.onDozingChanged(false)
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ verify(statusBarStateController).removeCallback(captor.value)
+ }
+
+ @Test
+ fun dozeAmount() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ verify(statusBarStateController).addCallback(captor.capture())
+
+ captor.value.onDozeAmountChanged(0.433f, 0.4f)
+ captor.value.onDozeAmountChanged(0.498f, 0.5f)
+ captor.value.onDozeAmountChanged(0.661f, 0.65f)
+
+ assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
+
+ job.cancel()
+ verify(statusBarStateController).removeCallback(captor.value)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 000000000000..ba0c31ffa9c9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import android.content.Intent
+import com.android.systemui.animation.ActivityLaunchAnimator
+
+/** Fake implementation of [LaunchKeyguardQuickAffordanceUseCase], for tests. */
+class FakeLaunchKeyguardQuickAffordanceUseCase : LaunchKeyguardQuickAffordanceUseCase {
+
+ data class Invocation(
+ val intent: Intent,
+ val canShowWhileLocked: Boolean,
+ val animationController: ActivityLaunchAnimator.Controller?
+ )
+
+ private val _invocations = mutableListOf<Invocation>()
+ val invocations: List<Invocation> = _invocations
+
+ override fun invoke(
+ intent: Intent,
+ canShowWhileLocked: Boolean,
+ animationController: ActivityLaunchAnimator.Controller?
+ ) {
+ _invocations.add(
+ Invocation(
+ intent = intent,
+ canShowWhileLocked = canShowWhileLocked,
+ animationController = animationController,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
new file mode 100644
index 000000000000..b3c1ae0106cf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class LaunchKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
+
+ companion object {
+ private val INTENT = Intent("some.intent.action")
+
+ @Parameters(
+ name =
+ "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
+ " keyguardIsUnlocked={2}, needsToUnlockFirst={3}"
+ )
+ @JvmStatic
+ fun data() =
+ listOf(
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ ),
+ )
+ }
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+ private lateinit var underTest: LaunchKeyguardQuickAffordanceUseCase
+
+ @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
+ @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
+ @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ LaunchKeyguardQuickAffordanceUseCaseImpl(
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ )
+ }
+
+ @Test
+ fun invoke() {
+ setUpMocks(
+ needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+ keyguardIsUnlocked = keyguardIsUnlocked,
+ )
+
+ underTest(
+ intent = INTENT,
+ canShowWhileLocked = canShowWhileLocked,
+ animationController = animationController,
+ )
+
+ if (needsToUnlockFirst) {
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ INTENT,
+ /* delay= */ 0,
+ animationController,
+ )
+ } else {
+ verify(activityStarter)
+ .startActivity(
+ INTENT,
+ /* dismissShade= */ true,
+ animationController,
+ /* showOverLockscreenWhenLocked= */ true,
+ )
+ }
+ }
+
+ private fun setUpMocks(
+ needStrongAuthAfterBoot: Boolean = true,
+ keyguardIsUnlocked: Boolean = false,
+ ) {
+ whenever(userTracker.userHandle).thenReturn(mock())
+ whenever(lockPatternUtils.getStrongAuthForUser(any()))
+ .thenReturn(
+ if (needStrongAuthAfterBoot) {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ } else {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+ }
+ )
+ whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
new file mode 100644
index 000000000000..b90400be16d8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class ObserveKeyguardQuickAffordanceUseCaseTest : SysuiTestCase() {
+
+ private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
+
+ private lateinit var repository: FakeKeyguardRepository
+ private lateinit var quickAffordanceRepository: FakeKeyguardQuickAffordanceRepository
+ private lateinit var isDozingUseCase: ObserveIsDozingUseCase
+ private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardRepository()
+ repository.setKeyguardShowing(true)
+ isDozingUseCase = ObserveIsDozingUseCase(repository)
+ isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
+ quickAffordanceRepository = FakeKeyguardQuickAffordanceRepository()
+
+ underTest =
+ ObserveKeyguardQuickAffordanceUseCase(
+ repository = quickAffordanceRepository,
+ isDozingUseCase = isDozingUseCase,
+ isKeyguardShowingUseCase = isKeyguardShowingUseCase,
+ )
+ }
+
+ @Test
+ fun `invoke - affordance is visible`() = runBlockingTest {
+ val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+ val model =
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_END,
+ model,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.contentDescriptionResourceId)
+ .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - affordance not visible while dozing`() = runBlockingTest {
+ repository.setDozing(true)
+ val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+ val model =
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_END,
+ model,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - affordance not visible when lockscreen is not showing`() = runBlockingTest {
+ repository.setKeyguardShowing(false)
+ val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+ val model =
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_END,
+ model,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - affordance is none`() = runBlockingTest {
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_START,
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ companion object {
+ private val ICON: ContainedDrawable = mock()
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
new file mode 100644
index 000000000000..00dd58e19142
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -0,0 +1,503 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveIsKeyguardShowingUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
+
+ @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+ @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+
+ private lateinit var underTest: KeyguardBottomAreaViewModel
+
+ private lateinit var affordanceRepository: FakeKeyguardQuickAffordanceRepository
+ private lateinit var repository: FakeKeyguardRepository
+ private lateinit var isDozingUseCase: ObserveIsDozingUseCase
+ private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
+ private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
+ private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+ private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+ private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
+ .thenReturn(RETURNED_BURN_IN_OFFSET)
+
+ affordanceRepository = FakeKeyguardQuickAffordanceRepository()
+ repository = FakeKeyguardRepository()
+ isDozingUseCase =
+ ObserveIsDozingUseCase(
+ repository = repository,
+ )
+ isKeyguardShowingUseCase =
+ ObserveIsKeyguardShowingUseCase(
+ repository = repository,
+ )
+ launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
+ homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+
+ underTest =
+ KeyguardBottomAreaViewModel(
+ observeQuickAffordanceUseCase =
+ ObserveKeyguardQuickAffordanceUseCase(
+ repository = affordanceRepository,
+ isDozingUseCase = isDozingUseCase,
+ isKeyguardShowingUseCase = isKeyguardShowingUseCase,
+ ),
+ onQuickAffordanceClickedUseCase =
+ OnKeyguardQuickAffordanceClickedUseCase(
+ configs =
+ FakeKeyguardQuickAffordanceConfigs(
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControlsQuickAffordanceConfig,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ quickAccessWalletAffordanceConfig,
+ qrCodeScannerAffordanceConfig,
+ ),
+ ),
+ ),
+ launchAffordanceUseCase = launchQuickAffordanceUseCase,
+ ),
+ observeBottomAreaAlphaUseCase =
+ ObserveBottomAreaAlphaUseCase(
+ repository = repository,
+ ),
+ observeIsDozingUseCase = isDozingUseCase,
+ observeAnimateBottomAreaTransitionsUseCase =
+ ObserveAnimateBottomAreaTransitionsUseCase(
+ repository = repository,
+ ),
+ observeDozeAmountUseCase =
+ ObserveDozeAmountUseCase(
+ repository = repository,
+ ),
+ observeClockPositionUseCase =
+ ObserveClockPositionUseCase(
+ repository = repository,
+ ),
+ burnInHelperWrapper = burnInHelperWrapper,
+ )
+ }
+
+ @Test
+ fun `startButton - present - not dozing - lockscreen showing - visible model - starts activity on click`() = // ktlint-disable max-line-length
+ runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(false)
+ repository.setKeyguardShowing(true)
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = testConfig,
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `endButton - present - not dozing - lockscreen showing - visible model - do nothing on click`() = // ktlint-disable max-line-length
+ runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.endButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(false)
+ repository.setKeyguardShowing(true)
+ val config =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent =
+ null, // This will cause it to tell the system that the click was handled.
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = config,
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `startButton - not present - not dozing - lockscreen showing - model is none`() =
+ runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(false)
+ repository.setKeyguardShowing(true)
+ val config =
+ TestConfig(
+ isVisible = false,
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = config,
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `startButton - present - dozing - lockscreen showing - model is none`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(true)
+ repository.setKeyguardShowing(true)
+ val config =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = TestConfig(isVisible = false),
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `startButton - present - not dozing - lockscreen not showing - model is none`() =
+ runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(false)
+ repository.setKeyguardShowing(false)
+ val config =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = TestConfig(isVisible = false),
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun animateButtonReveal() = runBlockingTest {
+ val values = mutableListOf<Boolean>()
+ val job = underTest.animateButtonReveal.onEach(values::add).launchIn(this)
+
+ repository.setAnimateDozingTransitions(true)
+ repository.setAnimateDozingTransitions(false)
+
+ assertThat(values).isEqualTo(listOf(false, true, false))
+ job.cancel()
+ }
+
+ @Test
+ fun isOverlayContainerVisible() = runBlockingTest {
+ val values = mutableListOf<Boolean>()
+ val job = underTest.isOverlayContainerVisible.onEach(values::add).launchIn(this)
+
+ repository.setDozing(true)
+ repository.setDozing(false)
+
+ assertThat(values).isEqualTo(listOf(true, false, true))
+ job.cancel()
+ }
+
+ @Test
+ fun alpha() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job = underTest.alpha.onEach(values::add).launchIn(this)
+
+ repository.setBottomAreaAlpha(0.1f)
+ repository.setBottomAreaAlpha(0.5f)
+ repository.setBottomAreaAlpha(0.2f)
+ repository.setBottomAreaAlpha(0f)
+
+ assertThat(values).isEqualTo(listOf(1f, 0.1f, 0.5f, 0.2f, 0f))
+ job.cancel()
+ }
+
+ @Test
+ fun isIndicationAreaPadded() = runBlockingTest {
+ repository.setKeyguardShowing(true)
+ val values = mutableListOf<Boolean>()
+ val job = underTest.isIndicationAreaPadded.onEach(values::add).launchIn(this)
+
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = true,
+ )
+ )
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ )
+ )
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = false,
+ )
+ )
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig =
+ TestConfig(
+ isVisible = false,
+ )
+ )
+
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initially, no button is visible so the indication area is not padded.
+ false,
+ // Once we add the first visible button, the indication area becomes padded.
+ // This
+ // continues to be true after we add the second visible button and even after we
+ // make the first button not visible anymore.
+ true,
+ // Once both buttons are not visible, the indication area is, again, not padded.
+ false,
+ )
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun indicationAreaTranslationX() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job = underTest.indicationAreaTranslationX.onEach(values::add).launchIn(this)
+
+ repository.setClockPosition(100, 100)
+ repository.setClockPosition(200, 100)
+ repository.setClockPosition(200, 200)
+ repository.setClockPosition(300, 100)
+
+ assertThat(values).isEqualTo(listOf(0f, 100f, 200f, 300f))
+ job.cancel()
+ }
+
+ @Test
+ fun indicationAreaTranslationY() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job =
+ underTest
+ .indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)
+ .onEach(values::add)
+ .launchIn(this)
+
+ val expectedTranslationValues =
+ listOf(
+ -0f, // Negative 0 - apparently there's a difference in floating point arithmetic -
+ // FML
+ setDozeAmountAndCalculateExpectedTranslationY(0.1f),
+ setDozeAmountAndCalculateExpectedTranslationY(0.2f),
+ setDozeAmountAndCalculateExpectedTranslationY(0.5f),
+ setDozeAmountAndCalculateExpectedTranslationY(1f),
+ )
+
+ assertThat(values).isEqualTo(expectedTranslationValues)
+ job.cancel()
+ }
+
+ private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+ repository.setDozeAmount(dozeAmount)
+ return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
+ }
+
+ private suspend fun setUpQuickAffordanceModel(
+ position: KeyguardQuickAffordancePosition,
+ testConfig: TestConfig,
+ ): KClass<*> {
+ val config =
+ when (position) {
+ KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
+ KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
+ }
+
+ affordanceRepository.setModel(
+ position = position,
+ model =
+ if (testConfig.isVisible) {
+ if (testConfig.intent != null) {
+ config.onClickedResult =
+ KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent = testConfig.intent,
+ canShowWhileLocked = testConfig.canShowWhileLocked,
+ )
+ }
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = config::class,
+ icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ )
+ return config::class
+ }
+
+ private fun assertQuickAffordanceViewModel(
+ viewModel: KeyguardQuickAffordanceViewModel?,
+ testConfig: TestConfig,
+ configKey: KClass<*>,
+ ) {
+ checkNotNull(viewModel)
+ assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
+ if (testConfig.isVisible) {
+ assertThat(viewModel.icon).isEqualTo(testConfig.icon)
+ viewModel.onClicked.invoke(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = configKey,
+ animationController = animationController,
+ )
+ )
+ testConfig.intent?.let { intent ->
+ assertThat(launchQuickAffordanceUseCase.invocations)
+ .isEqualTo(
+ listOf(
+ FakeLaunchKeyguardQuickAffordanceUseCase.Invocation(
+ intent = intent,
+ canShowWhileLocked = testConfig.canShowWhileLocked,
+ animationController = animationController,
+ )
+ )
+ )
+ }
+ ?: run { assertThat(launchQuickAffordanceUseCase.invocations).isEmpty() }
+ } else {
+ assertThat(viewModel.isVisible).isFalse()
+ }
+ }
+
+ private data class TestConfig(
+ val isVisible: Boolean,
+ val icon: ContainedDrawable? = null,
+ val canShowWhileLocked: Boolean = false,
+ val intent: Intent? = null,
+ ) {
+ init {
+ check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
+ }
+ }
+
+ companion object {
+ private const val DEFAULT_BURN_IN_OFFSET = 5
+ private const val RETURNED_BURN_IN_OFFSET = 3
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
new file mode 100644
index 000000000000..4abb973817b1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -0,0 +1,167 @@
+package com.android.systemui.log
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnitRunner
+
+@SmallTest
+@RunWith(MockitoJUnitRunner::class)
+class LogBufferTest : SysuiTestCase() {
+ private lateinit var buffer: LogBuffer
+
+ private lateinit var outputWriter: StringWriter
+
+ @Mock
+ private lateinit var logcatEchoTracker: LogcatEchoTracker
+
+ @Before
+ fun setup() {
+ outputWriter = StringWriter()
+ buffer = createBuffer(UNBOUNDED_STACK_TRACE, NESTED_TRACE_DEPTH)
+ }
+
+ private fun createBuffer(rootTraceDepth: Int, nestedTraceDepth: Int): LogBuffer {
+ return LogBuffer("TestBuffer",
+ 1,
+ logcatEchoTracker,
+ false,
+ rootStackTraceDepth = rootTraceDepth,
+ nestedStackTraceDepth = nestedTraceDepth)
+ }
+
+ @Test
+ fun log_shouldSaveLogToBuffer() {
+ buffer.log("Test", LogLevel.INFO, "Some test message")
+
+ val dumpedString = dumpBuffer()
+
+ assertThat(dumpedString).contains("Some test message")
+ }
+
+ @Test
+ fun log_shouldRotateIfLogBufferIsFull() {
+ buffer.log("Test", LogLevel.INFO, "This should be rotated")
+ buffer.log("Test", LogLevel.INFO, "New test message")
+
+ val dumpedString = dumpBuffer()
+
+ assertThat(dumpedString).contains("New test message")
+ }
+
+ @Test
+ fun dump_writesExceptionAndStacktraceLimitedToGivenDepth() {
+ buffer = createBuffer(rootTraceDepth = 2, nestedTraceDepth = -1)
+ // stack trace depth of 5
+ val exception = createTestException("Exception message", "TestClass", 5)
+ buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+ val dumpedString = dumpBuffer()
+
+ // logs are limited to depth 2
+ assertThat(dumpedString).contains("E Tag: Extra message")
+ assertThat(dumpedString).contains("E Tag: java.lang.RuntimeException: Exception message")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)")
+ assertThat(dumpedString)
+ .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)")
+ }
+
+ @Test
+ fun dump_writesCauseAndStacktraceLimitedToGivenDepth() {
+ buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2)
+ val exception = createTestException("Exception message",
+ "TestClass",
+ 1,
+ cause = createTestException("The real cause!", "TestClass", 5))
+ buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+ val dumpedString = dumpBuffer()
+
+ // logs are limited to depth 2
+ assertThat(dumpedString)
+ .contains("E Tag: Caused by: java.lang.RuntimeException: The real cause!")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)")
+ assertThat(dumpedString)
+ .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)")
+ }
+
+ @Test
+ fun dump_writesSuppressedExceptionAndStacktraceLimitedToGivenDepth() {
+ buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2)
+ val exception = RuntimeException("Root exception message")
+ exception.addSuppressed(
+ createTestException(
+ "First suppressed exception",
+ "FirstClass",
+ 5,
+ createTestException("Cause of suppressed exp", "ThirdClass", 5)
+ ))
+ exception.addSuppressed(
+ createTestException("Second suppressed exception", "SecondClass", 5))
+ buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+ val dumpedStr = dumpBuffer()
+
+ // logs are limited to depth 2
+ // first suppressed exception
+ assertThat(dumpedStr)
+ .contains("E Tag: Suppressed: " +
+ "java.lang.RuntimeException: First suppressed exception")
+ assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:1)")
+ assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:2)")
+ assertThat(dumpedStr)
+ .doesNotContain("E Tag: \tat FirstClass.TestMethod(FirstClass.java:3)")
+
+ assertThat(dumpedStr)
+ .contains("E Tag: Caused by: java.lang.RuntimeException: Cause of suppressed exp")
+ assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:1)")
+ assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:2)")
+ assertThat(dumpedStr)
+ .doesNotContain("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:3)")
+
+ // second suppressed exception
+ assertThat(dumpedStr)
+ .contains("E Tag: Suppressed: " +
+ "java.lang.RuntimeException: Second suppressed exception")
+ assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:1)")
+ assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:2)")
+ assertThat(dumpedStr)
+ .doesNotContain("E Tag: \tat SecondClass.TestMethod(SecondClass.java:3)")
+ }
+
+ private fun createTestException(
+ message: String,
+ errorClass: String,
+ stackTraceLength: Int,
+ cause: Throwable? = null
+ ): Exception {
+ val exception = RuntimeException(message, cause)
+ exception.stackTrace = createStackTraceElements(errorClass, stackTraceLength)
+ return exception
+ }
+
+ private fun dumpBuffer(): String {
+ buffer.dump(PrintWriter(outputWriter), tailLength = 100)
+ return outputWriter.toString()
+ }
+
+ private fun createStackTraceElements(
+ errorClass: String,
+ stackTraceLength: Int
+ ): Array<StackTraceElement> {
+ return (1..stackTraceLength).map { lineNumber ->
+ StackTraceElement(errorClass,
+ "TestMethod",
+ "$errorClass.java",
+ lineNumber)
+ }.toTypedArray()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 2eb478303cb2..f1330682b0e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -21,7 +21,6 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.os.PowerManager
-import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
@@ -31,7 +30,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -53,7 +53,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
class MediaTttChipControllerCommonTest : SysuiTestCase() {
- private lateinit var controllerCommon: MediaTttChipControllerCommon<ChipInfo>
+ private lateinit var controllerCommon: TestControllerCommon
private lateinit var fakeClock: FakeSystemClock
private lateinit var fakeExecutor: FakeExecutor
@@ -68,12 +68,12 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
+ private lateinit var configurationController: ConfigurationController
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var viewUtil: ViewUtil
@Mock
- private lateinit var tapGestureDetector: TapGestureDetector
- @Mock
private lateinit var powerManager: PowerManager
@Before
@@ -98,35 +98,49 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
fakeExecutor = FakeExecutor(fakeClock)
controllerCommon = TestControllerCommon(
- context,
- logger,
- windowManager,
- viewUtil,
- fakeExecutor,
- accessibilityManager,
- tapGestureDetector,
- powerManager
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ fakeExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
)
}
@Test
- fun displayChip_chipAddedAndGestureDetectionStartedAndScreenOn() {
+ fun displayChip_chipAdded() {
controllerCommon.displayChip(getState())
verify(windowManager).addView(any(), any())
- verify(tapGestureDetector).addOnGestureDetectedCallback(any(), any())
+ }
+
+ @Test
+ fun displayChip_screenOff_screenWakes() {
+ whenever(powerManager.isScreenOn).thenReturn(false)
+
+ controllerCommon.displayChip(getState())
+
verify(powerManager).wakeUp(any(), any(), any())
}
@Test
- fun displayChip_twice_chipAndGestureDetectionNotAddedTwice() {
+ fun displayChip_screenAlreadyOn_screenNotWoken() {
+ whenever(powerManager.isScreenOn).thenReturn(true)
+
+ controllerCommon.displayChip(getState())
+
+ verify(powerManager, never()).wakeUp(any(), any(), any())
+ }
+
+ @Test
+ fun displayChip_twice_chipNotAddedTwice() {
controllerCommon.displayChip(getState())
reset(windowManager)
- reset(tapGestureDetector)
controllerCommon.displayChip(getState())
verify(windowManager, never()).addView(any(), any())
- verify(tapGestureDetector, never()).addOnGestureDetectedCallback(any(), any())
}
@Test
@@ -186,7 +200,20 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
}
@Test
- fun removeChip_chipRemovedAndGestureDetectionStoppedAndRemovalLogged() {
+ fun displayScaleChange_chipReinflatedWithMostRecentState() {
+ controllerCommon.displayChip(getState(name = "First name"))
+ controllerCommon.displayChip(getState(name = "Second name"))
+ reset(windowManager)
+
+ getConfigurationListener().onDensityOrFontScaleChanged()
+
+ verify(windowManager).removeView(any())
+ verify(windowManager).addView(any(), any())
+ assertThat(controllerCommon.mostRecentChipInfo?.name).isEqualTo("Second name")
+ }
+
+ @Test
+ fun removeChip_chipRemovedAndRemovalLogged() {
// First, add the chip
controllerCommon.displayChip(getState())
@@ -195,7 +222,6 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
controllerCommon.removeChip(reason)
verify(windowManager).removeView(any())
- verify(tapGestureDetector).removeOnGestureDetectedCallback(any())
verify(logger).logChipRemoval(reason)
}
@@ -307,41 +333,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(ICON_SIZE)
}
- @Test
- fun tapGestureDetected_outsideViewBounds_viewHidden() {
- controllerCommon.displayChip(getState())
- whenever(viewUtil.touchIsWithinView(any(), any(), any())).thenReturn(false)
- val gestureCallbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
- verify(tapGestureDetector).addOnGestureDetectedCallback(
- any(), capture(gestureCallbackCaptor)
- )
- val callback = gestureCallbackCaptor.value!!
-
- callback.invoke(
- MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
- )
-
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun tapGestureDetected_insideViewBounds_viewNotHidden() {
- controllerCommon.displayChip(getState())
- whenever(viewUtil.touchIsWithinView(any(), any(), any())).thenReturn(true)
- val gestureCallbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
- verify(tapGestureDetector).addOnGestureDetectedCallback(
- any(), capture(gestureCallbackCaptor)
- )
- val callback = gestureCallbackCaptor.value!!
-
- callback.invoke(
- MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
- )
-
- verify(windowManager, never()).removeView(any())
- }
-
- private fun getState() = ChipInfo()
+ private fun getState(name: String = "name") = ChipInfo(name)
private fun getChipView(): ViewGroup {
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -351,6 +343,12 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
+ private fun getConfigurationListener(): ConfigurationListener {
+ val callbackCaptor = argumentCaptor<ConfigurationListener>()
+ verify(configurationController).addCallback(capture(callbackCaptor))
+ return callbackCaptor.value
+ }
+
inner class TestControllerCommon(
context: Context,
logger: MediaTttLogger,
@@ -358,8 +356,8 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
viewUtil: ViewUtil,
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
- tapGestureDetector: TapGestureDetector,
- powerManager: PowerManager
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
) : MediaTttChipControllerCommon<ChipInfo>(
context,
logger,
@@ -367,16 +365,21 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
viewUtil,
mainExecutor,
accessibilityManager,
- tapGestureDetector,
+ configurationController,
powerManager,
- R.layout.media_ttt_chip
+ R.layout.media_ttt_chip,
) {
+ var mostRecentChipInfo: ChipInfo? = null
+
override val windowLayoutParams = commonWindowLayoutParams
- override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {}
+ override fun updateChipView(newChipInfo: ChipInfo, currentChipView: ViewGroup) {
+ super.updateChipView(newChipInfo, currentChipView)
+ mostRecentChipInfo = newChipInfo
+ }
override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE
}
- inner class ChipInfo : ChipInfoCommon {
+ inner class ChipInfo(val name: String) : ChipInfoCommon {
override fun getTimeoutMs() = 1L
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index bbc564193080..dbc5f7c7041e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -36,7 +36,7 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -68,6 +68,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
+ private lateinit var configurationController: ConfigurationController
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var windowManager: WindowManager
@@ -103,7 +105,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
viewUtil,
FakeExecutor(FakeSystemClock()),
accessibilityManager,
- TapGestureDetector(context),
+ configurationController,
powerManager,
Handler.getMain(),
receiverUiEventLogger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 7ca0cd34ab26..cd8ee732e113 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -37,13 +37,12 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
-
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -52,8 +51,8 @@ import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -70,6 +69,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
+ private lateinit var configurationController: ConfigurationController
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var windowManager: WindowManager
@@ -112,7 +113,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
viewUtil,
fakeExecutor,
accessibilityManager,
- TapGestureDetector(context),
+ configurationController,
powerManager,
senderUiEventLogger
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 4e3bdea4edfa..b0cf0612b2d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -41,6 +41,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
@@ -96,7 +97,8 @@ public class NavigationBarControllerTest extends SysuiTestCase {
mock(AutoHideController.class),
mock(LightBarController.class),
Optional.of(mock(Pip.class)),
- Optional.of(mock(BackAnimation.class))));
+ Optional.of(mock(BackAnimation.class)),
+ mock(FeatureFlags.class)));
initializeNavigationBars();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index bd794d6813ec..09ce37b4ac62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -16,19 +16,26 @@
package com.android.systemui.qs.carrier;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.content.Intent;
import android.os.Handler;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -50,6 +57,7 @@ import com.android.systemui.utils.os.FakeHandler;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -85,6 +93,7 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
private QSCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
private FakeSlotIndexResolver mSlotIndexResolver;
+ private ClickListenerTextView mNoCarrierTextView;
@Before
public void setup() throws Exception {
@@ -108,7 +117,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
.when(mCarrierTextManager)
.setListening(any(CarrierTextManager.CarrierTextCallback.class));
- when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext));
+ mNoCarrierTextView = new ClickListenerTextView(mContext);
+ when(mQSCarrierGroup.getNoSimTextView()).thenReturn(mNoCarrierTextView);
when(mQSCarrierGroup.getCarrier1View()).thenReturn(mQSCarrier1);
when(mQSCarrierGroup.getCarrier2View()).thenReturn(mQSCarrier2);
when(mQSCarrierGroup.getCarrier3View()).thenReturn(mQSCarrier3);
@@ -376,6 +386,47 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
verify(mOnSingleCarrierChangedListener, never()).onSingleCarrierChanged(anyBoolean());
}
+ @Test
+ public void testOnlyInternalViewsHaveClickableListener() {
+ ArgumentCaptor<View.OnClickListener> captor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+
+ verify(mQSCarrier1).setOnClickListener(captor.capture());
+ verify(mQSCarrier2).setOnClickListener(captor.getValue());
+ verify(mQSCarrier3).setOnClickListener(captor.getValue());
+
+ assertThat(mNoCarrierTextView.getOnClickListener()).isSameInstanceAs(captor.getValue());
+ verify(mQSCarrierGroup, never()).setOnClickListener(any());
+ }
+
+ @Test
+ public void testOnClickListenerDoesntStartActivityIfViewNotVisible() {
+ ArgumentCaptor<View.OnClickListener> captor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+
+ verify(mQSCarrier1).setOnClickListener(captor.capture());
+ when(mQSCarrier1.isVisibleToUser()).thenReturn(false);
+
+ captor.getValue().onClick(mQSCarrier1);
+ verifyZeroInteractions(mActivityStarter);
+ }
+
+ @Test
+ public void testOnClickListenerLaunchesActivityIfViewVisible() {
+ ArgumentCaptor<View.OnClickListener> listenerCaptor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ verify(mQSCarrier1).setOnClickListener(listenerCaptor.capture());
+ when(mQSCarrier1.isVisibleToUser()).thenReturn(true);
+
+ listenerCaptor.getValue().onClick(mQSCarrier1);
+ verify(mActivityStarter)
+ .postStartActivityDismissingKeyguard(intentCaptor.capture(), anyInt());
+ assertThat(intentCaptor.getValue().getAction())
+ .isEqualTo(Settings.ACTION_WIRELESS_SETTINGS);
+ }
+
private class FakeSlotIndexResolver implements QSCarrierGroupController.SlotIndexResolver {
public boolean overrideInvalid;
@@ -384,4 +435,22 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
return overrideInvalid ? -1 : subscriptionId;
}
}
+
+ private class ClickListenerTextView extends TextView {
+ View.OnClickListener mListener = null;
+
+ ClickListenerTextView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener l) {
+ super.setOnClickListener(l);
+ mListener = l;
+ }
+
+ View.OnClickListener getOnClickListener() {
+ return mListener;
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index e2673bb74084..c9405c890278 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -97,6 +97,10 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
@@ -373,6 +377,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
private ViewParent mViewParent;
@Mock
private ViewTreeObserver mViewTreeObserver;
+ @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
+ @Mock private SetClockPositionUseCase mSetClockPositionUseCase;
+ @Mock private SetKeyguardBottomAreaAlphaUseCase mSetKeyguardBottomAreaAlphaUseCase;
+ @Mock private SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
@@ -564,7 +573,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mUnlockedScreenOffAnimationController,
mShadeTransitionController,
mSystemClock,
- mock(CameraGestureHelper.class));
+ mock(CameraGestureHelper.class),
+ () -> mKeyguardBottomAreaViewModel,
+ () -> mSetClockPositionUseCase,
+ () -> mSetKeyguardBottomAreaAlphaUseCase,
+ () -> mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
() -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index d61989fc3128..131eac668af3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -110,6 +110,7 @@ class ClockRegistryTest : SysuiTestCase() {
get() = settingValue
set(value) { settingValue = value }
}
+ registry.isEnabled = true
verify(mockPluginManager)
.addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java))
@@ -129,13 +130,16 @@ class ClockRegistryTest : SysuiTestCase() {
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
val list = registry.getClocks()
- assertEquals(list, listOf(
- ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
- ClockMetadata("clock_1", "clock 1"),
- ClockMetadata("clock_2", "clock 2"),
- ClockMetadata("clock_3", "clock 3"),
- ClockMetadata("clock_4", "clock 4")
- ))
+ assertEquals(
+ list,
+ listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2"),
+ ClockMetadata("clock_3", "clock 3"),
+ ClockMetadata("clock_4", "clock 4")
+ )
+ )
}
@Test
@@ -157,11 +161,14 @@ class ClockRegistryTest : SysuiTestCase() {
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
val list = registry.getClocks()
- assertEquals(list, listOf(
- ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
- ClockMetadata("clock_1", "clock 1"),
- ClockMetadata("clock_2", "clock 2")
- ))
+ assertEquals(
+ list,
+ listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2")
+ )
+ )
assertEquals(registry.createExampleClock("clock_1"), mockClock)
assertEquals(registry.createExampleClock("clock_2"), mockClock)
@@ -221,7 +228,7 @@ class ClockRegistryTest : SysuiTestCase() {
pluginListener.onPluginConnected(plugin2, mockContext)
var changeCallCount = 0
- registry.registerClockChangeListener({ changeCallCount++ })
+ registry.registerClockChangeListener { changeCallCount++ }
pluginListener.onPluginDisconnected(plugin1)
assertEquals(0, changeCallCount)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index b2ef7b37f63f..0fd2e380cf2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -30,13 +30,11 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
import org.junit.Ignore;
@@ -55,11 +53,8 @@ import org.mockito.MockitoAnnotations;
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class NonPhoneDependencyTest extends SysuiTestCase {
@Mock private NotificationPresenter mPresenter;
- @Mock private NotifStackController mStackController;
@Mock private NotificationListContainer mListContainer;
- @Mock
- private NotificationEntryListener mEntryListener;
- @Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private NotificationEntryListener mEntryListener;
@Mock private RemoteInputController.Delegate mDelegate;
@Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
@Mock private CheckSaveListener mCheckSaveListener;
@@ -79,25 +74,20 @@ public class NonPhoneDependencyTest extends SysuiTestCase {
mDependency.injectMockDependency(ShadeController.class);
NotificationEntryManager entryManager = Dependency.get(NotificationEntryManager.class);
NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
- NotificationListener notificationListener = Dependency.get(NotificationListener.class);
NotificationLogger notificationLogger = Dependency.get(NotificationLogger.class);
NotificationMediaManager mediaManager = Dependency.get(NotificationMediaManager.class);
NotificationRemoteInputManager remoteInputManager =
Dependency.get(NotificationRemoteInputManager.class);
NotificationLockscreenUserManager lockscreenUserManager =
Dependency.get(NotificationLockscreenUserManager.class);
- NotificationViewHierarchyManager viewHierarchyManager =
- Dependency.get(NotificationViewHierarchyManager.class);
- entryManager.setUpWithPresenter(mPresenter);
entryManager.addNotificationEntryListener(mEntryListener);
gutsManager.setUpWithPresenter(mPresenter, mListContainer,
- mCheckSaveListener, mOnSettingsClickListener);
+ mOnSettingsClickListener);
notificationLogger.setUpWithContainer(mListContainer);
mediaManager.setUpWithPresenter(mPresenter);
remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback,
mDelegate);
lockscreenUserManager.setUpWithPresenter(mPresenter);
- viewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
TestableLooper.get(this).processAllMessages();
assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
deleted file mode 100644
index 407044b5378a..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ /dev/null
@@ -1,375 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.util.List;
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
- @Mock private NotificationPresenter mPresenter;
- @Mock private NotifStackController mStackController;
- @Spy private FakeListContainer mListContainer = new FakeListContainer();
-
- // Dependency mocks:
- @Mock private FeatureFlags mFeatureFlags;
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
- @Mock private NotificationEntryManager mEntryManager;
- @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
- @Mock private NotificationGroupManagerLegacy mGroupManager;
- @Mock private VisualStabilityManager mVisualStabilityManager;
-
- private TestableLooper mTestableLooper;
- private Handler mHandler;
- private NotificationViewHierarchyManager mViewHierarchyManager;
- private NotificationTestHelper mHelper;
- private boolean mMadeReentrantCall = false;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mTestableLooper = TestableLooper.get(this);
- allowTestableLooperAsMainThread();
- mHandler = Handler.createAsync(mTestableLooper.getLooper());
-
- mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
- mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
- mLockscreenUserManager);
- mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
- mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
- when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true);
- when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true);
-
- when(mNotifPipelineFlags.checkLegacyPipelineEnabled()).thenReturn(true);
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
-
- mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
-
- mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
- mHandler, mFeatureFlags, mLockscreenUserManager, mGroupManager,
- mVisualStabilityManager,
- mock(StatusBarStateControllerImpl.class), mEntryManager,
- mock(KeyguardBypassController.class),
- Optional.of(mock(Bubbles.class)),
- mock(DynamicPrivacyController.class),
- mock(DynamicChildBindController.class),
- mock(LowPriorityInflationHelper.class),
- mock(AssistantFeedbackController.class),
- mNotifPipelineFlags,
- mock(KeyguardUpdateMonitor.class),
- mock(KeyguardStateController.class));
- mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
- }
-
- private NotificationEntry createEntry() throws Exception {
- ExpandableNotificationRow row = mHelper.createRow();
- return row.getEntry();
- }
-
- @Test
- public void testNotificationsBecomingBundled() throws Exception {
- // Tests 3 top level notifications becoming a single bundled notification with |entry0| as
- // the summary.
- NotificationEntry entry0 = createEntry();
- NotificationEntry entry1 = createEntry();
- NotificationEntry entry2 = createEntry();
-
- // Set up the prior state to look like three top level notifications.
- mListContainer.addContainerView(entry0.getRow());
- mListContainer.addContainerView(entry1.getRow());
- mListContainer.addContainerView(entry2.getRow());
- when(mEntryManager.getVisibleNotifications()).thenReturn(
- Lists.newArrayList(entry0, entry1, entry2));
-
- // Set up group manager to report that they should be bundled now.
- when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry1)).thenReturn(true);
- when(mGroupManager.isChildInGroup(entry2)).thenReturn(true);
- when(mGroupManager.getGroupSummary(entry1)).thenReturn(entry0);
- when(mGroupManager.getGroupSummary(entry2)).thenReturn(entry0);
-
- // Run updateNotifications - the view hierarchy should be reorganized.
- mViewHierarchyManager.updateNotificationViews();
-
- verify(mListContainer).notifyGroupChildAdded(entry1.getRow());
- verify(mListContainer).notifyGroupChildAdded(entry2.getRow());
- assertTrue(Lists.newArrayList(entry0.getRow()).equals(mListContainer.mRows));
- }
-
- @Test
- public void testNotificationsBecomingUnbundled() throws Exception {
- // Tests a bundled notification becoming three top level notifications.
- NotificationEntry entry0 = createEntry();
- NotificationEntry entry1 = createEntry();
- NotificationEntry entry2 = createEntry();
- entry0.getRow().addChildNotification(entry1.getRow());
- entry0.getRow().addChildNotification(entry2.getRow());
-
- // Set up the prior state to look like one top level notification.
- mListContainer.addContainerView(entry0.getRow());
- when(mEntryManager.getVisibleNotifications()).thenReturn(
- Lists.newArrayList(entry0, entry1, entry2));
-
- // Set up group manager to report that they should not be bundled now.
- when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry1)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry2)).thenReturn(false);
-
- // Run updateNotifications - the view hierarchy should be reorganized.
- mViewHierarchyManager.updateNotificationViews();
-
- verify(mListContainer).notifyGroupChildRemoved(
- entry1.getRow(), entry0.getRow().getChildrenContainer());
- verify(mListContainer).notifyGroupChildRemoved(
- entry2.getRow(), entry0.getRow().getChildrenContainer());
- assertTrue(
- Lists.newArrayList(entry0.getRow(), entry1.getRow(), entry2.getRow())
- .equals(mListContainer.mRows));
- }
-
- @Test
- public void testNotificationsBecomingSuppressed() throws Exception {
- // Tests two top level notifications becoming a suppressed summary and a child.
- NotificationEntry entry0 = createEntry();
- NotificationEntry entry1 = createEntry();
- entry0.getRow().addChildNotification(entry1.getRow());
-
- // Set up the prior state to look like a top level notification.
- mListContainer.addContainerView(entry0.getRow());
- when(mEntryManager.getVisibleNotifications()).thenReturn(
- Lists.newArrayList(entry0, entry1));
-
- // Set up group manager to report a suppressed summary now.
- when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry1)).thenReturn(false);
- when(mGroupManager.isSummaryOfSuppressedGroup(entry0.getSbn())).thenReturn(true);
-
- // Run updateNotifications - the view hierarchy should be reorganized.
- mViewHierarchyManager.updateNotificationViews();
-
- verify(mListContainer).notifyGroupChildRemoved(
- entry1.getRow(), entry0.getRow().getChildrenContainer());
- assertTrue(Lists.newArrayList(entry0.getRow(), entry1.getRow()).equals(mListContainer.mRows));
- assertEquals(View.GONE, entry0.getRow().getVisibility());
- assertEquals(View.VISIBLE, entry1.getRow().getVisibility());
- }
-
- @Test
- public void testReentrantCallsToOnDynamicPrivacyChangedPostForLater() {
- // GIVEN a ListContainer that will make a re-entrant call to updateNotificationViews()
- mMadeReentrantCall = false;
- doAnswer((invocation) -> {
- if (!mMadeReentrantCall) {
- mMadeReentrantCall = true;
- mViewHierarchyManager.onDynamicPrivacyChanged();
- }
- return null;
- }).when(mListContainer).onNotificationViewUpdateFinished();
-
- // WHEN we call updateNotificationViews()
- mViewHierarchyManager.updateNotificationViews();
-
- // THEN onNotificationViewUpdateFinished() is only called once
- verify(mListContainer).onNotificationViewUpdateFinished();
-
- // WHEN we drain the looper
- mTestableLooper.processAllMessages();
-
- // THEN updateNotificationViews() is called a second time (for the reentrant call)
- verify(mListContainer, times(2)).onNotificationViewUpdateFinished();
- }
-
- @Test
- public void testMultipleReentrantCallsToOnDynamicPrivacyChangedOnlyPostOnce() {
- // GIVEN a ListContainer that will make many re-entrant calls to updateNotificationViews()
- mMadeReentrantCall = false;
- doAnswer((invocation) -> {
- if (!mMadeReentrantCall) {
- mMadeReentrantCall = true;
- mViewHierarchyManager.onDynamicPrivacyChanged();
- mViewHierarchyManager.onDynamicPrivacyChanged();
- mViewHierarchyManager.onDynamicPrivacyChanged();
- mViewHierarchyManager.onDynamicPrivacyChanged();
- }
- return null;
- }).when(mListContainer).onNotificationViewUpdateFinished();
-
- // WHEN we call updateNotificationViews() and drain the looper
- mViewHierarchyManager.updateNotificationViews();
- verify(mListContainer).onNotificationViewUpdateFinished();
- clearInvocations(mListContainer);
- mTestableLooper.processAllMessages();
-
- // THEN updateNotificationViews() is called only one more time
- verify(mListContainer).onNotificationViewUpdateFinished();
- }
-
- private class FakeListContainer implements NotificationListContainer {
- final LinearLayout mLayout = new LinearLayout(mContext);
- final List<View> mRows = Lists.newArrayList();
-
- @Override
- public void setChildTransferInProgress(boolean childTransferInProgress) {}
-
- @Override
- public void changeViewPosition(ExpandableView child, int newIndex) {
- mRows.remove(child);
- mRows.add(newIndex, child);
- }
-
- @Override
- public void notifyGroupChildAdded(ExpandableView row) {}
-
- @Override
- public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {}
-
- @Override
- public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {}
-
- @Override
- public void generateChildOrderChangedEvent() {}
-
- @Override
- public void onReset(ExpandableView view) {}
-
- @Override
- public int getContainerChildCount() {
- return mRows.size();
- }
-
- @Override
- public View getContainerChildAt(int i) {
- return mRows.get(i);
- }
-
- @Override
- public void removeContainerView(View v) {
- mLayout.removeView(v);
- mRows.remove(v);
- }
-
- @Override
- public void setNotificationActivityStarter(
- NotificationActivityStarter notificationActivityStarter) {}
-
- @Override
- public void addContainerView(View v) {
- mLayout.addView(v);
- mRows.add(v);
- }
-
- @Override
- public void addContainerViewAt(View v, int index) {
- mLayout.addView(v, index);
- mRows.add(index, v);
- }
-
- @Override
- public void setMaxDisplayedNotifications(int maxNotifications) {
- }
-
- @Override
- public ViewGroup getViewParentForNotification(NotificationEntry entry) {
- return null;
- }
-
- @Override
- public void onHeightChanged(ExpandableView view, boolean animate) {}
-
- @Override
- public void resetExposedMenuView(boolean animate, boolean force) {}
-
- @Override
- public NotificationSwipeActionHelper getSwipeActionHelper() {
- return null;
- }
-
- @Override
- public void cleanUpViewStateForEntry(NotificationEntry entry) { }
-
- @Override
- public boolean isInVisibleLocation(NotificationEntry entry) {
- return true;
- }
-
- @Override
- public void setChildLocationsChangedListener(
- NotificationLogger.OnChildLocationsChangedListener listener) {}
-
- @Override
- public boolean hasPulsingNotifications() {
- return false;
- }
-
- @Override
- public void onNotificationViewUpdateFinished() { }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index aeef6b04108e..842f057b96fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -77,10 +77,8 @@ import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -91,7 +89,6 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationEntryManagerInflationTest;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -115,9 +112,7 @@ import java.util.List;
import java.util.Set;
/**
- * Unit tests for {@link NotificationEntryManager}. This test will not test any interactions with
- * inflation. Instead, for functional inflation tests, see
- * {@link NotificationEntryManagerInflationTest}.
+ * Unit tests for {@link NotificationEntryManager}.
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -205,7 +200,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
mStats = defaultStats(mEntry);
mSbn = mEntry.getSbn();
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
mEntryManager = new NotificationEntryManager(
mLogger,
mGroupManager,
@@ -214,7 +208,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
() -> mRemoteInputManager,
mLeakDetector,
mStatusBarService,
- NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
mock(DumpManager.class),
mBgExecutor
);
@@ -230,7 +223,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
mock(PeopleNotificationIdentifier.class),
mock(HighPriorityProvider.class),
mEnvironment));
- mEntryManager.setUpWithPresenter(mPresenter);
mEntryManager.addNotificationEntryListener(mEntryListener);
mEntryManager.addCollectionListener(mNotifCollectionListener);
mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);
@@ -273,17 +265,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
}
@Test
- public void testUpdateNotification_updatesUserSentiment() {
- mEntryManager.addActiveNotificationForTest(mEntry);
- setUserSentiment(
- mEntry.getKey(), Ranking.USER_SENTIMENT_NEGATIVE);
-
- mEntryManager.updateNotification(mSbn, mRankingMap);
-
- assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, mEntry.getUserSentiment());
- }
-
- @Test
public void testUpdateNotification_prePostEntryOrder() throws Exception {
TestableLooper.get(this).processAllMessages();
@@ -294,7 +275,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
// Ensure that update callbacks happen in correct order
InOrder order = inOrder(mEntryListener, mPresenter, mEntryListener);
order.verify(mEntryListener).onPreEntryUpdated(mEntry);
- order.verify(mPresenter).updateNotificationViews(any());
order.verify(mEntryListener).onPostEntryUpdated(mEntry);
}
@@ -305,7 +285,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
- verify(mPresenter).updateNotificationViews(any());
verify(mEntryListener).onEntryRemoved(
argThat(matchEntryOnKey()), any(),
eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
@@ -379,23 +358,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
}
@Test
- public void testUpdateNotificationRanking() {
- when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
- mEntry.setRow(mRow);
- mEntry.setInflationTask(mAsyncInflationTask);
- mEntryManager.addActiveNotificationForTest(mEntry);
- setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
-
- mEntryManager.updateNotificationRanking(mRankingMap);
- assertEquals(1, mEntry.getSmartActions().size());
- assertEquals("action", mEntry.getSmartActions().get(0).title);
- verify(mEntryListener).onNotificationRankingUpdated(mRankingMap);
- }
-
- @Test
public void testUpdateNotificationRanking_noChange() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
@@ -409,20 +371,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
}
@Test
- public void testUpdateNotificationRanking_rowNotInflatedYet() {
- when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
- mEntry.setRow(null);
- mEntryManager.addActiveNotificationForTest(mEntry);
- setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
-
- mEntryManager.updateNotificationRanking(mRankingMap);
- assertEquals(1, mEntry.getSmartActions().size());
- assertEquals("action", mEntry.getSmartActions().get(0).title);
- }
-
- @Test
public void testUpdateNotificationRanking_pendingNotification() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
@@ -614,31 +562,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
/* Tests annexed from NotificationDataTest go here */
@Test
- public void testChannelIsSetWhenAdded() {
- NotificationChannel nc = new NotificationChannel(
- "testId",
- "testName",
- IMPORTANCE_DEFAULT);
-
- Ranking r = new RankingBuilder()
- .setKey(mEntry.getKey())
- .setChannel(nc)
- .build();
-
- RankingMap rm = new RankingMap(new Ranking[] { r });
-
- // GIVEN: a notification is added, and the ranking updated
- mEntryManager.addActiveNotificationForTest(mEntry);
- mEntryManager.updateRanking(rm, "testReason");
-
- // THEN the notification entry better have a channel on it
- assertEquals(
- "Channel must be set when adding a notification",
- nc.getName(),
- mEntry.getChannel().getName());
- }
-
- @Test
public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() {
Notification.Builder n = new Notification.Builder(mContext, "di")
.setSmallIcon(R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index c283cec5c15c..dfa38abc1ff8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -33,7 +33,6 @@ import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -1846,103 +1845,6 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@Test
- public void stableOrderingDisregardedWithSectionChange() {
- // GIVEN the first sectioner's packages can be changed from run-to-run
- List<String> mutableSectionerPackages = new ArrayList<>();
- mutableSectionerPackages.add(PACKAGE_1);
- mListBuilder.setSectioners(asList(
- new PackageSectioner(mutableSectionerPackages, null),
- new PackageSectioner(List.of(PACKAGE_1, PACKAGE_2, PACKAGE_3), null)));
- mStabilityManager.setAllowEntryReordering(false);
-
- // WHEN the list is originally built with reordering disabled (and section changes allowed)
- addNotif(0, PACKAGE_1).setRank(1);
- addNotif(1, PACKAGE_1).setRank(5);
- addNotif(2, PACKAGE_2).setRank(2);
- addNotif(3, PACKAGE_2).setRank(3);
- addNotif(4, PACKAGE_3).setRank(4);
- dispatchBuild();
-
- // VERIFY the order and that entry reordering has not been suppressed
- verifyBuiltList(
- notif(0),
- notif(1),
- notif(2),
- notif(3),
- notif(4)
- );
- verify(mStabilityManager, never()).onEntryReorderSuppressed();
-
- // WHEN the first section now claims PACKAGE_3 notifications
- mutableSectionerPackages.add(PACKAGE_3);
- dispatchBuild();
-
- // VERIFY the re-sectioned notification is inserted at the top of the first section, because
- // it's effectively "new" and "new" things are inserted at the top of their section.
- verifyBuiltList(
- notif(4),
- notif(0),
- notif(1),
- notif(2),
- notif(3)
- );
- verify(mStabilityManager).onEntryReorderSuppressed();
- clearInvocations(mStabilityManager);
-
- // WHEN reordering is now allowed again
- mStabilityManager.setAllowEntryReordering(true);
- dispatchBuild();
-
- // VERIFY that list order changes to put the re-sectioned notification in the middle where
- // it is ranked.
- verifyBuiltList(
- notif(0),
- notif(4),
- notif(1),
- notif(2),
- notif(3)
- );
- verify(mStabilityManager, never()).onEntryReorderSuppressed();
- }
-
- @Test
- public void groupRevertingToSummaryRetainsStablePosition() {
- // GIVEN a notification group is on screen
- mStabilityManager.setAllowEntryReordering(false);
-
- // WHEN the list is originally built with reordering disabled (and section changes allowed)
- addNotif(0, PACKAGE_1).setRank(2);
- addNotif(1, PACKAGE_1).setRank(3);
- addGroupSummary(2, PACKAGE_1, "group").setRank(4);
- addGroupChild(3, PACKAGE_1, "group").setRank(5);
- addGroupChild(4, PACKAGE_1, "group").setRank(6);
- dispatchBuild();
-
- verifyBuiltList(
- notif(0),
- notif(1),
- group(
- summary(2),
- child(3),
- child(4)
- )
- );
-
- // WHEN the notification summary rank increases and children removed
- setNewRank(notif(2).entry, 1);
- mEntrySet.remove(4);
- mEntrySet.remove(3);
- dispatchBuild();
-
- // VERIFY the summary stays in the same location on rebuild
- verifyBuiltList(
- notif(0),
- notif(1),
- notif(2)
- );
- }
-
- @Test
public void testStableChildOrdering() {
// WHEN the list is originally built with reordering disabled
mStabilityManager.setAllowEntryReordering(false);
@@ -2138,7 +2040,6 @@ public class ShadeListBuilderTest extends SysuiTestCase {
private void assertOrder(String visible, String active, String expected) {
StringBuilder differenceSb = new StringBuilder();
- NotifSection section = new NotifSection(mock(NotifSectioner.class), 0);
for (char c : active.toCharArray()) {
if (visible.indexOf(c) < 0) differenceSb.append(c);
}
@@ -2147,7 +2048,6 @@ public class ShadeListBuilderTest extends SysuiTestCase {
for (int i = 0; i < visible.length(); i++) {
addNotif(i, String.valueOf(visible.charAt(i)))
.setRank(active.indexOf(visible.charAt(i)))
- .setSection(section)
.setStableIndex(i);
}
@@ -2155,7 +2055,6 @@ public class ShadeListBuilderTest extends SysuiTestCase {
for (int i = 0; i < difference.length(); i++) {
addNotif(i + visible.length(), String.valueOf(difference.charAt(i)))
.setRank(active.indexOf(difference.charAt(i)))
- .setSection(section)
.setStableIndex(-1);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 5386171eeda2..e00e20f0e46b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -118,7 +118,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
mPowerManager,
mDreamManager,
mAmbientDisplayConfiguration,
- mNotificationFilter,
mBatteryController,
mStatusBarStateController,
mKeyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
deleted file mode 100644
index bf7549a23707..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row;
-
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-
-import static junit.framework.Assert.assertNotNull;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.os.Handler;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.NotificationMessagingUtil;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaFeatureFlag;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SbnBuilder;
-import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationClicker;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
-import com.android.systemui.statusbar.notification.NotificationFilter;
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.icon.IconBuilder;
-import com.android.systemui.statusbar.notification.icon.IconManager;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
-import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
-import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
-import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.leak.LeakDetector;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.wmshell.BubblesManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Functional tests for notification inflation from {@link NotificationEntryManager}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NotificationEntryManagerInflationTest extends SysuiTestCase {
-
- private static final String TEST_TITLE = "Title";
- private static final String TEST_TEXT = "Text";
- private static final long TIMEOUT_TIME = 10000;
- private static final Runnable TIMEOUT_RUNNABLE = () -> {
- throw new RuntimeException("Timed out waiting to inflate");
- };
-
- @Mock private NotificationListener mNotificationListener;
- @Mock private NotificationPresenter mPresenter;
- @Mock private NotificationEntryManager.KeyguardEnvironment mEnvironment;
- @Mock private NotificationListContainer mListContainer;
- @Mock private NotificationEntryListener mEntryListener;
- @Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback;
- @Mock private HeadsUpManager mHeadsUpManager;
- @Mock private NotificationInterruptStateProvider mNotificationInterruptionStateProvider;
- @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
- @Mock private NotificationGutsManager mGutsManager;
- @Mock private NotificationRemoteInputManager mRemoteInputManager;
- @Mock private NotificationMediaManager mNotificationMediaManager;
- @Mock(answer = Answers.RETURNS_SELF)
- private ExpandableNotificationRowComponent.Builder mExpandableNotificationRowComponentBuilder;
- @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent;
- @Mock private KeyguardBypassController mKeyguardBypassController;
- @Mock private StatusBarStateController mStatusBarStateController;
-
- @Mock private NotificationGroupManagerLegacy mGroupMembershipManager;
- @Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
- @Mock private LeakDetector mLeakDetector;
-
- @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
- @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
- @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
- @Mock private InflatedSmartReplyState mInflatedSmartReplyState;
- @Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies;
-
- private StatusBarNotification mSbn;
- private NotificationListenerService.RankingMap mRankingMap;
- private NotificationEntryManager mEntryManager;
- private NotificationRowBinderImpl mRowBinder;
- private Handler mHandler;
- private FakeExecutor mBgExecutor;
- private RowContentBindStage mRowContentBindStage;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(SmartReplyController.class);
- mDependency.injectMockDependency(MediaOutputDialogFactory.class);
-
- mHandler = Handler.createAsync(TestableLooper.get(this).getLooper());
-
- // Add an action so heads up content views are made
- Notification.Action action = new Notification.Action.Builder(null, null, null).build();
- Notification notification = new Notification.Builder(mContext)
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle(TEST_TITLE)
- .setContentText(TEST_TEXT)
- .setActions(action)
- .build();
- mSbn = new SbnBuilder()
- .setNotification(notification)
- .build();
-
- when(mNotifPipelineFlags.checkLegacyPipelineEnabled()).thenReturn(true);
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
-
- mEntryManager = new NotificationEntryManager(
- mock(NotificationEntryManagerLogger.class),
- mGroupMembershipManager,
- mNotifPipelineFlags,
- () -> mRowBinder,
- () -> mRemoteInputManager,
- mLeakDetector,
- mock(IStatusBarService.class),
- NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
- mock(DumpManager.class),
- mBgExecutor
- );
- mEntryManager.initialize(
- mNotificationListener,
- new NotificationRankingManager(
- () -> mock(NotificationMediaManager.class),
- mGroupMembershipManager,
- mHeadsUpManager,
- mock(NotificationFilter.class),
- mock(NotificationEntryManagerLogger.class),
- mock(NotificationSectionsFeatureManager.class),
- mock(PeopleNotificationIdentifier.class),
- mock(HighPriorityProvider.class),
- mEnvironment));
-
- NotifRemoteViewCache cache = new NotifRemoteViewCacheImpl(mEntryManager);
- NotifBindPipeline pipeline = new NotifBindPipeline(
- mEntryManager,
- mock(NotifBindPipelineLogger.class),
- TestableLooper.get(this).getLooper());
- mBgExecutor = new FakeExecutor(new FakeSystemClock());
- NotificationContentInflater binder = new NotificationContentInflater(
- cache,
- mRemoteInputManager,
- mock(ConversationNotificationProcessor.class),
- mock(MediaFeatureFlag.class),
- mBgExecutor,
- new SmartReplyStateInflater() {
- @Override
- public InflatedSmartReplyState inflateSmartReplyState(NotificationEntry entry) {
- return mInflatedSmartReplyState;
- }
-
- @Override
- public InflatedSmartReplyViewHolder inflateSmartReplyViewHolder(
- Context sysuiContext, Context notifPackageContext,
- NotificationEntry entry,
- InflatedSmartReplyState existingSmartReplyState,
- InflatedSmartReplyState newSmartReplyState) {
- return mInflatedSmartReplies;
- }
- });
- mRowContentBindStage = new RowContentBindStage(
- binder,
- mock(NotifInflationErrorManager.class),
- mock(RowContentBindStageLogger.class));
- pipeline.setStage(mRowContentBindStage);
-
- ArgumentCaptor<ExpandableNotificationRow> viewCaptor =
- ArgumentCaptor.forClass(ExpandableNotificationRow.class);
- when(mExpandableNotificationRowComponentBuilder
- .expandableNotificationRow(viewCaptor.capture()))
- .thenReturn(mExpandableNotificationRowComponentBuilder);
- when(mExpandableNotificationRowComponentBuilder.build())
- .thenReturn(mExpandableNotificationRowComponent);
-
- when(mExpandableNotificationRowComponent.getExpandableNotificationRowController())
- .thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
- new ExpandableNotificationRowController(
- viewCaptor.getValue(),
- mock(ActivatableNotificationViewController.class),
- mock(RemoteInputViewSubcomponent.Factory.class),
- mock(MetricsLogger.class),
- mListContainer,
- mNotificationMediaManager,
- mock(SmartReplyConstants.class),
- mock(SmartReplyController.class),
- mock(PluginManager.class),
- new FakeSystemClock(),
- "FOOBAR",
- "FOOBAR",
- mKeyguardBypassController,
- mGroupMembershipManager,
- mGroupExpansionManager,
- mRowContentBindStage,
- mock(NotificationLogger.class),
- mHeadsUpManager,
- mPresenter,
- mStatusBarStateController,
- mGutsManager,
- true,
- null,
- new FalsingManagerFake(),
- new FalsingCollectorFake(),
- mock(FeatureFlags.class),
- mPeopleNotificationIdentifier,
- Optional.of(mock(BubblesManager.class)),
- mock(ExpandableNotificationRowDragController.class)));
-
- when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
- .thenReturn(mNotificationRowComponentBuilder);
- when(mNotificationRowComponentBuilder.build()).thenReturn(
- () -> mActivatableNotificationViewController);
-
- mRowBinder = new NotificationRowBinderImpl(
- mContext,
- new NotificationMessagingUtil(mContext),
- mRemoteInputManager,
- mLockscreenUserManager,
- pipeline,
- mRowContentBindStage,
- RowInflaterTask::new,
- mExpandableNotificationRowComponentBuilder,
- new IconManager(
- mEntryManager,
- mock(LauncherApps.class),
- new IconBuilder(mContext)),
- mock(LowPriorityInflationHelper.class),
- mNotifPipelineFlags);
-
- mEntryManager.setUpWithPresenter(mPresenter);
- mEntryManager.addNotificationEntryListener(mEntryListener);
-
- mRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback);
- mRowBinder.setNotificationClicker(mock(NotificationClicker.class));
-
- Ranking ranking = new Ranking();
- ranking.populate(
- mSbn.getKey(),
- 0,
- false,
- 0,
- 0,
- IMPORTANCE_DEFAULT,
- null,
- null,
- null,
- null,
- null,
- true,
- Ranking.USER_SENTIMENT_NEUTRAL,
- false,
- -1,
- false,
- null,
- null,
- false,
- false,
- false,
- null,
- 0,
- false
- );
- mRankingMap = new NotificationListenerService.RankingMap(new Ranking[] {ranking});
-
- TestableLooper.get(this).processAllMessages();
- }
-
- @After
- public void cleanUp() {
- // Don't leave anything on main thread
- TestableLooper.get(this).processAllMessages();
- }
-
- @Test
- public void testAddNotification() {
- // WHEN a notification is added
- mEntryManager.addNotification(mSbn, mRankingMap);
- ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
- NotificationEntry.class);
- verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
- NotificationEntry entry = entryCaptor.getValue();
-
- waitForInflation();
-
- // THEN the notification has its row inflated
- assertNotNull(entry.getRow());
- assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
-
- // THEN inflation callbacks are called
- verify(mBindCallback).onBindRow(entry.getRow());
- verify(mEntryListener, never()).onInflationError(any(), any());
- verify(mEntryListener).onEntryInflated(entry);
- verify(mEntryListener).onNotificationAdded(entry);
-
- // THEN the notification is active
- assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
-
- // THEN we update the presenter
- verify(mPresenter).updateNotificationViews(any());
- }
-
- @Test
- public void testUpdateNotification() {
- // GIVEN a notification already added
- mEntryManager.addNotification(mSbn, mRankingMap);
- ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
- NotificationEntry.class);
- verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
- NotificationEntry entry = entryCaptor.getValue();
- waitForInflation();
-
- Mockito.reset(mEntryListener);
- Mockito.reset(mPresenter);
-
- // WHEN the notification is updated
- mEntryManager.updateNotification(mSbn, mRankingMap);
-
- waitForInflation();
-
- // THEN the notification has its row and inflated
- assertNotNull(entry.getRow());
-
- // THEN inflation callbacks are called
- verify(mEntryListener, never()).onInflationError(any(), any());
- verify(mEntryListener).onEntryReinflated(entry);
-
- // THEN we update the presenter
- verify(mPresenter).updateNotificationViews(any());
- }
-
- @Test
- public void testContentViewInflationDuringRowInflationInflatesCorrectViews() {
- // GIVEN a notification is added and the row is inflating
- mEntryManager.addNotification(mSbn, mRankingMap);
- ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
- NotificationEntry.class);
- verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
- NotificationEntry entry = entryCaptor.getValue();
-
- // WHEN we try to bind a content view
- mRowContentBindStage.getStageParams(entry).requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
- mRowContentBindStage.requestRebind(entry, null);
-
- waitForInflation();
-
- // THEN the notification has its row and all relevant content views inflated
- assertNotNull(entry.getRow());
- assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
- assertNotNull(entry.getRow().getPrivateLayout().getHeadsUpChild());
- }
-
- /**
- * Wait for inflation to finish.
- *
- * A few things to note
- * 1) Row inflation is done via {@link AsyncLayoutInflater} on its own background thread that
- * calls back to main thread which is why we wait on main thread.
- * 2) Row *content* inflation is done on the {@link FakeExecutor} we pass in in this test class
- * so we control when that work is done. The callback is still always on the main thread.
- */
- private void waitForInflation() {
- mHandler.postDelayed(TIMEOUT_RUNNABLE, TIMEOUT_TIME);
- final CountDownLatch latch = new CountDownLatch(1);
- NotificationEntryListener inflationListener = new NotificationEntryListener() {
- @Override
- public void onEntryInflated(NotificationEntry entry) {
- latch.countDown();
- }
-
- @Override
- public void onEntryReinflated(NotificationEntry entry) {
- latch.countDown();
- }
-
- @Override
- public void onInflationError(StatusBarNotification notification, Exception exception) {
- latch.countDown();
- }
- };
- mEntryManager.addNotificationEntryListener(inflationListener);
- while (latch.getCount() != 0) {
- mBgExecutor.runAllReady();
- TestableLooper.get(this).processMessages(1);
- }
- mHandler.removeCallbacks(TIMEOUT_RUNNABLE);
- mEntryManager.removeNotificationEntryListener(inflationListener);
- }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index e26c583f0f42..f57c40958b24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -156,13 +156,13 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
mGutsManager = new NotificationGutsManager(mContext,
() -> Optional.of(mCentralSurfaces), mHandler, mHandler, mAccessibilityManager,
- mHighPriorityProvider, mINotificationManager, mNotificationEntryManager,
+ mHighPriorityProvider, mINotificationManager,
mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
mShadeController, mock(DumpManager.class));
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
- mCheckSaveListener, mOnSettingsClickListener);
+ mOnSettingsClickListener);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 30c40b910fbd..2a3509ce3a2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -121,7 +121,6 @@ import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
@@ -129,7 +128,6 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -220,7 +218,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private BatteryController mBatteryController;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private StatusBarNotificationPresenter mNotificationPresenter;
- @Mock private NotificationFilter mNotificationFilter;
@Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
@Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -242,7 +239,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private AutoHideController mAutoHideController;
@Mock private StatusBarWindowController mStatusBarWindowController;
@Mock private StatusBarWindowStateController mStatusBarWindowStateController;
- @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
@Mock private UserSwitcherController mUserSwitcherController;
@Mock private Bubbles mBubbles;
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@@ -283,7 +279,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private OperatorNameViewController mOperatorNameViewController;
@Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
@Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
@Mock private NotifLiveDataStore mNotifLiveDataStore;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private DeviceStateManager mDeviceStateManager;
@@ -298,7 +293,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mDependency.injectTestDependency(NotificationFilter.class, mNotificationFilter);
IPowerManager powerManagerService = mock(IPowerManager.class);
IThermalService thermalService = mock(IThermalService.class);
@@ -310,7 +304,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mPowerManager,
mDreamManager,
mAmbientDisplayConfiguration,
- mNotificationFilter,
mStatusBarStateController,
mKeyguardStateController,
mBatteryController,
@@ -410,7 +403,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mNotificationGutsManager,
notificationLogger,
mNotificationInterruptStateProvider,
- mNotificationViewHierarchyManager,
new PanelExpansionStateManager(),
mKeyguardViewMediator,
new DisplayMetrics(),
@@ -472,7 +464,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mWallpaperManager,
Optional.of(mStartingSurface),
mActivityLaunchAnimator,
- mNotifPipelineFlags,
mJankMonitor,
mDeviceStateManager,
mWiredChargingRippleController, mDreamManager);
@@ -659,7 +650,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
public void testShouldHeadsUp_nonSuppressedGroupSummary() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a")
@@ -683,7 +673,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
public void testShouldHeadsUp_suppressedGroupSummary() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a")
@@ -707,7 +696,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
public void testShouldHeadsUp_suppressedHeadsUp() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a").build();
@@ -729,7 +717,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
public void testShouldHeadsUp_noSuppressedHeadsUp() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a").build();
@@ -1041,7 +1028,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
PowerManager powerManager,
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
- NotificationFilter filter,
StatusBarStateController controller,
KeyguardStateController keyguardStateController,
BatteryController batteryController,
@@ -1055,7 +1041,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
powerManager,
dreamManager,
ambientDisplayConfiguration,
- filter,
batteryController,
controller,
keyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
index 65401356b3e3..c0243dc537b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone
+import android.graphics.Color
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.view.WindowInsetsController
@@ -24,6 +25,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
@@ -33,7 +35,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when` as whenever
-import com.android.systemui.dump.DumpManager
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -56,6 +57,7 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() {
@Mock private lateinit var statusBarBoundsProvider: StatusBarBoundsProvider
@Mock private lateinit var statusBarFragmentComponent: StatusBarFragmentComponent
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var letterboxBackgroundProvider: LetterboxBackgroundProvider
private lateinit var calculator: LetterboxAppearanceCalculator
@@ -63,8 +65,12 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(statusBarFragmentComponent.boundsProvider).thenReturn(statusBarBoundsProvider)
- calculator = LetterboxAppearanceCalculator(lightBarController, dumpManager)
+ calculator =
+ LetterboxAppearanceCalculator(
+ lightBarController, dumpManager, letterboxBackgroundProvider)
calculator.onStatusBarViewInitialized(statusBarFragmentComponent)
+ whenever(letterboxBackgroundProvider.letterboxBackgroundColor).thenReturn(Color.BLACK)
+ whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(false)
}
@Test
@@ -100,6 +106,23 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() {
}
@Test
+ fun getLetterboxAppearance_noOverlap_BackgroundMultiColor_returnsAppearanceWithScrim() {
+ whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(true)
+ whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
+ whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100))
+
+ val letterboxAppearance =
+ calculator.getLetterboxAppearance(
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+
+ expect
+ .that(letterboxAppearance.appearance)
+ .isEqualTo(TEST_APPEARANCE or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS)
+ expect.that(letterboxAppearance.appearanceRegions).isEqualTo(TEST_APPEARANCE_REGIONS)
+ }
+
+ @Test
fun getLetterboxAppearance_noOverlap_returnsAppearanceWithoutScrim() {
whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
new file mode 100644
index 000000000000..44325dddd111
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.statusbar.phone
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import android.view.IWindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxBackgroundProviderTest : SysuiTestCase() {
+
+ private val fakeSystemClock = FakeSystemClock()
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Mock private lateinit var windowManager: IWindowManager
+ @Mock private lateinit var dumpManager: DumpManager
+
+ private lateinit var provider: LetterboxBackgroundProvider
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ provider = LetterboxBackgroundProvider(windowManager, fakeExecutor, dumpManager)
+ }
+
+ @Test
+ fun letterboxBackgroundColor_defaultValue_returnsBlack() {
+ assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.BLACK)
+ }
+
+ @Test
+ fun letterboxBackgroundColor_afterOnStart_executorNotDone_returnsDefaultValue() {
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+
+ provider.start()
+
+ assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.BLACK)
+ }
+
+ @Test
+ fun letterboxBackgroundColor_afterOnStart_executorDone_returnsValueFromWindowManager() {
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+
+ provider.start()
+ fakeExecutor.runAllReady()
+
+ assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.RED)
+ }
+
+ @Test
+ fun isLetterboxBackgroundMultiColored_defaultValue_returnsFalse() {
+ assertThat(provider.isLetterboxBackgroundMultiColored).isEqualTo(false)
+ }
+ @Test
+ fun isLetterboxBackgroundMultiColored_afterOnStart_executorNotDone_returnsDefaultValue() {
+ whenever(windowManager.isLetterboxBackgroundMultiColored).thenReturn(true)
+
+ provider.start()
+
+ assertThat(provider.isLetterboxBackgroundMultiColored).isFalse()
+ }
+
+ @Test
+ fun isBackgroundMultiColored_afterOnStart_executorDone_returnsValueFromWindowManager() {
+ whenever(windowManager.isLetterboxBackgroundMultiColored).thenReturn(true)
+
+ provider.start()
+ fakeExecutor.runAllReady()
+
+ assertThat(provider.isLetterboxBackgroundMultiColored).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 43c6fe18fe95..eef43bde1a23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -43,7 +43,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -51,7 +50,6 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -77,16 +75,17 @@ import org.mockito.ArgumentCaptor;
@RunWithLooper()
public class StatusBarNotificationPresenterTest extends SysuiTestCase {
private StatusBarNotificationPresenter mStatusBarNotificationPresenter;
- private NotificationInterruptStateProvider mNotificationInterruptStateProvider =
+ private final NotificationInterruptStateProvider mNotificationInterruptStateProvider =
mock(NotificationInterruptStateProvider.class);
private NotificationInterruptSuppressor mInterruptSuppressor;
private CommandQueue mCommandQueue;
private FakeMetricsLogger mMetricsLogger;
- private ShadeController mShadeController = mock(ShadeController.class);
- private CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
- private KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class);
- private NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
- private InitController mInitController = new InitController();
+ private final ShadeController mShadeController = mock(ShadeController.class);
+ private final CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
+ private final KeyguardStateController mKeyguardStateController =
+ mock(KeyguardStateController.class);
+ private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
+ private final InitController mInitController = new InitController();
@Before
public void setup() {
@@ -117,16 +116,13 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
mock(ActivityStarter.class),
stackScrollLayoutController,
mock(DozeScrimController.class),
- mock(ScrimController.class),
mock(NotificationShadeWindowController.class),
mock(DynamicPrivacyController.class),
mKeyguardStateController,
mock(KeyguardIndicationController.class),
mCentralSurfaces,
- mock(ShadeControllerImpl.class),
mock(LockscreenShadeTransitionController.class),
mCommandQueue,
- mock(NotificationViewHierarchyManager.class),
mock(NotificationLockscreenUserManager.class),
mock(SysuiStatusBarStateController.class),
mock(NotifShadeEventSource.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 56804d58d241..25348f3bc686 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -230,10 +230,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void disable_isDozingButNoCustomClock_clockAndSystemInfoVisible() {
+ public void disable_isDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(false);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -242,10 +241,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void disable_customClockButNotDozing_clockAndSystemInfoVisible() {
+ public void disable_NotDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -254,40 +252,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void disable_dozingAndCustomClock_clockAndSystemInfoHidden() {
- CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
-
- // Make sure they start out as visible
- assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
- assertEquals(View.VISIBLE, getClockView().getVisibility());
-
- fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
-
- assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
- assertEquals(View.GONE, getClockView().getVisibility());
- }
-
- @Test
- public void onDozingChanged_clockAndSystemInfoVisibilitiesUpdated() {
- CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
-
- // Make sure they start out as visible
- assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
- assertEquals(View.VISIBLE, getClockView().getVisibility());
-
- fragment.onDozingChanged(true);
-
- // When this callback is triggered, we want to make sure the clock and system info
- // visibilities are recalculated. Since dozing=true, they shouldn't be visible.
- assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
- assertEquals(View.GONE, getClockView().getVisibility());
- }
-
- @Test
public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
new file mode 100644
index 000000000000..2915ae8dea88
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.statusbar.pipeline
+
+import android.net.Network
+import android.net.NetworkCapabilities
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.log.LogcatEchoTracker
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+
+@SmallTest
+class ConnectivityPipelineLoggerTest : SysuiTestCase() {
+ private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+ .create("buffer", 10)
+ private val logger = ConnectivityPipelineLogger(buffer)
+
+ @Test
+ fun testLogNetworkCapsChange_bufferHasInfo() {
+ logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ val expectedNetId = NET_1_ID.toString()
+ val expectedCaps = NET_1_CAPS.toString()
+
+ assertThat(actualString).contains(expectedNetId)
+ assertThat(actualString).contains(expectedCaps)
+ }
+
+ @Test
+ fun testLogOnLost_bufferHasNetIdOfLostNetwork() {
+ logger.logOnLost(NET_1)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ val expectedNetId = NET_1_ID.toString()
+
+ assertThat(actualString).contains(expectedNetId)
+ }
+
+ private val NET_1_ID = 100
+ private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
+ Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
+ }
+ private val NET_1_CAPS = NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt
new file mode 100644
index 000000000000..40f8fbfd9af2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt
@@ -0,0 +1,252 @@
+/*
+ * 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.statusbar.pipeline.repository
+
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+// TODO(b/240619365): Update this test to use `runTest` when we update the testing library
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NetworkCapabilitiesRepoTest : SysuiTestCase() {
+ @Mock private lateinit var connectivityManager: ConnectivityManager
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testOnCapabilitiesChanged_oneNewNetwork_networkStored() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN a new network is added
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN it is emitted from the flow
+ assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1)
+ assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnCapabilitiesChanged_twoNewNetworks_bothStored() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN two new networks are added
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+ callback.onCapabilitiesChanged(NET_2, NET_2_CAPS)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow reflects 2 networks
+ assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1)
+ assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS)
+ assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2)
+ assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnCapabilitesChanged_newCapabilitiesForExistingNetwork_areCaptured() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN a network is added, and then its capabilities are changed
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+ callback.onCapabilitiesChanged(NET_1, NET_2_CAPS)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow reflects the new capabilities
+ assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnLost_networkIsRemoved() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN two new networks are added, and one is removed
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+ callback.onCapabilitiesChanged(NET_2, NET_2_CAPS)
+ callback.onLost(NET_1)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow reflects only the remaining network
+ assertThat(currentMap[NET_1_ID]).isNull()
+ assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2)
+ assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnLost_noNetworks_doesNotCrash() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN no networks are added, and one is removed
+ callback.onLost(NET_1)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow shows no networks
+ assertThat(currentMap).isEmpty()
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ private val NET_1_ID = 100
+ private val NET_1 = mock<Network>().also {
+ whenever(it.getNetId()).thenReturn(NET_1_ID)
+ }
+ private val NET_2_ID = 200
+ private val NET_2 = mock<Network>().also {
+ whenever(it.getNetId()).thenReturn(NET_2_ID)
+ }
+
+ private val NET_1_CAPS = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_VALIDATED)
+ .build()
+
+ private val NET_2_CAPS = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .addCapability(NET_CAPABILITY_VALIDATED)
+ .build()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
new file mode 100644
index 000000000000..f51f78315dbd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.unfold
+
+import android.hardware.devicestate.DeviceStateManager
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.os.Handler
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.unfold.util.FoldableDeviceStates
+import com.android.systemui.unfold.util.FoldableTestUtils
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.settings.GlobalSettings
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class FoldAodAnimationControllerTest : SysuiTestCase() {
+
+ @Mock lateinit var deviceStateManager: DeviceStateManager
+
+ @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+
+ @Mock lateinit var globalSettings: GlobalSettings
+
+ @Mock lateinit var latencyTracker: LatencyTracker
+
+ @Mock lateinit var centralSurfaces: CentralSurfaces
+
+ @Mock lateinit var lightRevealScrim: LightRevealScrim
+
+ @Mock lateinit var notificationPanelViewController: NotificationPanelViewController
+
+ @Mock lateinit var viewGroup: ViewGroup
+
+ @Mock lateinit var viewTreeObserver: ViewTreeObserver
+
+ @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+
+ private lateinit var deviceStates: FoldableDeviceStates
+
+ private lateinit var testableLooper: TestableLooper
+
+ lateinit var foldAodAnimationController: FoldAodAnimationController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ foldAodAnimationController =
+ FoldAodAnimationController(
+ Handler(testableLooper.looper),
+ context.mainExecutor,
+ context,
+ deviceStateManager,
+ wakefulnessLifecycle,
+ globalSettings,
+ latencyTracker,
+ )
+ .apply { initialize(centralSurfaces, lightRevealScrim) }
+ deviceStates = FoldableTestUtils.findDeviceStates(context)
+
+ whenever(notificationPanelViewController.view).thenReturn(viewGroup)
+ whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
+ whenever(wakefulnessLifecycle.lastSleepReason)
+ .thenReturn(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
+ whenever(centralSurfaces.notificationPanelViewController)
+ .thenReturn(notificationPanelViewController)
+ whenever(notificationPanelViewController.startFoldToAodAnimation(any(), any(), any()))
+ .then {
+ val onActionStarted = it.arguments[0] as Runnable
+ onActionStarted.run()
+ }
+ verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
+
+ foldAodAnimationController.setIsDozing(dozing = true)
+ setAodEnabled(enabled = true)
+ sendFoldEvent(folded = false)
+ }
+
+ @Test
+ fun onFolded_aodDisabled_doesNotLogLatency() {
+ setAodEnabled(enabled = false)
+
+ fold()
+ simulateScreenTurningOn()
+
+ verifyNoMoreInteractions(latencyTracker)
+ }
+
+ @Test
+ fun onFolded_aodEnabled_logsLatency() {
+ setAodEnabled(enabled = true)
+
+ fold()
+ simulateScreenTurningOn()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker).onActionEnd(any())
+ }
+
+ @Test
+ fun onFolded_animationCancelled_doesNotLogLatency() {
+ setAodEnabled(enabled = true)
+
+ fold()
+ foldAodAnimationController.onScreenTurningOn({})
+ foldAodAnimationController.onStartedWakingUp()
+ testableLooper.processAllMessages()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker).onActionCancel(any())
+ }
+
+ private fun simulateScreenTurningOn() {
+ foldAodAnimationController.onScreenTurningOn({})
+ foldAodAnimationController.onScreenTurnedOn()
+ testableLooper.processAllMessages()
+ }
+
+ private fun fold() = sendFoldEvent(folded = true)
+
+ private fun setAodEnabled(enabled: Boolean) =
+ foldAodAnimationController.onAlwaysOnChanged(alwaysOn = enabled)
+
+ private fun sendFoldEvent(folded: Boolean) {
+ val state = if (folded) deviceStates.folded else deviceStates.unfolded
+ foldStateListenerCaptor.value.onStateChanged(state)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 59a9a3c74718..fee17c785ed2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -43,6 +43,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -93,7 +94,6 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -134,6 +134,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Ignore;
@@ -143,7 +144,10 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
@@ -215,6 +219,8 @@ public class BubblesTest extends SysuiTestCase {
private BubbleEntry mBubbleEntry2User11;
@Mock
+ private ShellInit mShellInit;
+ @Mock
private ShellController mShellController;
@Mock
private Bubbles.BubbleExpandListener mBubbleExpandListener;
@@ -252,6 +258,8 @@ public class BubblesTest extends SysuiTestCase {
private TaskViewTransitions mTaskViewTransitions;
@Mock
private Optional<OneHandedController> mOneHandedOptional;
+ @Mock
+ private UserManager mUserManager;
private TestableBubblePositioner mPositioner;
@@ -314,12 +322,14 @@ public class BubblesTest extends SysuiTestCase {
mPositioner.setMaxBubbles(5);
mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
+ when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn(
+ Collections.singletonList(mock(UserInfo.class)));
+
TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
mock(PowerManager.class),
mock(IDreamManager.class),
mock(AmbientDisplayConfiguration.class),
- mock(NotificationFilter.class),
mock(StatusBarStateController.class),
mock(KeyguardStateController.class),
mock(BatteryController.class),
@@ -332,6 +342,7 @@ public class BubblesTest extends SysuiTestCase {
when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
mBubbleController = new TestableBubbleController(
mContext,
+ mShellInit,
mShellController,
mBubbleData,
mFloatingContentCoordinator,
@@ -339,7 +350,7 @@ public class BubblesTest extends SysuiTestCase {
mStatusBarService,
mWindowManager,
mWindowManagerShellWrapper,
- mock(UserManager.class),
+ mUserManager,
mLauncherApps,
mBubbleLogger,
mTaskStackListener,
@@ -382,6 +393,11 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void instantiateController_registerConfigChangeListener() {
verify(mShellController, times(1)).addConfigurationChangeListener(any());
}
@@ -1025,7 +1041,7 @@ public class BubblesTest extends SysuiTestCase {
assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull();
// Switch users
- mBubbleController.onUserChanged(secondUserId);
+ switchUser(secondUserId);
assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
// Give this user some bubbles
@@ -1042,6 +1058,41 @@ public class BubblesTest extends SysuiTestCase {
verify(mDataRepository, times(2)).loadBubbles(anyInt(), any());
}
+ @Test
+ public void testOnUserChanged_bubblesRestored() {
+ int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier();
+ int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier();
+ // Mock current profile
+ when(mLockscreenUserManager.isCurrentProfile(firstUserId)).thenReturn(true);
+ when(mLockscreenUserManager.isCurrentProfile(secondUserId)).thenReturn(false);
+
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertThat(mBubbleController.hasBubbles()).isTrue();
+ // We start with 1 bubble
+ assertThat(mBubbleData.getBubbles()).hasSize(1);
+
+ // Switch to second user
+ switchUser(secondUserId);
+
+ // Second user has no bubbles
+ assertThat(mBubbleController.hasBubbles()).isFalse();
+
+ // Send bubble update for first user, ensure it does not show up
+ mBubbleController.updateBubble(mBubbleEntry2);
+ assertThat(mBubbleController.hasBubbles()).isFalse();
+
+ // Start returning notif for first user again
+ when(mCommonNotifCollection.getAllNotifs()).thenReturn(Arrays.asList(mRow, mRow2));
+
+ // Switch back to first user
+ switchUser(firstUserId);
+
+ // Check we now have two bubbles, one previous and one new that came in
+ assertThat(mBubbleController.hasBubbles()).isTrue();
+ // Now there are 2 bubbles
+ assertThat(mBubbleData.getBubbles()).hasSize(2);
+ }
+
/**
* Verifies we only load the overflow data once.
*/
@@ -1443,6 +1494,14 @@ public class BubblesTest extends SysuiTestCase {
.build();
}
+ private void switchUser(int userId) {
+ when(mLockscreenUserManager.isCurrentProfile(anyInt())).thenAnswer(
+ (Answer<Boolean>) invocation -> invocation.<Integer>getArgument(0) == userId);
+ SparseArray<UserInfo> userInfos = new SparseArray<>(1);
+ userInfos.put(userId, mock(UserInfo.class));
+ mBubbleController.onCurrentProfilesChanged(userInfos);
+ mBubbleController.onUserChanged(userId);
+ }
/**
* Asserts that the bubble stack is expanded and also validates the cached state is updated.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index f901c327b76e..880ad187f910 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -39,6 +39,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.Optional;
@@ -49,6 +50,7 @@ public class TestableBubbleController extends BubbleController {
// Let's assume surfaces can be synchronized immediately.
TestableBubbleController(Context context,
+ ShellInit shellInit,
ShellController shellController,
BubbleData data,
FloatingContentCoordinator floatingContentCoordinator,
@@ -69,13 +71,13 @@ public class TestableBubbleController extends BubbleController {
Handler shellMainHandler,
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
- super(context, shellController, data, Runnable::run, floatingContentCoordinator,
+ super(context, shellInit, shellController, data, Runnable::run, floatingContentCoordinator,
dataRepository, statusBarService, windowManager, windowManagerShellWrapper,
userManager, launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer,
positioner, displayController, oneHandedOptional, dragAndDropController,
shellMainExecutor, shellMainHandler, new SyncExecutor(), taskViewTransitions,
syncQueue);
setInflateSynchronously(true);
- initialize();
+ onInit();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index d80ea154e77a..9635faf6e858 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -24,7 +24,6 @@ import android.service.dreams.IDreamManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
@@ -40,7 +39,6 @@ public class TestableNotificationInterruptStateProviderImpl
PowerManager powerManager,
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
- NotificationFilter filter,
StatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
BatteryController batteryController,
@@ -53,7 +51,6 @@ public class TestableNotificationInterruptStateProviderImpl
powerManager,
dreamManager,
ambientDisplayConfiguration,
- filter,
batteryController,
statusBarStateController,
keyguardStateController,
diff --git a/packages/SystemUI/tests/utils/AndroidManifest.xml b/packages/SystemUI/tests/utils/AndroidManifest.xml
new file mode 100644
index 000000000000..cbef5f6036ab
--- /dev/null
+++ b/packages/SystemUI/tests/utils/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.tests.utils">
+
+
+</manifest>
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
index 9179efc9f39f..9179efc9f39f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index c52ea60f0bfc..c52ea60f0bfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 0674ea855d7f..0674ea855d7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/utils/src/com/android/systemui/TestableDependency.java
index 0751475c2fb0..0751475c2fb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestableDependency.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index 53dcc8d269c9..53dcc8d269c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
index 48b5c62da38f..48b5c62da38f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt
new file mode 100644
index 000000000000..0bde5d21d32f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.people
+
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.data.repository.PeopleTileRepository
+
+/** A fake [PeopleTileRepository] to be used in tests. */
+class FakePeopleTileRepository(
+ private val priorityTiles: List<PeopleTileModel>,
+ private val recentTiles: List<PeopleTileModel>,
+) : PeopleTileRepository {
+ override fun priorityTiles(): List<PeopleTileModel> = priorityTiles
+
+ override fun recentTiles(): List<PeopleTileModel> = recentTiles
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt
new file mode 100644
index 000000000000..2f814091f3e5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.people
+
+import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** A fake [PeopleWidgetRepository] to be used in tests. */
+class FakePeopleWidgetRepository(
+ private val onSetWidgetTile: (widgetId: Int, tileKey: PeopleTileKey) -> Unit = { _, _ -> },
+) : PeopleWidgetRepository {
+ override fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey) {
+ onSetWidgetTile(widgetId, tileKey)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
index 63f7c9755782..63f7c9755782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSession.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
index 478658eb232d..478658eb232d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSession.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
index 045e6f19c667..045e6f19c667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SbnBuilder.java
index 1b0ed112cea1..1b0ed112cea1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SbnBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index b91f7e6b6169..b91f7e6b6169 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
index 16a326869562..16a326869562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
index 33ece0084906..33ece0084906 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java
index d3d30f242dcf..d3d30f242dcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
index 477f615faf2b..477f615faf2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
index 301a157bd42e..301a157bd42e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
index 9d5ccbec87ea..9d5ccbec87ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
index f7a04dcdbbd8..f7a04dcdbbd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeProximitySensor.java
index 22cf744c726b..22cf744c726b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeProximitySensor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 197873f15d0d..197873f15d0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
index 0d4a6c7023fb..0d4a6c7023fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index e660e1f2d845..e660e1f2d845 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java
index db6164dcf8f9..db6164dcf8f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/wakelock/WakeLockFake.java
index 553b8a42edc8..553b8a42edc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/wakelock/WakeLockFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
index 8fe7f59bca67..8fe7f59bca67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index eaa109d672f8..eaa109d672f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
index 6cbd175c1084..6cbd175c1084 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
index f6b24da9b821..f6b24da9b821 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
index 516eb6e6dffd..516eb6e6dffd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
index 886722e46376..886722e46376 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeExtensionController.java
index ab16e3bd163e..ab16e3bd163e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeExtensionController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index f6fd2cb8f3b1..f6fd2cb8f3b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeHotspotController.java
index 016160aea433..016160aea433 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeHotspotController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
index 95b62a12c621..95b62a12c621 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
index 838a2739f0b0..838a2739f0b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
index 18b07cf25fbc..18b07cf25fbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java
index 33ef9cf7a9c5..33ef9cf7a9c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
index 5ae8e22c06ee..5ae8e22c06ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
index d245c727dcf8..d245c727dcf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
index 4f9cb35db1a3..4f9cb35db1a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
index d5348dc39832..d5348dc39832 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 2be67edfc946..2be67edfc946 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeTunerService.java
index 7d8a28812fde..7d8a28812fde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeTunerService.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
index f7ef653aebf2..f7ef653aebf2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeZenModeController.java
index 75df4e67db23..75df4e67db23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeZenModeController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
index dc6a8fb9a4c4..dc6a8fb9a4c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/os/FakeHandler.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/os/FakeHandler.java
index 5a7d4b5e54b3..5a7d4b5e54b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/os/FakeHandler.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/os/FakeHandler.java
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 803177b6c010..3fa0ab69d67c 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -16,8 +16,6 @@
package com.android.server.accessibility;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
-
import android.accessibilityservice.AccessibilityService;
import android.app.PendingIntent;
import android.app.RemoteAction;
@@ -34,6 +32,7 @@ import android.util.Slog;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.internal.R;
@@ -392,8 +391,8 @@ public class SystemActionPerformer {
private boolean takeScreenshot() {
ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
- true, true, SCREENSHOT_ACCESSIBILITY_ACTIONS,
+ screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+ WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
new Handler(Looper.getMainLooper()), null);
return true;
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 3ab873de4bb5..e07f41204500 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -439,6 +439,7 @@ public final class AutoFillUI {
log(MetricsEvent.TYPE_DISMISS);
hideFillDialogUiThread(callback);
callback.requestShowSoftInput(focusedId);
+ callback.requestFallbackFromFillDialog();
}
@Override
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 7fe3c1f5272b..606a09cb1cac 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -798,6 +798,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
final BatteryUsageStatsQuery querySinceReset =
new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
.includeProcessStateData()
.includeVirtualUids()
.build();
@@ -806,6 +807,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
final BatteryUsageStatsQuery queryPowerProfile =
new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
.includeProcessStateData()
.includeVirtualUids()
.powerProfileModeledOnly()
@@ -822,6 +824,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
final long sessionEnd = mStats.getStartClockTime();
final BatteryUsageStatsQuery queryBeforeReset =
new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
.includeProcessStateData()
.includeVirtualUids()
.aggregateSnapshots(sessionStart, sessionEnd)
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index a44a658359ff..a6e73e491d0c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3934,14 +3934,12 @@ public class AudioService extends IAudioService.Stub
case AudioSystem.MODE_IN_COMMUNICATION:
case AudioSystem.MODE_IN_CALL:
case AudioSystem.MODE_NORMAL:
+ case AudioSystem.MODE_CALL_SCREENING:
+ case AudioSystem.MODE_CALL_REDIRECT:
+ case AudioSystem.MODE_COMMUNICATION_REDIRECT:
break;
- case AudioSystem.MODE_RINGTONE:
- // not changing anything for ringtone
- return;
- case AudioSystem.MODE_CURRENT:
- case AudioSystem.MODE_INVALID:
default:
- // don't know what to do in this case, better bail
+ // no-op is enough for all other values
return;
}
@@ -3964,6 +3962,34 @@ public class AudioService extends IAudioService.Stub
}
}
+ private void setLeAudioVolumeOnModeUpdate(int mode) {
+ switch (mode) {
+ case AudioSystem.MODE_IN_COMMUNICATION:
+ case AudioSystem.MODE_IN_CALL:
+ case AudioSystem.MODE_NORMAL:
+ case AudioSystem.MODE_CALL_SCREENING:
+ case AudioSystem.MODE_CALL_REDIRECT:
+ case AudioSystem.MODE_COMMUNICATION_REDIRECT:
+ break;
+ default:
+ // no-op is enough for all other values
+ return;
+ }
+
+ int streamType = getBluetoothContextualVolumeStream(mode);
+
+ // Currently, DEVICE_OUT_BLE_HEADSET is the only output type for LE_AUDIO profile.
+ // (See AudioDeviceBroker#createBtDeviceInfo())
+ int index = mStreamStates[streamType].getIndex(AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ int maxIndex = mStreamStates[streamType].getMaxIndex();
+
+ if (DEBUG_VOL) {
+ Log.d(TAG, "setLeAudioVolumeOnModeUpdate postSetLeAudioVolumeIndex index="
+ + index + " maxIndex=" + maxIndex + " streamType=" + streamType);
+ }
+ mDeviceBroker.postSetLeAudioVolumeIndex(index, maxIndex, streamType);
+ }
+
private void setStreamVolume(int streamType, int index, int flags,
@Nullable AudioDeviceAttributes ada,
String callingPackage, String caller, String attributionTag, int uid,
@@ -5340,6 +5366,10 @@ public class AudioService extends IAudioService.Stub
// change of mode may require volume to be re-applied on some devices
updateAbsVolumeMultiModeDevices(previousMode, mode);
+ // Forcefully set LE audio volume as a workaround, since the value of 'device'
+ // is not DEVICE_OUT_BLE_* even when BLE is connected.
+ setLeAudioVolumeOnModeUpdate(mode);
+
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
mDeviceBroker.postSetModeOwnerPid(pid, mode);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 95c8fef12976..62b1cfeda9de 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1500,6 +1500,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Animate the screen brightness when the screen is on or dozing.
// Skip the animation when the screen is off or suspended or transition to/from VR.
boolean brightnessAdjusted = false;
+ final boolean brightnessIsTemporary =
+ mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
if (!mPendingScreenOff) {
if (mSkipScreenOnBrightnessRamp) {
if (state == Display.STATE_ON) {
@@ -1532,8 +1534,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// level without it being a noticeable jump since any actual content isn't yet visible.
final boolean isDisplayContentVisible =
mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
- final boolean brightnessIsTemporary =
- mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
// We only want to animate the brightness if it is between 0.0f and 1.0f.
// brightnessState can contain the values -1.0f and NaN, which we do not want to
// animate to. To avoid this, we check the value first.
@@ -1605,7 +1605,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting());
}
- if (brightnessAdjusted) {
+ // Only notify if the brightness adjustment is not temporary (i.e. slider has been released)
+ if (brightnessAdjusted && !brightnessIsTemporary) {
postBrightnessChangeRunnable();
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index d34682df3413..13816142a633 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -1698,7 +1698,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
mDefaultPermissionCallback.onInstallPermissionGranted();
}
- public void onPermissionRevoked(int uid, int userId, String reason) {
+ public void onPermissionRevoked(int uid, int userId, String reason,
+ boolean overrideKill, @Nullable String permissionName) {
revokedPermissions.add(IntPair.of(uid, userId));
syncUpdatedUsers.add(userId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ed32a7d7f778..1f3f039a83fa 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2612,7 +2612,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// either way, abort and reset the sequence.
if (parcelable == null
|| mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
- || mStartingWindow == null
+ || mStartingWindow == null || mStartingWindow.mRemoved
|| finishing) {
if (parcelable != null) {
parcelable.clearIfNeeded();
@@ -4746,6 +4746,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mPendingRemoteAnimation = options.getRemoteAnimationAdapter();
}
mPendingRemoteTransition = options.getRemoteTransition();
+ // Since options gets sent to client apps, remove transition information from it.
+ options.setRemoteTransition(null);
+ options.setRemoteAnimationAdapter(null);
}
void applyOptionsAnimation() {
@@ -9755,6 +9758,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState());
target.setShowBackdrop(record.mShowBackdrop);
+ target.setWillShowImeOnTarget(mStartingData != null && mStartingData.hasImeSurface());
target.hasAnimatingParent = record.hasAnimatingParent();
return target;
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index f967cf995e67..d9ab971c9a78 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -131,7 +131,6 @@ class BackNavigationController {
"Focused window found using getFocusedWindowToken");
}
- OnBackInvokedCallbackInfo overrideCallbackInfo = null;
if (window != null) {
// This is needed to bridge the old and new back behavior with recents. While in
// Overview with live tile enabled, the previous app is technically focused but we
@@ -140,15 +139,18 @@ class BackNavigationController {
// the right window to consume back while in overview, so we need to route it to
// launcher and use the legacy behavior of injecting KEYCODE_BACK since the existing
// compat callback in VRI only works when the window is focused.
+ // This symptom also happen while shell transition enabled, we can check that by
+ // isTransientLaunch to know whether the focus window is point to live tile.
final RecentsAnimationController recentsAnimationController =
wmService.getRecentsAnimationController();
- if (recentsAnimationController != null
- && recentsAnimationController.shouldApplyInputConsumer(
- window.getActivityRecord())) {
- window = recentsAnimationController.getTargetAppMainWindow();
- overrideCallbackInfo = recentsAnimationController.getBackInvokedInfo();
+ final ActivityRecord ar = window.mActivityRecord;
+ if ((ar != null && ar.isActivityTypeHomeOrRecents()
+ && ar.mTransitionController.isTransientLaunch(ar))
+ || (recentsAnimationController != null
+ && recentsAnimationController.shouldApplyInputConsumer(ar))) {
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Current focused window being animated by "
+ "recents. Overriding back callback to recents controller callback.");
+ return null;
}
}
@@ -166,9 +168,7 @@ class BackNavigationController {
if (window != null) {
currentActivity = window.mActivityRecord;
currentTask = window.getTask();
- callbackInfo = overrideCallbackInfo != null
- ? overrideCallbackInfo
- : window.getOnBackInvokedCallbackInfo();
+ callbackInfo = window.getOnBackInvokedCallbackInfo();
if (callbackInfo == null) {
Slog.e(TAG, "No callback registered, returning null.");
return null;
@@ -213,8 +213,10 @@ class BackNavigationController {
Task finalTask = currentTask;
prevActivity = currentTask.getActivity(
(r) -> !r.finishing && r.getTask() == finalTask && !r.isTopRunningActivity());
- if (window.getParent().getChildCount() > 1 && window.getParent().getChildAt(0)
- != window) {
+ // TODO Dialog window does not need to attach on activity, check
+ // window.mAttrs.type != TYPE_BASE_APPLICATION
+ if ((window.getParent().getChildCount() > 1
+ && window.getParent().getChildAt(0) != window)) {
// Are we the top window of our parent? If not, we are a window on top of the
// activity, we won't close the activity.
backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
@@ -379,7 +381,8 @@ class BackNavigationController {
private void onBackNavigationDone(
Bundle result, WindowState focusedWindow, WindowContainer<?> windowContainer,
- int backType, Task task, ActivityRecord prevActivity, boolean prepareAnimation) {
+ int backType, @Nullable Task task, @Nullable ActivityRecord prevActivity,
+ boolean prepareAnimation) {
SurfaceControl surfaceControl = windowContainer.getSurfaceControl();
boolean triggerBack = result != null && result.getBoolean(
BackNavigationInfo.KEY_TRIGGER_BACK);
@@ -404,7 +407,7 @@ class BackNavigationController {
"Setting Activity.mLauncherTaskBehind to false. Activity=%s",
prevActivity);
}
- } else {
+ } else if (task != null) {
task.mBackGestureStarted = false;
}
resetSurfaces(windowContainer);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 565d5cc88f4b..25d187fbf86f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -318,12 +318,19 @@ public class DisplayPolicy {
*/
private final ArrayList<WindowState> mStatusBarBackgroundWindows = new ArrayList<>();
+ /**
+ * A collection of {@link LetterboxDetails} of all visible activities to be sent to SysUI in
+ * order to determine status bar appearance
+ */
+ private final ArrayList<LetterboxDetails> mLetterboxDetails = new ArrayList<>();
+
private String mFocusedApp;
private int mLastDisableFlags;
private int mLastAppearance;
private int mLastBehavior;
private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
private AppearanceRegion[] mLastStatusBarAppearanceRegions;
+ private LetterboxDetails[] mLastLetterboxDetails;
/** The union of checked bounds while building {@link #mStatusBarAppearanceRegionList}. */
private final Rect mStatusBarColorCheckedBounds = new Rect();
@@ -1638,6 +1645,7 @@ public class DisplayPolicy {
mNavBarColorWindowCandidate = null;
mNavBarBackgroundWindow = null;
mStatusBarAppearanceRegionList.clear();
+ mLetterboxDetails.clear();
mStatusBarBackgroundWindows.clear();
mStatusBarColorCheckedBounds.setEmpty();
mStatusBarBackgroundCheckedBounds.setEmpty();
@@ -1717,6 +1725,16 @@ public class DisplayPolicy {
win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
new Rect(win.getFrame())));
mStatusBarColorCheckedBounds.union(sTmpRect);
+ // Check if current activity is letterboxed in order create a LetterboxDetails
+ // component to be passed to SysUI for status bar treatment
+ final ActivityRecord currentActivity = win.getActivityRecord();
+ if (currentActivity != null) {
+ final LetterboxDetails currentLetterboxDetails = currentActivity
+ .mLetterboxUiController.getLetterboxDetails();
+ if (currentLetterboxDetails != null) {
+ mLetterboxDetails.add(currentLetterboxDetails);
+ }
+ }
}
}
@@ -2406,12 +2424,15 @@ public class DisplayPolicy {
callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags,
cause));
}
+ final LetterboxDetails[] letterboxDetails = new LetterboxDetails[mLetterboxDetails.size()];
+ mLetterboxDetails.toArray(letterboxDetails);
if (mLastAppearance == appearance
&& mLastBehavior == behavior
&& mRequestedVisibilities.equals(win.getRequestedVisibilities())
&& Objects.equals(mFocusedApp, focusedApp)
&& mLastFocusIsFullscreen == isFullscreen
- && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)) {
+ && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)
+ && Arrays.equals(mLastLetterboxDetails, letterboxDetails)) {
return;
}
if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen
@@ -2427,9 +2448,10 @@ public class DisplayPolicy {
mFocusedApp = focusedApp;
mLastFocusIsFullscreen = isFullscreen;
mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
+ mLastLetterboxDetails = letterboxDetails;
callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
- requestedVisibilities, focusedApp, new LetterboxDetails[]{}));
+ requestedVisibilities, focusedApp, letterboxDetails));
}
private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
@@ -2750,8 +2772,6 @@ public class DisplayPolicy {
public void takeScreenshot(int screenshotType, int source) {
if (mScreenshotHelper != null) {
mScreenshotHelper.takeScreenshot(screenshotType,
- getStatusBar() != null && getStatusBar().isVisible(),
- getNavigationBar() != null && getNavigationBar().isVisible(),
source, mHandler, null /* completionConsumer */);
}
}
@@ -2845,6 +2865,12 @@ public class DisplayPolicy {
pw.print(prefixInner); pw.println(mLastStatusBarAppearanceRegions[i]);
}
}
+ if (mLastLetterboxDetails != null) {
+ pw.print(prefix); pw.println("mLastLetterboxDetails=");
+ for (int i = mLastLetterboxDetails.length - 1; i >= 0; i--) {
+ pw.print(prefixInner); pw.println(mLastLetterboxDetails[i]);
+ }
+ }
if (!mStatusBarBackgroundWindows.isEmpty()) {
pw.print(prefix); pw.println("mStatusBarBackgroundWindows=");
for (int i = mStatusBarBackgroundWindows.size() - 1; i >= 0; i--) {
@@ -2909,7 +2935,10 @@ public class DisplayPolicy {
return;
}
- mDisplayContent.unregisterPointerEventListener(mPointerLocationView);
+ if (!mDisplayContent.isRemoved()) {
+ mDisplayContent.unregisterPointerEventListener(mPointerLocationView);
+ }
+
final WindowManager wm = mContext.getSystemService(WindowManager.class);
wm.removeView(mPointerLocationView);
mPointerLocationView = null;
@@ -2934,6 +2963,9 @@ public class DisplayPolicy {
mHandler.post(mGestureNavigationSettingsObserver::unregister);
mHandler.post(mForceShowNavBarSettingsObserver::unregister);
mImmersiveModeConfirmation.release();
+ if (mService.mPointerLocationEnabled) {
+ setPointerLocationEnabled(false);
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index b5eff41d4f62..df3109ad33c7 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -138,6 +138,11 @@ public class Letterbox {
return mInner;
}
+ /** @return The frame that contains the inner frame and the insets. */
+ Rect getOuterFrame() {
+ return mOuter;
+ }
+
/**
* Returns {@code true} if the letterbox does not overlap with the bar, or the letterbox can
* fully cover the window frame.
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index d65276793700..ec9ee29679a4 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -68,6 +68,7 @@ import android.view.WindowManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
import java.io.PrintWriter;
@@ -141,6 +142,15 @@ final class LetterboxUiController {
}
}
+ /** Gets the outer bounds of letterbox. The bounds will be empty if there is no letterbox. */
+ private void getLetterboxOuterBounds(Rect outBounds) {
+ if (mLetterbox != null) {
+ outBounds.set(mLetterbox.getOuterFrame());
+ } else {
+ outBounds.setEmpty();
+ }
+ }
+
/**
* @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent
* when the current activity is displayed.
@@ -683,4 +693,26 @@ final class LetterboxUiController {
mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
.logLetterboxPositionChange(mActivityRecord, letterboxPositionChange);
}
+
+ @Nullable
+ LetterboxDetails getLetterboxDetails() {
+ final WindowState w = mActivityRecord.findMainWindow();
+ if (mLetterbox == null || w == null || w.isLetterboxedForDisplayCutout()) {
+ return null;
+ }
+ Rect letterboxInnerBounds = new Rect();
+ Rect letterboxOuterBounds = new Rect();
+ getLetterboxInnerBounds(letterboxInnerBounds);
+ getLetterboxOuterBounds(letterboxOuterBounds);
+
+ if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) {
+ return null;
+ }
+
+ return new LetterboxDetails(
+ letterboxInnerBounds,
+ letterboxOuterBounds,
+ w.mAttrs.insetsFlags.appearance
+ );
+ }
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 53f1fe6abec5..5b702eac7059 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -19,12 +19,10 @@ package com.android.server.wm;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -41,7 +39,6 @@ import android.graphics.GraphicBuffer;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.hardware.input.InputManager;
import android.os.Binder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
@@ -54,18 +51,12 @@ import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
import android.view.IRecentsAnimationController;
import android.view.IRecentsAnimationRunner;
-import android.view.InputDevice;
import android.view.InputWindowHandle;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.WindowInsets.Type;
-import android.window.BackEvent;
-import android.window.IOnBackInvokedCallback;
-import android.window.OnBackInvokedCallbackInfo;
import android.window.PictureInPictureSurfaceTransaction;
import android.window.TaskSnapshot;
@@ -195,46 +186,6 @@ public class RecentsAnimationController implements DeathRecipient {
}
};
- /**
- * Back invoked callback for legacy recents transition with the new back dispatch system.
- */
- final IOnBackInvokedCallback mBackCallback = new IOnBackInvokedCallback.Stub() {
- @Override
- public void onBackStarted() {
- // Do nothing
- }
-
- @Override
- public void onBackProgressed(BackEvent backEvent) {
- // Do nothing
- }
-
- @Override
- public void onBackCancelled() {
- // Do nothing
- }
-
- @Override
- public void onBackInvoked() {
- sendBackEvent(KeyEvent.ACTION_DOWN);
- sendBackEvent(KeyEvent.ACTION_UP);
- }
-
- private void sendBackEvent(int action) {
- if (mTargetActivityRecord == null) {
- return;
- }
- long when = SystemClock.uptimeMillis();
- final KeyEvent ev = new KeyEvent(when, when, action,
- KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
- KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
- InputDevice.SOURCE_KEYBOARD);
- ev.setDisplayId(mTargetActivityRecord.getDisplayId());
- InputManager.getInstance().injectInputEvent(ev, INJECT_INPUT_EVENT_MODE_ASYNC);
- }
- };
-
public interface RecentsAnimationCallbacks {
/** Callback when recents animation is finished. */
void onAnimationFinished(@ReorderMode int reorderMode, boolean sendUserLeaveHint);
@@ -1112,10 +1063,6 @@ public class RecentsAnimationController implements DeathRecipient {
return mTargetActivityRecord.findMainWindow();
}
- OnBackInvokedCallbackInfo getBackInvokedInfo() {
- return new OnBackInvokedCallbackInfo(mBackCallback, PRIORITY_DEFAULT);
- }
-
DisplayArea getTargetAppDisplayArea() {
if (mTargetActivityRecord == null) {
return null;
@@ -1236,6 +1183,12 @@ public class RecentsAnimationController implements DeathRecipient {
mLocalBounds, mBounds, mTask.getWindowConfiguration(),
mIsRecentTaskInvisible, null, null, mTask.getTaskInfo(),
topApp.checkEnterPictureInPictureAppOpsState());
+
+ final ActivityRecord topActivity = mTask.getTopNonFinishingActivity();
+ if (topActivity != null && topActivity.mStartingData != null
+ && topActivity.mStartingData.hasImeSurface()) {
+ mTarget.setWillShowImeOnTarget(true);
+ }
return mTarget;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d91e2c8d4922..3b25f2876de4 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -401,6 +401,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mTaskFragmentOrganizerProcessName = processName;
}
+ void onTaskFragmentOrganizerRemoved() {
+ mTaskFragmentOrganizer = null;
+ }
+
/** Whether this TaskFragment is organized by the given {@code organizer}. */
boolean hasTaskFragmentOrganizer(ITaskFragmentOrganizer organizer) {
return organizer != null && mTaskFragmentOrganizer != null
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index d4551becbaaf..602579fb4f9f 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -136,6 +136,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
void dispose() {
while (!mOrganizedTaskFragments.isEmpty()) {
final TaskFragment taskFragment = mOrganizedTaskFragments.get(0);
+ // Cleanup before remove to prevent it from sending any additional event, such as
+ // #onTaskFragmentVanished, to the removed organizer.
+ taskFragment.onTaskFragmentOrganizerRemoved();
taskFragment.removeImmediately();
mOrganizedTaskFragments.remove(taskFragment);
}
@@ -512,10 +515,21 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
mPendingTaskFragmentEvents.add(pendingEvent);
}
+ boolean isOrganizerRegistered(ITaskFragmentOrganizer organizer) {
+ return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
+ }
+
private void removeOrganizer(ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
// remove all of the children of the organized TaskFragment
state.dispose();
+ // Remove any pending event of this organizer.
+ for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
+ final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i);
+ if (event.mTaskFragmentOrg.asBinder().equals(organizer.asBinder())) {
+ mPendingTaskFragmentEvents.remove(i);
+ }
+ }
mTaskFragmentOrganizerState.remove(organizer.asBinder());
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 801665862d29..c6989ef906d5 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -632,6 +632,11 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
final Rect mainFrame = window.getRelativeFrame();
final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
window.startAnimation(t, adaptor, false, ANIMATION_TYPE_STARTING_REVEAL);
+ if (adaptor.mAnimationLeash == null) {
+ Slog.e(TAG, "Cannot start starting window animation, the window " + window
+ + " was removed");
+ return null;
+ }
t.setPosition(adaptor.mAnimationLeash, mainFrame.left, mainFrame.top);
return adaptor.mAnimationLeash;
}
@@ -679,13 +684,15 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
if (topActivity != null) {
removalInfo.deferRemoveForIme = topActivity.mDisplayContent
.mayImeShowOnLaunchingActivity(topActivity);
- if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
- final WindowState mainWindow =
- topActivity.findMainWindow(false/* includeStartingApp */);
- if (mainWindow != null) {
- removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
- removalInfo.mainFrame = mainWindow.getRelativeFrame();
- }
+ final WindowState mainWindow =
+ topActivity.findMainWindow(false/* includeStartingApp */);
+ // No app window for this activity, app might be crashed.
+ // Remove starting window immediately without playing reveal animation.
+ if (mainWindow == null || mainWindow.mRemoved) {
+ removalInfo.playRevealAnimation = false;
+ } else if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
+ removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
+ removalInfo.mainFrame = mainWindow.getRelativeFrame();
}
}
try {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 31d8eb8eea17..bbc95a1dd70f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -54,6 +54,7 @@ import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
@@ -1728,6 +1729,13 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (task != null && task.voiceSession != null) {
flags |= FLAG_IS_VOICE_INTERACTION;
}
+ if (task != null) {
+ final ActivityRecord topActivity = task.getTopNonFinishingActivity();
+ if (topActivity != null && topActivity.mStartingData != null
+ && topActivity.mStartingData.hasImeSurface()) {
+ flags |= FLAG_WILL_IME_SHOWN;
+ }
+ }
final ActivityRecord record = wc.asActivityRecord();
if (record != null) {
if (record.mUseTransferredAnimation) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 5c20258538ee..4f324f22aa5e 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -479,9 +479,10 @@ class TransitionController {
// Collect all visible non-app windows which need to be drawn before the animation starts.
final DisplayContent dc = wc.asDisplayContent();
if (dc != null) {
+ final boolean noAsyncRotation = dc.getAsyncRotationController() == null;
wc.forAllWindows(w -> {
if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
- && dc.shouldSyncRotationChange(w)) {
+ && (noAsyncRotation || !AsyncRotationController.canBeAsync(w.mToken))) {
transition.collect(w.mToken);
}
}, true /* traverseTopToBottom */);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d9b25adb8c4c..2749b11ce6a1 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3773,9 +3773,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// Check if this is changing displays. If so, mark the old display as "ready" for
// transitions. This is to work around the problem where setting readiness against this
// container will only set the new display as ready and leave the old display as unready.
- if (mSyncState != SYNC_STATE_NONE && oldParent != null
- && oldParent.getDisplayContent() != null && (newParent == null
- || oldParent.getDisplayContent() != newParent.getDisplayContent())) {
+ if (mSyncState != SYNC_STATE_NONE && oldParent != null && newParent != null
+ && oldParent.getDisplayContent() != null && newParent.getDisplayContent() != null
+ && oldParent.getDisplayContent() != newParent.getDisplayContent()) {
mTransitionController.setReady(oldParent.getDisplayContent());
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 48740a3c2f50..29e407fccdb7 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -385,6 +385,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
@Nullable Transition transition, @NonNull CallerInfo caller,
@Nullable Transition finishTransition) {
+ if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
+ .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
+ Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
+ + " is no longer registered");
+ return;
+ }
int effects = 0;
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
mService.deferWindowLayout();
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 0afb1829f9d4..0483a60ca378 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -103,6 +103,7 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
<uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
+ <uses-permission android:name="android.permission.BATTERY_STATS" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index 1d6ed038b86d..c15f6a9c3d66 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
@@ -27,7 +28,6 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -301,8 +301,9 @@ public class SystemActionPerformerTest {
mSystemActionPerformer.performSystemAction(
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
verify(mMockScreenshotHelper).takeScreenshot(
- eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(),
- anyBoolean(), eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any());
+ eq(TAKE_SCREENSHOT_FULLSCREEN),
+ eq(SCREENSHOT_ACCESSIBILITY_ACTIONS),
+ any(Handler.class), any());
}
// PendingIntent is a final class and cannot be mocked. So we are using this
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
new file mode 100644
index 000000000000..7ae111711b6b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.server.power.stats;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.fail;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.UidBatteryConsumer;
+
+import org.junit.Test;
+
+/**
+ * Test BatteryStatsManager and CellularBatteryStats to ensure that valid data is being reported
+ * and that invalid data is not reported.
+ */
+public class BatteryStatsManagerTest {
+
+ @Test
+ public void testBatteryUsageStatsDataConsistency() {
+ BatteryStatsManager bsm = getContext().getSystemService(BatteryStatsManager.class);
+ BatteryUsageStats stats = bsm.getBatteryUsageStats(
+ new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(
+ 0).includeProcessStateData().build());
+ final int[] components =
+ {BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ BatteryConsumer.POWER_COMPONENT_WIFI,
+ BatteryConsumer.POWER_COMPONENT_BLUETOOTH};
+ final int[] states =
+ {BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+ BatteryConsumer.PROCESS_STATE_CACHED};
+ for (UidBatteryConsumer ubc : stats.getUidBatteryConsumers()) {
+ for (int component : components) {
+ double consumedPower = ubc.getConsumedPower(ubc.getKey(component));
+ double sumStates = 0;
+ for (int state : states) {
+ sumStates += ubc.getConsumedPower(ubc.getKey(component, state));
+ }
+ if (sumStates > consumedPower + 0.1) {
+ fail("Sum of states exceeds total. UID = " + ubc.getUid() + " "
+ + BatteryConsumer.powerComponentIdToString(component)
+ + " total = " + consumedPower + " states = " + sumStates);
+ }
+ }
+ }
+ }
+}
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 324e244c46f5..21839aac4ec5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -61,8 +61,10 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@@ -82,11 +84,14 @@ import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
+import android.view.InsetsVisibilities;
import android.view.WindowManager;
import androidx.test.filters.MediumTest;
import com.android.internal.policy.SystemBarUtils;
+import com.android.internal.statusbar.LetterboxDetails;
+import com.android.server.statusbar.StatusBarManagerInternal;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -97,6 +102,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
/**
* Tests for Size Compatibility mode.
@@ -2113,6 +2119,104 @@ public class SizeCompatTests extends WindowTestsBase {
assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(organizer.mPrimary.getBounds());
}
+ @Test
+ public void testLetterboxDetailsForStatusBar_noLetterbox() {
+ setUpDisplaySizeWithApp(2800, 1000);
+ addStatusBar(mActivity.mDisplayContent);
+ addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+ // appearance inside letterboxDetails
+
+ DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+ StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+ // We should get a null LetterboxDetails object as there is no letterboxed activity, so
+ // nothing will get passed to SysUI
+ verify(statusBar, never()).onSystemBarAttributesChanged(anyInt(), anyInt(),
+ any(), anyBoolean(), anyInt(),
+ any(InsetsVisibilities.class), isNull(), isNull());
+
+ }
+
+ @Test
+ public void testLetterboxDetailsForStatusBar_letterboxedForMaxAspectRatio() {
+ setUpDisplaySizeWithApp(2800, 1000);
+ addStatusBar(mActivity.mDisplayContent);
+ addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+ // appearance inside letterboxDetails
+ // Prepare unresizable activity with max aspect ratio
+ prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+ // Refresh the letterbox
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+ assertEquals(mBounds, new Rect(850, 0, 1950, 1000));
+
+ DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+ LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails(
+ mBounds,
+ mActivity.getDisplayContent().getBounds(),
+ mActivity.findMainWindow().mAttrs.insetsFlags.appearance
+ )};
+
+ // Check that letterboxDetails actually gets passed to SysUI
+ StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+ verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
+ any(), anyBoolean(), anyInt(),
+ any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ }
+
+ @Test
+ public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ setUpDisplaySizeWithApp(2800, 1000);
+ addStatusBar(mActivity.mDisplayContent);
+ // Create another task for the second activity
+ final Task newTask = new TaskBuilder(mSupervisor).setDisplay(mActivity.getDisplayContent())
+ .setCreateActivity(true).build();
+ ActivityRecord newActivity = newTask.getTopNonFinishingActivity();
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+ // Move first activity to split screen which takes half of the screen.
+ organizer.mPrimary.setBounds(0, 0, 1400, 1000);
+ organizer.putTaskToPrimary(mTask, true);
+ // Move second activity to split screen which takes half of the screen.
+ organizer.mSecondary.setBounds(1400, 0, 2800, 1000);
+ organizer.putTaskToSecondary(newTask, true);
+
+ addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+ // appearance inside letterboxDetails
+ // Prepare unresizable activity with max aspect ratio
+ prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+ addWindowToActivity(newActivity);
+ prepareUnresizable(newActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ // Refresh the letterboxes
+ newActivity.mRootWindowContainer.performSurfacePlacement();
+
+ Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+ assertEquals(mBounds, new Rect(150, 0, 1250, 1000));
+ final Rect newBounds = new Rect(newActivity.getWindowConfiguration().getBounds());
+ assertEquals(newBounds, new Rect(1550, 0, 2650, 1000));
+
+ DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+ LetterboxDetails[] expectedLetterboxDetails = { new LetterboxDetails(
+ mBounds,
+ organizer.mPrimary.getBounds(),
+ mActivity.findMainWindow().mAttrs.insetsFlags.appearance
+ ), new LetterboxDetails(
+ newBounds,
+ organizer.mSecondary.getBounds(),
+ newActivity.findMainWindow().mAttrs.insetsFlags.appearance
+ )};
+
+ // Check that letterboxDetails actually gets passed to SysUI
+ StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+ verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
+ any(), anyBoolean(), anyInt(),
+ any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ }
+
private void recomputeNaturalConfigurationOfUnresizableActivity() {
// Recompute the natural configuration of the non-resizable activity and the split screen.
mActivity.clearSizeCompatMode();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 97f0918e8136..eba27553937e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -420,6 +420,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots()
throws RemoteException {
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(mIOrganizer);
final TaskFragment taskFragment2 =
new TaskFragment(mAtm, new Binder(), true /* createdByOrganizer */);
final WindowContainerToken token2 = taskFragment2.mRemoteToken.toWindowContainerToken();
@@ -595,6 +596,25 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
+ public void testApplyTransaction_skipTransactionForUnregisterOrganizer() {
+ final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
+ final IBinder fragmentToken = new Binder();
+
+ // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+ createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+ mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+ // Nothing should happen as the organizer is not registered.
+ assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+
+ mController.registerOrganizer(mIOrganizer);
+ mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+ // Successfully created when the organizer is registered.
+ assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+ }
+
+ @Test
public void testTaskFragmentInPip_startActivityInTaskFragment() {
setupTaskFragmentInPip();
final ActivityRecord activity = mTaskFragment.getTopMostActivity();