summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityThread.java4
-rw-r--r--core/java/android/content/pm/LauncherApps.java14
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java4
-rw-r--r--core/java/android/hardware/camera2/params/SessionConfiguration.java24
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig7
-rw-r--r--core/java/android/os/UserManager.java11
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java2
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java3
-rw-r--r--core/java/android/text/flags/flags.aconfig31
-rw-r--r--core/java/android/view/Display.java5
-rw-r--r--core/java/android/view/DisplayInfo.java9
-rw-r--r--core/java/android/view/PointerIcon.java16
-rw-r--r--core/java/android/view/ViewRootImpl.java5
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--core/jni/android_graphics_BLASTBufferQueue.cpp17
-rw-r--r--core/res/res/values/styles.xml8
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--graphics/java/android/graphics/BLASTBufferQueue.java6
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java246
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java5
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt85
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt92
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt85
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp38
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml85
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml97
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml22
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto71
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/Android.bp46
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt78
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt75
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionService.kt110
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt)11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt117
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java177
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java6
-rw-r--r--native/android/libandroid.map.txt1
-rw-r--r--native/android/surface_control.cpp29
-rw-r--r--packages/SettingsLib/res/values/strings.xml8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java22
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java26
-rw-r--r--packages/SettingsProvider/Android.bp1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java41
-rw-r--r--packages/SystemUI/Android.bp2
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig31
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt24
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt60
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt21
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt149
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt11
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt12
-rw-r--r--packages/SystemUI/docs/scene.md6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt50
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt60
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt95
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt219
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt130
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt183
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt187
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt358
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt42
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt199
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java (renamed from packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java)112
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt926
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt204
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt503
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt329
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java)84
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java156
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java)406
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt73
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt4
-rw-r--r--ravenwood/Android.bp2
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java53
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java11
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java235
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java68
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java80
-rw-r--r--services/companion/java/com/android/server/companion/virtual/OWNERS4
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java10
-rw-r--r--services/core/java/com/android/server/am/OomAdjusterModernImpl.java6
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java3
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java3
-rw-r--r--services/core/java/com/android/server/display/DisplayControl.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceInfo.java8
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java145
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java24
-rw-r--r--services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java61
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java3
-rw-r--r--services/core/java/com/android/server/input/debug/TouchpadDebugView.java27
-rw-r--r--services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java107
-rw-r--r--services/core/java/com/android/server/notification/VibratorHelper.java10
-rw-r--r--services/core/java/com/android/server/notification/ZenModeConditions.java2
-rw-r--r--services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java208
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java84
-rw-r--r--services/core/java/com/android/server/pm/ShortcutLauncher.java18
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackage.java5
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java23
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java15
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java14
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java13
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java1
-rw-r--r--services/core/java/com/android/server/wm/EmbeddedWindowController.java12
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java39
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java9
-rw-r--r--services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt39
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java123
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java9
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java137
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java52
-rw-r--r--services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java27
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java38
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt2
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt126
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/Android.bp1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml25
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml32
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java6
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java156
-rw-r--r--tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt50
-rw-r--r--tests/Tracing/Android.bp33
-rw-r--r--tests/Tracing/AndroidManifest.xml33
-rw-r--r--tests/Tracing/AndroidTest.xml40
-rw-r--r--tests/Tracing/OWNERS (renamed from tests/Internal/src/com/android/internal/protolog/OWNERS)2
-rw-r--r--tests/Tracing/TEST_MAPPING7
-rw-r--r--tests/Tracing/res/xml/network_security_config.xml21
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java (renamed from tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java)1
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java (renamed from tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java)0
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java (renamed from tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java)0
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java (renamed from tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java)27
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java (renamed from tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java)0
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java (renamed from tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java)0
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java (renamed from tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java)0
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java (renamed from tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java)3
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java (renamed from tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java)3
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/common/LogDataTypeTest.java (renamed from tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java)0
252 files changed, 7321 insertions, 3635 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4350545a1b1d..5db79fe92345 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -8288,12 +8288,12 @@ public final class ActivityThread extends ClientTransactionHandler
}
Context c = null;
ApplicationInfo ai = info.applicationInfo;
- if (context.getPackageName().equals(ai.packageName)) {
+ if (context != null && context.getPackageName().equals(ai.packageName)) {
c = context;
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
c = mInitialApplication;
- } else {
+ } else if (context != null) {
try {
c = context.createPackageContext(ai.packageName,
Context.CONTEXT_INCLUDE_CODE);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 52c84dc0ac5d..26f919f99ee9 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -778,8 +778,18 @@ public class LauncherApps {
public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
- return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(),
- packageName, user), user);
+ final List<LauncherActivityInfo> activityList = convertToActivityList(
+ mService.getLauncherActivities(
+ mContext.getPackageName(),
+ packageName,
+ user
+ ), user);
+ if (activityList.isEmpty()) {
+ // b/350144057
+ Log.d(TAG, "getActivityList: No launchable activities found for"
+ + "packageName=" + packageName + ", user=" + user);
+ }
+ return activityList;
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
index 4ddf602c447b..b5fb0502ec2c 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
@@ -205,10 +205,6 @@ public class CameraDeviceSetupImpl extends CameraDevice.CameraDeviceSetup {
*/
@SuppressWarnings("AndroidFrameworkCompatChange")
public static boolean isCameraDeviceSetupSupported(CameraCharacteristics chars) {
- if (!Flags.featureCombinationQuery()) {
- return false;
- }
-
Integer queryVersion = chars.get(
CameraCharacteristics.INFO_SESSION_CONFIGURATION_QUERY_VERSION);
return queryVersion != null && queryVersion > Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 9bd4860e7ccc..50c6b5b8b995 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -165,12 +165,10 @@ public final class SessionConfiguration implements Parcelable {
source.readTypedList(outConfigs, OutputConfiguration.CREATOR);
// Ignore the values for hasSessionParameters and settings because we cannot reconstruct
// the CaptureRequest object.
- if (Flags.featureCombinationQuery()) {
- boolean hasSessionParameters = source.readBoolean();
- if (hasSessionParameters) {
- CameraMetadataNative settings = new CameraMetadataNative();
- settings.readFromParcel(source);
- }
+ boolean hasSessionParameters = source.readBoolean();
+ if (hasSessionParameters) {
+ CameraMetadataNative settings = new CameraMetadataNative();
+ settings.readFromParcel(source);
}
if ((inputWidth > 0) && (inputHeight > 0) && (inputFormat != -1)) {
@@ -212,14 +210,12 @@ public final class SessionConfiguration implements Parcelable {
dest.writeBoolean(/*isMultiResolution*/ false);
}
dest.writeTypedList(mOutputConfigurations);
- if (Flags.featureCombinationQuery()) {
- if (mSessionParameters != null) {
- dest.writeBoolean(/*hasSessionParameters*/true);
- CameraMetadataNative metadata = mSessionParameters.getNativeCopy();
- metadata.writeToParcel(dest, /*flags*/0);
- } else {
- dest.writeBoolean(/*hasSessionParameters*/false);
- }
+ if (mSessionParameters != null) {
+ dest.writeBoolean(/*hasSessionParameters*/true);
+ CameraMetadataNative metadata = mSessionParameters.getNativeCopy();
+ metadata.writeToParcel(dest, /*flags*/0);
+ } else {
+ dest.writeBoolean(/*hasSessionParameters*/false);
}
}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 1a309c646882..983bbc3b2774 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -119,6 +119,13 @@ flag {
}
flag {
+ namespace: "input_native"
+ name: "use_key_gesture_event_handler_multi_press_gestures"
+ description: "Use KeyGestureEvent handler APIs to control multi key press gestures"
+ bug: "358569822"
+}
+
+flag {
name: "keyboard_repeat_keys"
namespace: "input_native"
description: "Allow configurable timeout before key repeat and repeat delay rate for key repeats"
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a4a7a983c44c..1ca4574e79b4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3763,7 +3763,8 @@ public class UserManager {
}
private static final String CACHE_KEY_IS_USER_UNLOCKED_PROPERTY =
- "cache_key.is_user_unlocked";
+ PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "is_user_unlocked");
private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockedCache =
new PropertyInvalidatedCache<Integer, Boolean>(
@@ -6694,7 +6695,9 @@ public class UserManager {
}
/* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
- private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";
+ private static final String CACHE_KEY_STATIC_USER_PROPERTIES =
+ PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "static_user_props");
private final PropertyInvalidatedCache<Integer, String> mProfileTypeCache =
new PropertyInvalidatedCache<Integer, String>(32, CACHE_KEY_STATIC_USER_PROPERTIES) {
@@ -6721,7 +6724,9 @@ public class UserManager {
}
/* Cache key for UserProperties object. */
- private static final String CACHE_KEY_USER_PROPERTIES = "cache_key.user_properties";
+ private static final String CACHE_KEY_USER_PROPERTIES =
+ PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "user_properties");
// TODO: It would be better to somehow have this as static, so that it can work cross-context.
private final PropertyInvalidatedCache<Integer, UserProperties> mUserPropertiesCache =
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 77dde5e2b9a3..d45b24ed69be 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1046,6 +1046,8 @@ public class ZenModeConfig implements Parcelable {
DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
} else if (MANUAL_TAG.equals(tag)) {
rt.manualRule = readRuleXml(parser);
+ // manualRule.enabled can never be false, but it was broken in some builds.
+ rt.manualRule.enabled = true;
// Manual rule may be present prior to modes_ui if it were on, but in that
// case it would not have a set policy, so make note of the need to set
// it up later.
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index ad457ce6e18d..384add5cf929 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -70,6 +70,7 @@ import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -370,6 +371,7 @@ public abstract class WallpaperService extends Service {
private float mDefaultDimAmount = 0.05f;
SurfaceControl mBbqSurfaceControl;
BLASTBufferQueue mBlastBufferQueue;
+ IBinder mBbqApplyToken = new Binder();
private SurfaceControl mScreenshotSurfaceControl;
private Point mScreenshotSize = new Point();
@@ -2390,6 +2392,7 @@ public abstract class WallpaperService extends Service {
if (mBlastBufferQueue == null) {
mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mBbqSurfaceControl,
width, height, format);
+ mBlastBufferQueue.setApplyToken(mBbqApplyToken);
// We only return the Surface the first time, as otherwise
// it hasn't changed and there is no need to update.
ret = mBlastBufferQueue.createSurface();
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 3c61f4f5a33c..3846972a12e8 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -84,17 +84,6 @@ flag {
}
flag {
- name: "fix_font_update_failure"
- namespace: "text"
- description: "There was a bug of updating system font from Android 13 to 14. This flag for fixing the migration failure."
- is_fixed_read_only: true
- bug: "331717791"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "fix_misaligned_context_menu"
namespace: "text"
description: "Fix the context menu misalignment and incosistent icon size."
@@ -154,26 +143,6 @@ flag {
}
flag {
- name: "portuguese_hyphenator"
- namespace: "text"
- description: "Portuguese taiored hyphenator"
- bug: "344656282"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "dont_break_email_in_nobreak_tag"
- namespace: "text"
- description: "Prevent line break inside email."
- bug: "350691716"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "handwriting_gesture_with_transformation"
namespace: "text"
description: "Fix handwriting gesture is not working when view has transformation."
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index f8c97eb5fa72..53935e810913 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1341,7 +1341,7 @@ public final class Display {
public HdrCapabilities getHdrCapabilities() {
synchronized (mLock) {
updateDisplayInfoLocked();
- if (mDisplayInfo.hdrCapabilities == null) {
+ if (mDisplayInfo.hdrCapabilities == null || mDisplayInfo.isForceSdr) {
return null;
}
int[] supportedHdrTypes;
@@ -1363,6 +1363,7 @@ public final class Display {
supportedHdrTypes[index++] = enabledType;
}
}
+
return new HdrCapabilities(supportedHdrTypes,
mDisplayInfo.hdrCapabilities.mMaxLuminance,
mDisplayInfo.hdrCapabilities.mMaxAverageLuminance,
@@ -2087,6 +2088,7 @@ public final class Display {
/**
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static String stateToString(int state) {
switch (state) {
case STATE_UNKNOWN:
@@ -2109,6 +2111,7 @@ public final class Display {
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String stateReasonToString(@StateReason int reason) {
switch (reason) {
case STATE_REASON_UNKNOWN:
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 157cec8a4d0f..cac3e3c25098 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -230,6 +230,9 @@ public final class DisplayInfo implements Parcelable {
/** The formats disabled by user **/
public int[] userDisabledHdrTypes = {};
+ /** When true, all HDR capabilities are disabled **/
+ public boolean isForceSdr;
+
/**
* Indicates whether the display can be switched into a mode with minimal post
* processing.
@@ -440,6 +443,7 @@ public final class DisplayInfo implements Parcelable {
&& colorMode == other.colorMode
&& Arrays.equals(supportedColorModes, other.supportedColorModes)
&& Objects.equals(hdrCapabilities, other.hdrCapabilities)
+ && isForceSdr == other.isForceSdr
&& Arrays.equals(userDisabledHdrTypes, other.userDisabledHdrTypes)
&& minimalPostProcessingSupported == other.minimalPostProcessingSupported
&& logicalDensityDpi == other.logicalDensityDpi
@@ -502,6 +506,7 @@ public final class DisplayInfo implements Parcelable {
supportedColorModes = Arrays.copyOf(
other.supportedColorModes, other.supportedColorModes.length);
hdrCapabilities = other.hdrCapabilities;
+ isForceSdr = other.isForceSdr;
userDisabledHdrTypes = other.userDisabledHdrTypes;
minimalPostProcessingSupported = other.minimalPostProcessingSupported;
logicalDensityDpi = other.logicalDensityDpi;
@@ -567,6 +572,7 @@ public final class DisplayInfo implements Parcelable {
supportedColorModes[i] = source.readInt();
}
hdrCapabilities = source.readParcelable(null, android.view.Display.HdrCapabilities.class);
+ isForceSdr = source.readBoolean();
minimalPostProcessingSupported = source.readBoolean();
logicalDensityDpi = source.readInt();
physicalXDpi = source.readFloat();
@@ -636,6 +642,7 @@ public final class DisplayInfo implements Parcelable {
dest.writeInt(supportedColorModes[i]);
}
dest.writeParcelable(hdrCapabilities, flags);
+ dest.writeBoolean(isForceSdr);
dest.writeBoolean(minimalPostProcessingSupported);
dest.writeInt(logicalDensityDpi);
dest.writeFloat(physicalXDpi);
@@ -874,6 +881,8 @@ public final class DisplayInfo implements Parcelable {
sb.append(Arrays.toString(appsSupportedModes));
sb.append(", hdrCapabilities ");
sb.append(hdrCapabilities);
+ sb.append(", isForceSdr ");
+ sb.append(isForceSdr);
sb.append(", userDisabledHdrTypes ");
sb.append(Arrays.toString(userDisabledHdrTypes));
sb.append(", minimalPostProcessingSupported ");
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index dd950e83dd52..b21e85aeeb6a 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -174,24 +174,26 @@ public final class PointerIcon implements Parcelable {
@IntDef(prefix = {"POINTER_ICON_VECTOR_STYLE_FILL_"}, value = {
POINTER_ICON_VECTOR_STYLE_FILL_BLACK,
POINTER_ICON_VECTOR_STYLE_FILL_GREEN,
- POINTER_ICON_VECTOR_STYLE_FILL_YELLOW,
+ POINTER_ICON_VECTOR_STYLE_FILL_RED,
POINTER_ICON_VECTOR_STYLE_FILL_PINK,
- POINTER_ICON_VECTOR_STYLE_FILL_BLUE
+ POINTER_ICON_VECTOR_STYLE_FILL_BLUE,
+ POINTER_ICON_VECTOR_STYLE_FILL_PURPLE
})
@Retention(RetentionPolicy.SOURCE)
public @interface PointerIconVectorStyleFill {}
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLACK = 0;
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_GREEN = 1;
- /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_YELLOW = 2;
+ /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_RED = 2;
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_PINK = 3;
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLUE = 4;
+ /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_PURPLE = 5;
// If adding a PointerIconVectorStyleFill, update END value for {@link SystemSettingsValidators}
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BEGIN =
POINTER_ICON_VECTOR_STYLE_FILL_BLACK;
/** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_END =
- POINTER_ICON_VECTOR_STYLE_FILL_BLUE;
+ POINTER_ICON_VECTOR_STYLE_FILL_PURPLE;
/** @hide */
@IntDef(prefix = {"POINTER_ICON_VECTOR_STYLE_STROKE_"}, value = {
@@ -712,12 +714,14 @@ public final class PointerIcon implements Parcelable {
com.android.internal.R.style.PointerIconVectorStyleFillBlack;
case POINTER_ICON_VECTOR_STYLE_FILL_GREEN ->
com.android.internal.R.style.PointerIconVectorStyleFillGreen;
- case POINTER_ICON_VECTOR_STYLE_FILL_YELLOW ->
- com.android.internal.R.style.PointerIconVectorStyleFillYellow;
+ case POINTER_ICON_VECTOR_STYLE_FILL_RED ->
+ com.android.internal.R.style.PointerIconVectorStyleFillRed;
case POINTER_ICON_VECTOR_STYLE_FILL_PINK ->
com.android.internal.R.style.PointerIconVectorStyleFillPink;
case POINTER_ICON_VECTOR_STYLE_FILL_BLUE ->
com.android.internal.R.style.PointerIconVectorStyleFillBlue;
+ case POINTER_ICON_VECTOR_STYLE_FILL_PURPLE ->
+ com.android.internal.R.style.PointerIconVectorStyleFillPurple;
default -> com.android.internal.R.style.PointerIconVectorStyleFillBlack;
};
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e10cc28d0745..9ff503171a3f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -829,6 +829,7 @@ public final class ViewRootImpl implements ViewParent,
private final SurfaceControl mSurfaceControl = new SurfaceControl();
private BLASTBufferQueue mBlastBufferQueue;
+ private IBinder mBbqApplyToken = new Binder();
private final HdrRenderState mHdrRenderState = new HdrRenderState(this);
@@ -2743,6 +2744,10 @@ public final class ViewRootImpl implements ViewParent,
mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);
+ // If we create and destroy BBQ without recreating the SurfaceControl, we can end up
+ // queuing buffers on multiple apply tokens causing out of order buffer submissions. We
+ // fix this by setting the same apply token on all BBQs created by this VRI.
+ mBlastBufferQueue.setApplyToken(mBbqApplyToken);
Surface blastSurface;
if (addSchandleToVriSurface()) {
blastSurface = mBlastBufferQueue.createSurfaceWithHandle();
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 1c4b16ebe891..cc5e583034a5 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -117,6 +117,13 @@ flag {
}
flag {
+ name: "enable_tile_resizing"
+ namespace: "lse_desktop_experience"
+ description: "Enables drawing a divider bar upon tiling tasks left and right in desktop mode for simultaneous resizing"
+ bug: "351769839"
+}
+
+flag {
name: "respect_orientation_change_for_unresizeable"
namespace: "lse_desktop_experience"
description: "Whether to resize task to respect requested orientation change of unresizeable activity"
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 70505a45fa1b..b9c3bf73f11c 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -16,16 +16,16 @@
#define LOG_TAG "BLASTBufferQueue"
-#include <nativehelper/JNIHelp.h>
-
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
-#include <utils/Log.h>
-#include <utils/RefBase.h>
-
+#include <android_util_Binder.h>
#include <gui/BLASTBufferQueue.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
#include "core_jni_helpers.h"
namespace android {
@@ -209,6 +209,12 @@ static jobject nativeGatherPendingTransactions(JNIEnv* env, jclass clazz, jlong
reinterpret_cast<jlong>(transaction));
}
+static void nativeSetApplyToken(JNIEnv* env, jclass clazz, jlong ptr, jobject applyTokenObject) {
+ sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+ sp<IBinder> token(ibinderForJavaObject(env, applyTokenObject));
+ return queue->setApplyToken(std::move(token));
+}
+
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
// clang-format off
@@ -227,6 +233,7 @@ static const JNINativeMethod gMethods[] = {
{"nativeSetTransactionHangCallback",
"(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V",
(void*)nativeSetTransactionHangCallback},
+ {"nativeSetApplyToken", "(JLandroid/os/IBinder;)V", (void*)nativeSetApplyToken},
// clang-format on
};
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index dc99634ddabc..579dc91d2ca1 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1509,7 +1509,7 @@ please see styles_device_defaults.xml.
</style>
<!-- @hide -->
- <style name="PointerIconVectorStyleFillYellow">
+ <style name="PointerIconVectorStyleFillRed">
<item name="pointerIconVectorFill">#F55E57</item>
<item name="pointerIconVectorFillInverse">#F55E57</item>
</style>
@@ -1527,6 +1527,12 @@ please see styles_device_defaults.xml.
</style>
<!-- @hide -->
+ <style name="PointerIconVectorStyleFillPurple">
+ <item name="pointerIconVectorFill">#AD72FF</item>
+ <item name="pointerIconVectorFillInverse">#AD72FF</item>
+ </style>
+
+ <!-- @hide -->
<style name="PointerIconVectorStyleStrokeWhite">
<item name="pointerIconVectorStroke">@color/white</item>
<item name="pointerIconVectorStrokeInverse">@color/black</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1917ecdd99b4..039665982482 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1705,9 +1705,10 @@
<java-symbol type="style" name="VectorPointer" />
<java-symbol type="style" name="PointerIconVectorStyleFillBlack" />
<java-symbol type="style" name="PointerIconVectorStyleFillGreen" />
- <java-symbol type="style" name="PointerIconVectorStyleFillYellow" />
+ <java-symbol type="style" name="PointerIconVectorStyleFillRed" />
<java-symbol type="style" name="PointerIconVectorStyleFillPink" />
<java-symbol type="style" name="PointerIconVectorStyleFillBlue" />
+ <java-symbol type="style" name="PointerIconVectorStyleFillPurple" />
<java-symbol type="attr" name="pointerIconVectorFill" />
<java-symbol type="style" name="PointerIconVectorStyleStrokeWhite" />
<java-symbol type="style" name="PointerIconVectorStyleStrokeBlack" />
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index c52f700ef4f6..90723b2f1493 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.NonNull;
+import android.os.IBinder;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -47,6 +48,7 @@ public final class BLASTBufferQueue {
long frameNumber);
private static native void nativeSetTransactionHangCallback(long ptr,
TransactionHangCallback callback);
+ private static native void nativeSetApplyToken(long ptr, IBinder applyToken);
public interface TransactionHangCallback {
void onTransactionHang(String reason);
@@ -204,4 +206,8 @@ public final class BLASTBufferQueue {
public void setTransactionHangCallback(TransactionHangCallback hangCallback) {
nativeSetTransactionHangCallback(mNativeObject, hangCallback);
}
+
+ public void setApplyToken(IBinder applyToken) {
+ nativeSetApplyToken(mNativeObject, applyToken);
+ }
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 9027bf34a58e..88878c6adcf2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -40,6 +40,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.util.ArrayMap;
@@ -339,6 +340,52 @@ public class TransitionUtil {
return target;
}
+ /**
+ * Creates a new RemoteAnimationTarget from the provided change and leash
+ */
+ public static RemoteAnimationTarget newSyntheticTarget(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash, @TransitionInfo.TransitionMode int mode, int order,
+ boolean isTranslucent) {
+ int taskId;
+ boolean isNotInRecents;
+ WindowConfiguration windowConfiguration;
+
+ if (taskInfo != null) {
+ taskId = taskInfo.taskId;
+ isNotInRecents = !taskInfo.isRunning;
+ windowConfiguration = taskInfo.configuration.windowConfiguration;
+ } else {
+ taskId = INVALID_TASK_ID;
+ isNotInRecents = true;
+ windowConfiguration = new WindowConfiguration();
+ }
+
+ Rect localBounds = new Rect();
+ RemoteAnimationTarget target = new RemoteAnimationTarget(
+ taskId,
+ newModeToLegacyMode(mode),
+ // TODO: once we can properly sync transactions across process,
+ // then get rid of this leash.
+ leash,
+ isTranslucent,
+ null,
+ // TODO(shell-transitions): we need to send content insets? evaluate how its used.
+ new Rect(0, 0, 0, 0),
+ order,
+ null,
+ localBounds,
+ new Rect(),
+ windowConfiguration,
+ isNotInRecents,
+ null,
+ new Rect(),
+ taskInfo,
+ false,
+ INVALID_WINDOW_TYPE
+ );
+ return target;
+ }
+
private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change,
SurfaceControl leash) {
return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()),
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 452d12a242c0..7e6f43458ba6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -46,7 +46,6 @@ import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.ITaskOrganizerController;
-import android.window.ScreenCapture;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
@@ -55,7 +54,6 @@ import android.window.TaskOrganizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.api.CompatUIHandler;
@@ -74,7 +72,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.function.Consumer;
/**
* Unified task organizer for all components in the shell.
@@ -561,19 +558,6 @@ public class ShellTaskOrganizer extends TaskOrganizer {
mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo()));
}
- /**
- * Take a screenshot of a task.
- */
- public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
- Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) {
- final TaskAppearedInfo info = mTasks.get(taskInfo.taskId);
- if (info == null) {
- return;
- }
- ScreenshotUtils.captureLayer(info.getLeash(), crop, consumer);
- }
-
-
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
synchronized (mLock) {
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 cf1d99e06d9b..b47adb43c2a6 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
@@ -489,10 +489,11 @@ public abstract class WMShellModule {
@Provides
static RecentsTransitionHandler provideRecentsTransitionHandler(
ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
Transitions transitions,
Optional<RecentTasksController> recentTasksController,
HomeTransitionObserver homeTransitionObserver) {
- return new RecentsTransitionHandler(shellInit, transitions,
+ return new RecentsTransitionHandler(shellInit, shellTaskOrganizer, transitions,
recentTasksController.orElse(null), homeTransitionObserver);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index c8ffe28da79c..bd6172226cf2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -162,7 +162,7 @@ fun maximizeSizeGivenAspectRatio(
fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth
val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
- if (taskInfo.appCompatTaskInfo.isTopActivityLetterboxed) {
+ if (taskInfo.appCompatTaskInfo.isTopActivityLetterboxed || !taskInfo.canChangeAspectRatio) {
return maxOf(appLetterboxWidth, appLetterboxHeight) /
minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index c660000e4f61..8077aeebf27f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -20,9 +20,12 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -41,6 +44,7 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -64,6 +68,7 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -79,10 +84,15 @@ import java.util.function.Consumer;
* Handles the Recents (overview) animation. Only one of these can run at a time. A recents
* transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
*/
-public class RecentsTransitionHandler implements Transitions.TransitionHandler {
+public class RecentsTransitionHandler implements Transitions.TransitionHandler,
+ Transitions.TransitionObserver {
private static final String TAG = "RecentsTransitionHandler";
+ // A placeholder for a synthetic transition that isn't backed by a true system transition
+ public static final IBinder SYNTHETIC_TRANSITION = new Binder();
+
private final Transitions mTransitions;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final ShellExecutor mExecutor;
@Nullable
private final RecentTasksController mRecentTasksController;
@@ -99,19 +109,26 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
private final HomeTransitionObserver mHomeTransitionObserver;
private @Nullable Color mBackgroundColor;
- public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
+ public RecentsTransitionHandler(
+ @NonNull ShellInit shellInit,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull Transitions transitions,
@Nullable RecentTasksController recentTasksController,
- HomeTransitionObserver homeTransitionObserver) {
+ @NonNull HomeTransitionObserver homeTransitionObserver) {
+ mShellTaskOrganizer = shellTaskOrganizer;
mTransitions = transitions;
mExecutor = transitions.getMainExecutor();
mRecentTasksController = recentTasksController;
mHomeTransitionObserver = homeTransitionObserver;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
if (recentTasksController == null) return;
- shellInit.addInitCallback(() -> {
- recentTasksController.setTransitionHandler(this);
- transitions.addHandler(this);
- }, this);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mRecentTasksController.setTransitionHandler(this);
+ mTransitions.addHandler(this);
+ mTransitions.registerObserver(this);
}
/** Register a mixer handler. {@see RecentsMixedHandler}*/
@@ -138,17 +155,59 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mBackgroundColor = color;
}
+ /**
+ * Starts a new real/synthetic recents transition.
+ */
@VisibleForTesting
public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
IApplicationThread appThread, IRecentsAnimationRunner listener) {
+ // only care about latest one.
+ mAnimApp = appThread;
+
+ // TODO(b/366021931): Formalize this later
+ final boolean isSyntheticRequest = options.containsKey("is_synthetic_recents_transition");
+ if (isSyntheticRequest) {
+ return startSyntheticRecentsTransition(listener);
+ } else {
+ return startRealRecentsTransition(intent, fillIn, options, listener);
+ }
+ }
+
+ /**
+ * Starts a synthetic recents transition that is not backed by a real WM transition.
+ */
+ private IBinder startSyntheticRecentsTransition(@NonNull IRecentsAnimationRunner listener) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsTransitionHandler.startRecentsTransition(synthetic)");
+ final RecentsController lastController = getLastController();
+ if (lastController != null) {
+ lastController.cancel(lastController.isSyntheticTransition()
+ ? "existing_running_synthetic_transition"
+ : "existing_running_transition");
+ return null;
+ }
+
+ // Create a new synthetic transition and start it immediately
+ final RecentsController controller = new RecentsController(listener);
+ controller.startSyntheticTransition();
+ mControllers.add(controller);
+ return SYNTHETIC_TRANSITION;
+ }
+
+ /**
+ * Starts a real WM-backed recents transition.
+ */
+ private IBinder startRealRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+ IRecentsAnimationRunner listener) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startRecentsTransition");
- // only care about latest one.
- mAnimApp = appThread;
- WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.sendPendingIntent(intent, fillIn, options);
- final RecentsController controller = new RecentsController(listener);
+
+ // Find the mixed handler which should handle this request (if we are in a state where a
+ // mixed handler is needed). This is slightly convoluted because starting the transition
+ // requires the handler, but the mixed handler also needs a reference to the transition.
RecentsMixedHandler mixer = null;
Consumer<IBinder> setTransitionForMixer = null;
for (int i = 0; i < mMixers.size(); ++i) {
@@ -160,12 +219,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
mixer == null ? this : mixer);
- for (int i = 0; i < mStateListeners.size(); i++) {
- mStateListeners.get(i).onTransitionStarted(transition);
- }
if (mixer != null) {
setTransitionForMixer.accept(transition);
}
+
+ final RecentsController controller = new RecentsController(listener);
if (transition != null) {
controller.setTransition(transition);
mControllers.add(controller);
@@ -187,11 +245,28 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
return null;
}
- private int findController(IBinder transition) {
+ /**
+ * Returns if there is currently a pending or active recents transition.
+ */
+ @Nullable
+ private RecentsController getLastController() {
+ return !mControllers.isEmpty() ? mControllers.getLast() : null;
+ }
+
+ /**
+ * Finds an existing controller for the provided {@param transition}, or {@code null} if none
+ * exists.
+ */
+ @Nullable
+ @VisibleForTesting
+ RecentsController findController(@NonNull IBinder transition) {
for (int i = mControllers.size() - 1; i >= 0; --i) {
- if (mControllers.get(i).mTransition == transition) return i;
+ final RecentsController controller = mControllers.get(i);
+ if (controller.mTransition == transition) {
+ return controller;
+ }
}
- return -1;
+ return null;
}
@Override
@@ -199,13 +274,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
Transitions.TransitionFinishCallback finishCallback) {
- final int controllerIdx = findController(transition);
- if (controllerIdx < 0) {
+ final RecentsController controller = findController(transition);
+ if (controller == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startAnimation: no controller found");
return false;
}
- final RecentsController controller = mControllers.get(controllerIdx);
final IApplicationThread animApp = mAnimApp;
mAnimApp = null;
if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
@@ -221,13 +295,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
- final int targetIdx = findController(mergeTarget);
- if (targetIdx < 0) {
+ final RecentsController controller = findController(mergeTarget);
+ if (controller == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.mergeAnimation: no controller found");
return;
}
- final RecentsController controller = mControllers.get(targetIdx);
controller.merge(info, t, finishCallback);
}
@@ -244,8 +317,21 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ RecentsController controller = findController(SYNTHETIC_TRANSITION);
+ if (controller != null) {
+ // Cancel the existing synthetic transition if there is one
+ controller.cancel("incoming_transition");
+ }
+ }
+
/** There is only one of these and it gets reset on finish. */
- private class RecentsController extends IRecentsAnimationController.Stub {
+ @VisibleForTesting
+ class RecentsController extends IRecentsAnimationController.Stub {
+
private final int mInstanceId;
private IRecentsAnimationRunner mListener;
@@ -307,7 +393,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mDeathHandler = () -> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
- finish(mWillFinishToHome, false /* leaveHint */, null /* finishCb */);
+ finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */,
+ "deathRecipient");
};
try {
mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
@@ -317,6 +404,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
+ /**
+ * Sets the started transition for this instance of the recents transition.
+ */
void setTransition(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.setTransition: id=%s", mInstanceId, transition);
@@ -330,6 +420,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
void cancel(boolean toHome, boolean withScreenshots, String reason) {
+ if (cancelSyntheticTransition(reason)) {
+ return;
+ }
+
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.cancel: toHome=%b reason=%s",
mInstanceId, toHome, reason);
@@ -341,7 +435,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
if (mFinishCB != null) {
- finishInner(toHome, false /* userLeave */, null /* finishCb */);
+ finishInner(toHome, false /* userLeave */, null /* finishCb */, "cancel");
} else {
cleanUp();
}
@@ -436,6 +530,91 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
+ /**
+ * Starts a new transition that is not backed by a system transition.
+ */
+ void startSyntheticTransition() {
+ mTransition = SYNTHETIC_TRANSITION;
+
+ // TODO(b/366021931): Update mechanism for pulling the home task, for now add home as
+ // both opening and closing since there's some pre-existing
+ // dependencies on having a closing task
+ final ActivityManager.RunningTaskInfo homeTask =
+ mShellTaskOrganizer.getRunningTasks(DEFAULT_DISPLAY).stream()
+ .filter(task -> task.getActivityType() == ACTIVITY_TYPE_HOME)
+ .findFirst()
+ .get();
+ final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget(
+ homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN,
+ 0, true /* isTranslucent */);
+ final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget(
+ homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE,
+ 0, true /* isTranslucent */);
+ final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
+ apps.add(openingTarget);
+ apps.add(closingTarget);
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.start: calling onAnimationStart with %d apps",
+ mInstanceId, apps.size());
+ mListener.onAnimationStart(this,
+ apps.toArray(new RemoteAnimationTarget[apps.size()]),
+ new RemoteAnimationTarget[0],
+ new Rect(0, 0, 0, 0), new Rect(), new Bundle());
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ mStateListeners.get(i).onAnimationStateChanged(true);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting recents animation", e);
+ cancel("startSynthetricTransition() failed");
+ }
+ }
+
+ /**
+ * Returns whether this transition is backed by a real system transition or not.
+ */
+ boolean isSyntheticTransition() {
+ return mTransition == SYNTHETIC_TRANSITION;
+ }
+
+ /**
+ * Called when a synthetic transition is canceled.
+ */
+ boolean cancelSyntheticTransition(String reason) {
+ if (!isSyntheticTransition()) {
+ return false;
+ }
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.cancelSyntheticTransition reason=%s",
+ mInstanceId, reason);
+ try {
+ // TODO(b/366021931): Notify the correct tasks once we build actual targets, and
+ // clean up leashes accordingly
+ mListener.onAnimationCanceled(new int[0], new TaskSnapshot[0]);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error canceling previous recents animation", e);
+ }
+ cleanUp();
+ return true;
+ }
+
+ /**
+ * Called when a synthetic transition is finished.
+ * @return
+ */
+ boolean finishSyntheticTransition() {
+ if (!isSyntheticTransition()) {
+ return false;
+ }
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.finishSyntheticTransition", mInstanceId);
+ // TODO(b/366021931): Clean up leashes accordingly
+ cleanUp();
+ return true;
+ }
+
boolean start(TransitionInfo info, SurfaceControl.Transaction t,
SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -662,7 +841,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
// Set the callback once again so we can finish correctly.
mFinishCB = finishCB;
finishInner(true /* toHome */, false /* userLeave */,
- null /* finishCb */);
+ null /* finishCb */, "takeOverAnimation");
}, updatedStates);
});
}
@@ -810,7 +989,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
sendCancelWithSnapshots();
mExecutor.executeDelayed(
() -> finishInner(true /* toHome */, false /* userLeaveHint */,
- null /* finishCb */), 0);
+ null /* finishCb */, "merge"), 0);
return;
}
if (recentsOpening != null) {
@@ -1005,7 +1184,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
return;
}
final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId()
- : Display.DEFAULT_DISPLAY;
+ : DEFAULT_DISPLAY;
// transient launches don't receive focus automatically. Since we are taking over
// the gesture now, take focus explicitly.
// This also moves recents back to top if the user gestured before a switch
@@ -1038,11 +1217,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
@Override
@SuppressLint("NewApi")
public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) {
- mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb));
+ mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb,
+ "requested"));
}
private void finishInner(boolean toHome, boolean sendUserLeaveHint,
- IResultReceiver runnerFinishCb) {
+ IResultReceiver runnerFinishCb, String reason) {
+ if (finishSyntheticTransition()) {
+ return;
+ }
+
if (mFinishCB == null) {
Slog.e(TAG, "Duplicate call to finish");
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
index e8733ebd8f03..95874c8193c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
@@ -24,7 +24,4 @@ public interface RecentsTransitionStateListener {
/** Notifies whether the recents animation is running. */
default void onAnimationStateChanged(boolean running) {
}
-
- /** Notifies that a recents shell transition has started. */
- default void onTransitionStarted(IBinder transition) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 6013a1ea1d9d..dec28fefd789 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.transition;
-import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
+import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -257,7 +257,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
@Override
public Transitions.TransitionHandler getHandlerForTakeover(
@NonNull IBinder transition, @NonNull TransitionInfo info) {
- if (!returnAnimationFrameworkLibrary()) {
+ if (!returnAnimationFrameworkLongLived()) {
return null;
}
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 aba8b61af306..d03832d3e85e 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
@@ -38,7 +38,7 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
+import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived;
import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
import static com.android.window.flags.Flags.migratePredictiveBackTransition;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
@@ -1252,7 +1252,7 @@ public class Transitions implements RemoteCallable<Transitions>,
@Nullable
public TransitionHandler getHandlerForTakeover(
@NonNull IBinder transition, @NonNull TransitionInfo info) {
- if (!returnAnimationFrameworkLibrary()) {
+ if (!returnAnimationFrameworkLongLived()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"Trying to get a handler for takeover but the flag is disabled");
return null;
@@ -1501,16 +1501,16 @@ public class Transitions implements RemoteCallable<Transitions>,
* transition animation. The Transition system will apply it when
* finishCallback is called by the transition handler.
*/
- void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ default void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction);
+ @NonNull SurfaceControl.Transaction finishTransaction) {}
/**
* Called when the transition is starting to play. It isn't called for merged transitions.
*
* @param transition the unique token of this transition
*/
- void onTransitionStarting(@NonNull IBinder transition);
+ default void onTransitionStarting(@NonNull IBinder transition) {}
/**
* Called when a transition is merged into another transition. There won't be any following
@@ -1519,7 +1519,7 @@ public class Transitions implements RemoteCallable<Transitions>,
* @param merged the unique token of the transition that's merged to another one
* @param playing the unique token of the transition that accepts the merge
*/
- void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing);
+ default void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {}
/**
* Called when the transition is finished. This isn't called for merged transitions.
@@ -1527,7 +1527,7 @@ public class Transitions implements RemoteCallable<Transitions>,
* @param transition the unique token of this transition
* @param aborted {@code true} if this transition is aborted; {@code false} otherwise.
*/
- void onTransitionFinished(@NonNull IBinder transition, boolean aborted);
+ default void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {}
}
@BinderThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index d43ee4425e6b..b1fc55f604d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -753,9 +753,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
final IconProvider provider = new IconProvider(mContext);
final Drawable appIconDrawable = provider.getIcon(activityInfo);
+ final Drawable badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable,
+ UserHandle.of(mTaskInfo.userId));
final BaseIconFactory headerIconFactory = createIconFactory(mContext,
R.dimen.desktop_mode_caption_icon_radius);
- mAppIconBitmap = headerIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+ mAppIconBitmap = headerIconFactory.createIconBitmap(badgedAppIconDrawable,
+ 1f /* scale */);
final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext,
R.dimen.desktop_mode_resize_veil_icon_size);
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt
new file mode 100644
index 000000000000..f08e50e0d4ee
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.Corners.LEFT_TOP
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartAppMediaProjectionResizeAndDrag {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(0)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun startMediaProjectionAndResize() {
+ mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
+
+ with(DesktopModeAppHelper(targetApp)) {
+ val windowRect = wmHelper.getWindowRegion(this).bounds
+ // Set start x-coordinate as center of app header.
+ val startX = windowRect.centerX()
+ val startY = windowRect.top
+
+ dragWindow(startX, startY, endX = startX + 150, endY = startY + 150, wmHelper, device)
+ cornerResize(wmHelper, device, LEFT_TOP, -200, -200)
+ }
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ targetApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
new file mode 100644
index 000000000000..717ea306eb77
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 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.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartAppMediaProjectionWithMaxDesktopWindows {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val mailApp = MailAppHelper(instrumentation)
+ private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+ private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+ private val simpleApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(0)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun startMediaProjection() {
+ // TODO(b/366455106) - handle max task Limit
+ mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
+ mailApp.launchViaIntent(wmHelper)
+ simpleApp.launchViaIntent(wmHelper)
+ newTasksApp.launchViaIntent(wmHelper)
+ imeApp.launchViaIntent(wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ mailApp.exit(wmHelper)
+ simpleApp.exit(wmHelper)
+ newTasksApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
+ targetApp.exit(wmHelper)
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt
new file mode 100644
index 000000000000..005195296c62
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartScreenMediaProjectionWithMaxDesktopWindows {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+ private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+ private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+ private val simpleApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun startMediaProjection() {
+ mediaProjectionAppHelper.startEntireScreenMediaProjection(wmHelper)
+ simpleApp.launchViaIntent(wmHelper)
+ mailApp.launchViaIntent(wmHelper)
+ newTasksApp.launchViaIntent(wmHelper)
+ imeApp.launchViaIntent(wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ simpleApp.exit(wmHelper)
+ mailApp.exit(wmHelper)
+ newTasksApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp
new file mode 100644
index 000000000000..85e6a8d1d865
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp
@@ -0,0 +1,38 @@
+//
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsMediaProjection",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "WMShellFlickerTestsBase",
+ "WMShellScenariosMediaProjection",
+ "WMShellTestUtils",
+ ],
+ data: ["trace_config/*"],
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml
new file mode 100644
index 000000000000..74b0daf3a2aa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml
@@ -0,0 +1,85 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.wm.shell.flicker">
+
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+ <!-- Read and write traces from external storage -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <!-- Allow the test to write directly to /sdcard/ -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <!-- Write secure settings -->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Capture screen contents -->
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <!-- Enable / Disable tracing !-->
+ <uses-permission android:name="android.permission.DUMP" />
+ <!-- Run layers trace -->
+ <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+ <!-- Capture screen recording -->
+ <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+ <!-- Workaround grant runtime permission exception from b/152733071 -->
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+ <!-- Force-stop test apps -->
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+ <!-- Control test app's media session -->
+ <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <!-- ATM.removeRootTasksWithActivityTypes() -->
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+ <!-- Enable bubble notification-->
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+ <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
+ <uses-library android:name="android.test.runner"/>
+
+ <service android:name=".NotificationListener"
+ android:exported="true"
+ android:label="WMShellTestsNotificationListenerService"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService" />
+ </intent-filter>
+ </service>
+
+ <service android:name="com.android.wm.shell.flicker.utils.MediaProjectionService"
+ android:foregroundServiceType="mediaProjection"
+ android:label="WMShellTestsMediaProjectionService"
+ android:enabled="true">
+ </service>
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker"
+ android:label="WindowManager Shell Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml
new file mode 100644
index 000000000000..40dbbac32c7f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+ <option name="test-tag" value="FlickerTests"/>
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
+ <!-- keeps the screen on during tests -->
+ <option name="screen-always-on" value="on"/>
+ <!-- prevents the phone from restarting -->
+ <option name="force-skip-system-props" value="true"/>
+ <!-- set WM tracing verbose level to all -->
+ <option name="run-command" value="cmd window tracing level all"/>
+ <!-- set WM tracing to frame (avoid incomplete states) -->
+ <option name="run-command" value="cmd window tracing frame"/>
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+ <!-- ensure lock screen mode is swipe -->
+ <option name="run-command" value="locksettings set-disabled false"/>
+ <!-- restart launcher to activate TAPL -->
+ <option name="run-command"
+ value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+ <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+ <option name="run-command" value="cmd window tracing size 20480"/>
+ <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="test-user-token" value="%TEST_USER%"/>
+ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
+ <option name="run-command" value="settings put system show_touches 1"/>
+ <option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="teardown-command"
+ value="settings delete secure show_ime_with_hard_keyboard"/>
+ <option name="teardown-command" value="settings delete system show_touches"/>
+ <option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command"
+ value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="{MODULE}.apk"/>
+ <option name="test-file-name" value="FlickerTestApp.apk"/>
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file"
+ key="trace_config.textproto"
+ value="/data/misc/perfetto-traces/trace_config.textproto"
+ />
+ <!--Install the content provider automatically when we push some file in sdcard folder.-->
+ <!--Needed to avoid the installation during the test suite.-->
+ <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="{PACKAGE}"/>
+ <option name="shell-timeout" value="6600s"/>
+ <option name="test-timeout" value="6000s"/>
+ <option name="hidden-api-checks" value="false"/>
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+ <option name="instrumentation-arg"
+ key="perfetto_config_file"
+ value="trace_config.textproto"
+ />
+ <option name="instrumentation-arg" key="per_run" value="true"/>
+ <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
+ </test>
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml
new file mode 100644
index 000000000000..4bd9ca049f55
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto
new file mode 100644
index 000000000000..9f2e49755fec
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto
@@ -0,0 +1,71 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+ size_kb: 63488
+ fill_policy: RING_BUFFER
+}
+
+data_sources {
+ config {
+ name: "linux.process_stats"
+ target_buffer: 0
+ # polled per-process memory counters and process/thread names.
+ # If you don't want the polled counters, remove the "process_stats_config"
+ # section, but keep the data source itself as it still provides on-demand
+ # thread/process naming for ftrace data below.
+ process_stats_config {
+ scan_all_processes_on_start: true
+ }
+ }
+}
+
+data_sources: {
+ config {
+ name: "linux.ftrace"
+ ftrace_config {
+ ftrace_events: "ftrace/print"
+ ftrace_events: "task/task_newtask"
+ ftrace_events: "task/task_rename"
+ atrace_categories: "ss"
+ atrace_categories: "wm"
+ atrace_categories: "am"
+ atrace_categories: "aidl"
+ atrace_categories: "input"
+ atrace_categories: "binder_driver"
+ atrace_categories: "sched_process_exit"
+ atrace_apps: "com.android.server.wm.flicker.testapp"
+ atrace_apps: "com.android.systemui"
+ atrace_apps: "com.android.wm.shell.flicker.service"
+ atrace_apps: "com.google.android.apps.nexuslauncher"
+ }
+ }
+}
+
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/Android.bp b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/Android.bp
new file mode 100644
index 000000000000..997a0af68d1a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/Android.bp
@@ -0,0 +1,46 @@
+//
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library {
+ name: "WMShellScenariosMediaProjection",
+ platform_apis: true,
+ optimize: {
+ enabled: false,
+ },
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "WMShellFlickerTestsBase",
+ "WMShellTestUtils",
+ "wm-shell-flicker-utils",
+ "androidx.test.ext.junit",
+ "flickertestapplib",
+ "flickerlib-helpers",
+ "flickerlib-trace_processor_shell",
+ "platform-test-annotations",
+ "wm-flicker-common-app-helpers",
+ "launcher-helper-lib",
+ "launcher-aosp-tapl",
+ ],
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
new file mode 100644
index 000000000000..1573b58853da
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 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.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartAppMediaProjectionWithDisplayRotations {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val initialRotation = Rotation.ROTATION_0
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(initialRotation.value)
+ testApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun startMediaProjectionAndRotate() {
+ mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+ ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_90)
+ ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_270)
+ ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_0)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
new file mode 100644
index 000000000000..e80a895c1aa6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 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.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartScreenMediaProjectionWithDisplayRotations {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val initialRotation = Rotation.ROTATION_0
+ private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ testApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun startMediaProjectionAndRotate() {
+ mediaProjectionAppHelper.startEntireScreenMediaProjection(wmHelper)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+ ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_90)
+ ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_270)
+ ChangeDisplayOrientationRule.setRotation(initialRotation)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionService.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionService.kt
new file mode 100644
index 000000000000..aa4e216f01a2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionService.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 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.flicker.utils
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.drawable.Icon
+import android.os.IBinder
+import android.os.Message
+import android.os.Messenger
+import android.os.RemoteException
+import android.util.Log
+
+class MediaProjectionService : Service() {
+
+ private var mTestBitmap: Bitmap? = null
+
+ private val notificationId: Int = 1
+ private val notificationChannelId: String = "MediaProjectionFlickerTest"
+ private val notificationChannelName = "FlickerMediaProjectionService"
+
+ var mMessenger: Messenger? = null
+
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ mMessenger = intent.extras?.getParcelable(
+ MediaProjectionUtils.EXTRA_MESSENGER, Messenger::class.java)
+ startForeground()
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ override fun onBind(intent: Intent?): IBinder? = null
+
+ override fun onDestroy() {
+ mTestBitmap?.recycle()
+ mTestBitmap = null
+ sendMessage(MediaProjectionUtils.MSG_SERVICE_DESTROYED)
+ super.onDestroy()
+ }
+
+ private fun createNotificationIcon(): Icon {
+ Log.d(TAG, "createNotification")
+
+ mTestBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(mTestBitmap!!)
+ canvas.drawColor(Color.BLUE)
+ return Icon.createWithBitmap(mTestBitmap)
+ }
+
+ private fun startForeground() {
+ Log.d(TAG, "startForeground")
+ val channel = NotificationChannel(
+ notificationChannelId,
+ notificationChannelName, NotificationManager.IMPORTANCE_NONE
+ )
+ channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
+
+ val notificationManager: NotificationManager =
+ getSystemService(NotificationManager::class.java)
+ notificationManager.createNotificationChannel(channel)
+
+ val notificationBuilder: Notification.Builder =
+ Notification.Builder(this, notificationChannelId)
+
+ val notification = notificationBuilder.setOngoing(true)
+ .setContentTitle("App is running")
+ .setSmallIcon(createNotificationIcon())
+ .setCategory(Notification.CATEGORY_SERVICE)
+ .setContentText("Context")
+ .build()
+
+ startForeground(notificationId, notification)
+ sendMessage(MediaProjectionUtils.MSG_START_FOREGROUND_DONE)
+ }
+
+ fun sendMessage(what: Int) {
+ Log.d(TAG, "sendMessage")
+ with(Message.obtain()) {
+ this.what = what
+ try {
+ mMessenger!!.send(this)
+ } catch (e: RemoteException) {
+ Log.d(TAG, "Unable to send message", e)
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG: String = "FlickerMediaProjectionService"
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt
index e3beff727ae3..f9706969ff11 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.wm.shell.flicker.utils
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.noopGridConsistencyInteractor by Kosmos.Fixture { NoopGridConsistencyInteractor() }
+object MediaProjectionUtils {
+ const val REQUEST_CODE: Int = 99
+ const val MSG_START_FOREGROUND_DONE: Int = 1
+ const val MSG_SERVICE_DESTROYED: Int = 2
+ const val EXTRA_MESSENGER: String = "messenger"
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 712c30641200..e610ebd6bfab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -18,7 +18,9 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RecentTaskInfo
import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityOptions
import android.app.KeyguardManager
+import android.app.PendingIntent
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -44,6 +46,7 @@ import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
+import android.view.DragEvent
import android.view.Gravity
import android.view.SurfaceControl
import android.view.WindowManager
@@ -108,6 +111,7 @@ import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import java.util.function.Consumer
import java.util.Optional
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -132,8 +136,8 @@ import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
-import org.mockito.kotlin.eq
import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -2991,6 +2995,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
configuration.windowConfiguration.appBounds = bounds
}
+ appCompatTaskInfo.topActivityLetterboxAppWidth = bounds.width()
+ appCompatTaskInfo.topActivityLetterboxAppHeight = bounds.height()
isResizeable = false
}
@@ -3085,6 +3091,95 @@ class DesktopTasksControllerTest : ShellTestCase() {
assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
}
+
+ @Test
+ fun onUnhandledDrag_newFreeformIntent() {
+ testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ PointF(1200f, 700f),
+ Rect(240, 700, 2160, 1900))
+ }
+
+ @Test
+ fun onUnhandledDrag_newFreeformIntentSplitLeft() {
+ testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+ PointF(50f, 700f),
+ Rect(0, 0, 500, 1000))
+ }
+
+ @Test
+ fun onUnhandledDrag_newFreeformIntentSplitRight() {
+ testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+ PointF(2500f, 700f),
+ Rect(500, 0, 1000, 1000))
+ }
+
+ @Test
+ fun onUnhandledDrag_newFullscreenIntent() {
+ testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ PointF(1200f, 50f),
+ Rect())
+ }
+
+ /**
+ * Assert that an unhandled drag event launches a PendingIntent with the
+ * windowing mode and bounds we are expecting.
+ */
+ private fun testOnUnhandledDrag(
+ indicatorType: DesktopModeVisualIndicator.IndicatorType,
+ inputCoordinate: PointF,
+ expectedBounds: Rect
+ ) {
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask()
+ markTaskVisible(task)
+ task.isFocused = true
+ val runningTasks = ArrayList<RunningTaskInfo>()
+ runningTasks.add(task)
+ val spyController = spy(controller)
+ val mockPendingIntent = mock(PendingIntent::class.java)
+ val mockDragEvent = mock(DragEvent::class.java)
+ val mockCallback = mock(Consumer::class.java)
+ val b = SurfaceControl.Builder()
+ b.setName("test surface")
+ val dragSurface = b.build()
+ whenever(shellTaskOrganizer.runningTasks).thenReturn(runningTasks)
+ whenever(mockDragEvent.dragSurface).thenReturn(dragSurface)
+ whenever(mockDragEvent.x).thenReturn(inputCoordinate.x)
+ whenever(mockDragEvent.y).thenReturn(inputCoordinate.y)
+ whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull())).thenReturn(true)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ doReturn(indicatorType)
+ .whenever(spyController).updateVisualIndicator(
+ eq(task),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ eq(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+ )
+
+ spyController.onUnhandledDrag(
+ mockPendingIntent,
+ mockDragEvent,
+ mockCallback as Consumer<Boolean>
+ )
+ val arg: ArgumentCaptor<WindowContainerTransaction> =
+ ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ var expectedWindowingMode: Int
+ if (indicatorType == DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) {
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN
+ // Fullscreen launches currently use default transitions
+ verify(transitions).startTransition(any(), capture(arg), anyOrNull())
+ } else {
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM
+ // All other launches use a special handler.
+ verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg))
+ }
+ assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
+ .launchWindowingMode).isEqualTo(expectedWindowingMode)
+ assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
+ .launchBounds).isEqualTo(expectedBounds)
+ }
+
private val desktopWallpaperIntent: Intent
get() = Intent(context, DesktopWallpaperActivity::class.java)
@@ -3149,6 +3244,18 @@ class DesktopTasksControllerTest : ShellTestCase() {
appCompatTaskInfo.isUserFullscreenOverrideEnabled = enableUserFullscreenOverride
appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
+ if (deviceOrientation == ORIENTATION_LANDSCAPE) {
+ configuration.windowConfiguration.appBounds =
+ Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
+ appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_LONG
+ appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_SHORT
+ } else {
+ configuration.windowConfiguration.appBounds =
+ Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
+ appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_SHORT
+ appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_LONG
+ }
+
if (shouldLetterbox) {
appCompatTaskInfo.setHasMinAspectRatioOverride(aspectRatioOverrideApplied)
if (deviceOrientation == ORIENTATION_LANDSCAPE &&
@@ -3165,14 +3272,6 @@ class DesktopTasksControllerTest : ShellTestCase() {
appCompatTaskInfo.topActivityLetterboxAppHeight = 1200
}
}
-
- if (deviceOrientation == ORIENTATION_LANDSCAPE) {
- configuration.windowConfiguration.appBounds =
- Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
- } else {
- configuration.windowConfiguration.appBounds =
- Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
- }
}
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
runningTasks.add(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
new file mode 100644
index 000000000000..769acf7fdfde
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.
+ */
+
+package com.android.wm.shell.recents;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.HomeTransitionObserver;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.Optional;
+
+/**
+ * Tests for {@link RecentTasksController}
+ *
+ * Usage: atest WMShellUnitTests:RecentsTransitionHandlerTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RecentsTransitionHandlerTest extends ShellTestCase {
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private TaskStackListenerImpl mTaskStackListener;
+ @Mock
+ private ShellCommandHandler mShellCommandHandler;
+ @Mock
+ private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ @Mock
+ private ActivityTaskManager mActivityTaskManager;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
+ @Mock
+ private IRecentTasksListener mRecentTasksListener;
+ @Mock
+ private TaskStackTransitionObserver mTaskStackTransitionObserver;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private ShellTaskOrganizer mShellTaskOrganizer;
+ private RecentTasksController mRecentTasksController;
+ private RecentTasksController mRecentTasksControllerReal;
+ private RecentsTransitionHandler mRecentsTransitionHandler;
+ private ShellInit mShellInit;
+ private ShellController mShellController;
+ private TestShellExecutor mMainExecutor;
+ private static StaticMockitoSession sMockitoSession;
+
+ @Before
+ public void setUp() {
+ sMockitoSession = mockitoSession().initMocks(this).strictness(Strictness.LENIENT)
+ .mockStatic(DesktopModeStatus.class).startMocking();
+ ExtendedMockito.doReturn(true)
+ .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+
+ mMainExecutor = new TestShellExecutor();
+ when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+ when(mContext.getSystemService(KeyguardManager.class))
+ .thenReturn(mock(KeyguardManager.class));
+ mShellInit = spy(new ShellInit(mMainExecutor));
+ mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
+ mDisplayInsetsController, mMainExecutor));
+ mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
+ mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+ Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+ mMainExecutor);
+ mRecentTasksController = spy(mRecentTasksControllerReal);
+ mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
+ null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
+ mMainExecutor);
+
+ final Transitions transitions = mock(Transitions.class);
+ doReturn(mMainExecutor).when(transitions).getMainExecutor();
+ mRecentsTransitionHandler = new RecentsTransitionHandler(mShellInit, mShellTaskOrganizer,
+ transitions, mRecentTasksController, mock(HomeTransitionObserver.class));
+
+ mShellInit.init();
+ }
+
+ @After
+ public void tearDown() {
+ sMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testStartSyntheticRecentsTransition_callsOnAnimationStart() throws Exception {
+ final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
+ doReturn(new Binder()).when(runner).asBinder();
+ Bundle options = new Bundle();
+ options.putBoolean("is_synthetic_recents_transition", true);
+ IBinder transition = mRecentsTransitionHandler.startRecentsTransition(
+ mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class),
+ runner);
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+
+ // Finish and verify no transition remains
+ mRecentsTransitionHandler.findController(transition).finish(true /* toHome */,
+ false /* sendUserLeaveHint */, null /* finishCb */);
+ mMainExecutor.flushAll();
+ assertNull(mRecentsTransitionHandler.findController(transition));
+ }
+
+ @Test
+ public void testStartSyntheticRecentsTransition_callsOnAnimationCancel() throws Exception {
+ final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
+ doReturn(new Binder()).when(runner).asBinder();
+ Bundle options = new Bundle();
+ options.putBoolean("is_synthetic_recents_transition", true);
+ IBinder transition = mRecentsTransitionHandler.startRecentsTransition(
+ mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class),
+ runner);
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+
+ mRecentsTransitionHandler.findController(transition).cancel("test");
+ mMainExecutor.flushAll();
+ verify(runner).onAnimationCanceled(any(), any());
+ assertNull(mRecentsTransitionHandler.findController(transition));
+ }
+}
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 7937a843b90a..fec9e3ebd1ef 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
@@ -557,7 +557,7 @@ public class ShellTransitionTests extends ShellTestCase {
mMainExecutor.flushAll();
// Takeover shouldn't happen when the flag is disabled.
- setFlagsRule.disableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY);
+ setFlagsRule.disableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED);
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
@@ -572,7 +572,7 @@ public class ShellTransitionTests extends ShellTestCase {
verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
// Takeover should happen when the flag is enabled.
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY);
+ setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED);
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
info = new TransitionInfoBuilder(TRANSIT_OPEN)
@@ -1211,7 +1211,7 @@ public class ShellTransitionTests extends ShellTestCase {
mTransactionPool, createTestDisplayController(), mMainExecutor,
mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
final RecentsTransitionHandler recentsHandler =
- new RecentsTransitionHandler(shellInit, transitions,
+ new RecentsTransitionHandler(shellInit, mock(ShellTaskOrganizer.class), transitions,
mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
shellInit.init();
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 25c063d6ccd8..202535d45191 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -273,6 +273,7 @@ LIBANDROID {
ASurfaceTransaction_fromJava; # introduced=34
ASurfaceTransaction_reparent; # introduced=29
ASurfaceTransaction_setBuffer; # introduced=29
+ ASurfaceTransaction_setBufferWithRelease; # introduced=36
ASurfaceTransaction_setBufferAlpha; # introduced=29
ASurfaceTransaction_setBufferDataSpace; # introduced=29
ASurfaceTransaction_setBufferTransparency; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 6ce83cd7b765..e46db6bb3727 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -416,6 +416,35 @@ void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction,
transaction->setBuffer(surfaceControl, graphic_buffer, fence);
}
+void ASurfaceTransaction_setBufferWithRelease(
+ ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+ AHardwareBuffer* buffer, int acquire_fence_fd, void* _Null_unspecified context,
+ ASurfaceTransaction_OnBufferRelease aReleaseCallback) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+ CHECK_NOT_NULL(aReleaseCallback);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ sp<GraphicBuffer> graphic_buffer(GraphicBuffer::fromAHardwareBuffer(buffer));
+
+ std::optional<sp<Fence>> fence = std::nullopt;
+ if (acquire_fence_fd != -1) {
+ fence = new Fence(acquire_fence_fd);
+ }
+
+ ReleaseBufferCallback releaseBufferCallback =
+ [context,
+ aReleaseCallback](const ReleaseCallbackId&, const sp<Fence>& releaseFence,
+ std::optional<uint32_t> /* currentMaxAcquiredBufferCount */) {
+ (*aReleaseCallback)(context, (releaseFence) ? releaseFence->dup() : -1);
+ };
+
+ transaction->setBuffer(surfaceControl, graphic_buffer, fence, /* frameNumber */ std::nullopt,
+ /* producerId */ 0, releaseBufferCallback);
+}
+
void ASurfaceTransaction_setGeometry(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl, const ARect& source,
const ARect& destination, int32_t transform) {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index feee89a51e7c..0b094a2f70e2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1409,6 +1409,8 @@
<string name="media_transfer_this_device_name">This phone</string>
<!-- Name of the tablet device. [CHAR LIMIT=30] -->
<string name="media_transfer_this_device_name_tablet">This tablet</string>
+ <!-- Name of the internal speaker. [CHAR LIMIT=30] -->
+ <string name="media_transfer_this_device_name_desktop">This computer (internal)</string>
<!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
<string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string>
<!-- Name of the internal mic. [CHAR LIMIT=30] -->
@@ -1639,6 +1641,12 @@
<!-- Name of the 3.5mm and usb audio device. [CHAR LIMIT=50] -->
<string name="media_transfer_wired_usb_device_name">Wired headphone</string>
+ <!-- Name of the 3.5mm headphone, used in desktop devices. [CHAR LIMIT=50] -->
+ <string name="media_transfer_headphone_name">Headphone</string>
+
+ <!-- Name of the usb audio device speaker, used in desktop devices. [CHAR LIMIT=50] -->
+ <string name="media_transfer_usb_speaker_name">USB speaker</string>
+
<!-- Name of the 3.5mm audio device mic. [CHAR LIMIT=50] -->
<string name="media_transfer_wired_device_mic_name">Mic jack</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
index 548eb3fd4b8f..874e03012ae2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -57,7 +57,7 @@ public final class InputRouteManager {
}
};
- /* package */ InputRouteManager(@NonNull Context context, @NonNull AudioManager audioManager) {
+ public InputRouteManager(@NonNull Context context, @NonNull AudioManager audioManager) {
mContext = context;
mAudioManager = audioManager;
Handler handler = new Handler(context.getMainLooper());
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 9eaf8d3838d8..116de567a7da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -72,6 +72,8 @@ public class PhoneMediaDevice extends MediaDevice {
return context.getString(R.string.media_transfer_this_device_name_tv);
} else if (isTablet()) {
return context.getString(R.string.media_transfer_this_device_name_tablet);
+ } else if (inputRoutingEnabledAndIsDesktop()) {
+ return context.getString(R.string.media_transfer_this_device_name_desktop);
} else {
return context.getString(R.string.media_transfer_this_device_name);
}
@@ -85,10 +87,18 @@ public class PhoneMediaDevice extends MediaDevice {
switch (routeInfo.getType()) {
case TYPE_WIRED_HEADSET:
case TYPE_WIRED_HEADPHONES:
+ name =
+ inputRoutingEnabledAndIsDesktop()
+ ? context.getString(R.string.media_transfer_headphone_name)
+ : context.getString(R.string.media_transfer_wired_usb_device_name);
+ break;
case TYPE_USB_DEVICE:
case TYPE_USB_HEADSET:
case TYPE_USB_ACCESSORY:
- name = context.getString(R.string.media_transfer_wired_usb_device_name);
+ name =
+ inputRoutingEnabledAndIsDesktop()
+ ? context.getString(R.string.media_transfer_usb_speaker_name)
+ : context.getString(R.string.media_transfer_wired_usb_device_name);
break;
case TYPE_DOCK:
name = context.getString(R.string.media_transfer_dock_speaker_device_name);
@@ -139,6 +149,16 @@ public class PhoneMediaDevice extends MediaDevice {
.contains("tablet");
}
+ static boolean isDesktop() {
+ return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
+ .contains("desktop");
+ }
+
+ static boolean inputRoutingEnabledAndIsDesktop() {
+ return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl()
+ && isDesktop();
+ }
+
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index e2d58d660fd5..23cfc01b07b8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -47,6 +47,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowSystemProperties;
@RunWith(RobolectricTestRunner.class)
public class PhoneMediaDeviceTest {
@@ -114,6 +115,31 @@ public class PhoneMediaDeviceTest {
when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
+ assertThat(mPhoneMediaDevice.getName()).isEqualTo(getMediaTransferThisDeviceName(mContext));
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void getName_returnCorrectName_desktop() {
+ ShadowSystemProperties.override("ro.build.characteristics", "desktop");
+
+ when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
+
+ assertThat(mPhoneMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name));
+
+ when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADSET);
+
+ assertThat(mPhoneMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_headphone_name));
+
+ when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE);
+
+ assertThat(mPhoneMediaDevice.getName())
+ .isEqualTo(mContext.getString(R.string.media_transfer_usb_speaker_name));
+
+ when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
+
assertThat(mPhoneMediaDevice.getName())
.isEqualTo(getMediaTransferThisDeviceName(mContext));
}
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index c107ff5a34ce..1a99d25786ff 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -36,6 +36,7 @@ android_library {
"aconfig_new_storage_flags_lib",
"aconfigd_java_utils",
"aconfig_demo_flags_java_lib",
+ "configinfra_framework_flags_java_lib",
"device_config_service_flags_java",
"libaconfig_java_proto_lite",
"SettingsLibDeviceStateRotationLock",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index c9ad5a5e3b90..fbce6ca07b3e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -99,13 +99,23 @@ public final class DeviceConfigService extends Binder {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.print("SyncDisabledForTests: ");
- MyShellCommand.getSyncDisabledForTests(pw, pw);
-
- pw.print("Is mainline: ");
- pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService());
+ if (android.provider.flags.Flags.dumpImprovements()) {
+ pw.print("SyncDisabledForTests: ");
+ MyShellCommand.getSyncDisabledForTests(pw, pw);
+
+ pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): ");
+ pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService());
+
+ pw.println("DeviceConfig provider: ");
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) {
+ DeviceConfig.dump(pfd, pw, /* prefix= */ " ", args);
+ } catch (IOException e) {
+ pw.print("IOException creating ParcelFileDescriptor: ");
+ pw.println(e);
+ }
+ }
- final IContentProvider iprovider = mProvider.getIContentProvider();
+ IContentProvider iprovider = mProvider.getIContentProvider();
pw.println("DeviceConfig flags:");
for (String line : MyShellCommand.listAll(iprovider)) {
pw.println(line);
@@ -251,22 +261,13 @@ public final class DeviceConfigService extends Binder {
public static HashMap<String, String> getAllFlags(IContentProvider provider) {
HashMap<String, String> allFlags = new HashMap<String, String>();
- try {
- Bundle args = new Bundle();
- args.putInt(Settings.CALL_METHOD_USER_KEY,
- ActivityManager.getService().getCurrentUser().id);
- Bundle b = provider.call(new AttributionSource(Process.myUid(),
- resolveCallingPackage(), null), Settings.AUTHORITY,
- Settings.CALL_METHOD_LIST_CONFIG, null, args);
- if (b != null) {
- Map<String, String> flagsToValues =
- (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
- allFlags.putAll(flagsToValues);
+ for (DeviceConfig.Properties properties : DeviceConfig.getAllProperties()) {
+ List<String> keys = new ArrayList<>(properties.getKeyset());
+ for (String flagName : properties.getKeyset()) {
+ String fullName = properties.getNamespace() + "/" + flagName;
+ allFlags.put(fullName, properties.getString(flagName, null));
}
- } catch (RemoteException e) {
- throw new RuntimeException("Failed in IPC", e);
}
-
return allFlags;
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f59eab001be9..cd16af76d4b8 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -452,7 +452,7 @@ filegroup {
"tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java",
"tests/src/**/systemui/doze/DozeScreenStateTest.java",
"tests/src/**/systemui/keyguard/WorkLockActivityControllerTest.java",
- "tests/src/**/systemui/media/dialog/MediaOutputControllerTest.java",
+ "tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java",
"tests/src/**/systemui/navigationbar/views/NavigationBarTest.java",
"tests/src/**/systemui/power/PowerNotificationWarningsTest.java",
"tests/src/**/systemui/power/PowerUITest.java",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7974f9222a0c..98c491b82e2e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -149,6 +149,16 @@ flag {
}
flag {
+ name: "modes_dialog_single_rows"
+ namespace: "systemui"
+ description: "[Experiment] Display one entry per grid row in the Modes Dialog."
+ bug: "366034002"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "pss_app_selector_recents_split_screen"
namespace: "systemui"
description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
@@ -528,6 +538,13 @@ flag {
}
flag {
+ name: "status_bar_connected_displays"
+ namespace: "systemui"
+ description: "Shows the status bar on connected displays"
+ bug: "362720336"
+}
+
+flag {
name: "status_bar_switch_to_spn_from_data_spn"
namespace: "systemui"
description: "Fix usage of the SPN broadcast extras"
@@ -538,13 +555,6 @@ flag {
}
flag {
- name: "haptic_volume_slider"
- namespace: "systemui"
- description: "Adds haptic feedback to the volume slider."
- bug: "316953430"
-}
-
-flag {
name: "new_volume_panel"
namespace: "systemui"
description: "Switches to the new volume panel (without Slices)."
@@ -668,13 +678,6 @@ flag {
}
flag {
- name: "compose_lockscreen"
- namespace: "systemui"
- description: "Enables the compose version of lockscreen that runs standalone, outside of Flexiglass."
- bug: "301968149"
-}
-
-flag {
name: "enable_contextual_tip_for_power_off"
namespace: "systemui"
description: "Enables on-screen contextual tip about how to power off or restart phone"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 9d0b095ad4cc..d02527531a53 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -57,6 +57,7 @@ import com.android.systemui.Flags.activityTransitionUseLargestWindow
import com.android.systemui.Flags.translucentOccludingActivityFix
import com.android.systemui.animation.TransitionAnimator.Companion.toTransitionState
import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
+import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived
import com.android.wm.shell.shared.IShellTransitions
import com.android.wm.shell.shared.ShellTransitions
import java.util.concurrent.Executor
@@ -607,8 +608,8 @@ constructor(
* this registration.
*/
fun register(controller: Controller) {
- check(returnAnimationFrameworkLibrary()) {
- "Long-lived registrations cannot be used when the returnAnimationFrameworkLibrary " +
+ check(returnAnimationFrameworkLongLived()) {
+ "Long-lived registrations cannot be used when the returnAnimationFrameworkLongLived " +
"flag is disabled"
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 163b35596c1b..8321238b28b1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -16,7 +16,6 @@
package com.android.systemui.bouncer.ui.composable
-import android.view.HapticFeedbackConstants
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
@@ -133,10 +132,7 @@ fun PatternBouncer(
// Perform haptic feedback, but only if the current dot is not null, so we don't perform it
// when the UI first shows up or when the user lifts their pointer/finger.
if (currentDot != null) {
- view.performHapticFeedback(
- HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
- )
+ viewModel.performDotFeedback(view)
}
if (!isAnimationEnabled) {
@@ -206,10 +202,7 @@ fun PatternBouncer(
// Show the failure animation if the user entered the wrong input.
LaunchedEffect(animateFailure) {
if (animateFailure) {
- showFailureAnimation(
- dots = dots,
- scalingAnimatables = dotScalingAnimatables,
- )
+ showFailureAnimation(dots = dots, scalingAnimatables = dotScalingAnimatables)
viewModel.onFailureAnimationShown()
}
}
@@ -358,15 +351,10 @@ fun PatternBouncer(
(1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
drawCircle(
center =
- pixelOffset(
- dot,
- spacing,
- horizontalOffset,
- verticalOffset + appearOffset,
- ),
+ pixelOffset(dot, spacing, horizontalOffset, verticalOffset + appearOffset),
color =
dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value),
- radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value
+ radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value,
)
}
}
@@ -387,7 +375,7 @@ private suspend fun showEntryAnimation(
delayMillis = 33 * dot.y,
durationMillis = 450,
easing = Easings.LegacyDecelerate,
- )
+ ),
)
}
}
@@ -400,7 +388,7 @@ private suspend fun showEntryAnimation(
delayMillis = 0,
durationMillis = 450 + (33 * dot.y),
easing = Easings.StandardDecelerate,
- )
+ ),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 489e24e8b328..0830c9b359a4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -16,8 +16,8 @@
package com.android.systemui.bouncer.ui.composable
-import android.view.HapticFeedbackConstants
import android.view.MotionEvent
+import android.view.View
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
@@ -72,11 +72,7 @@ import kotlinx.coroutines.launch
/** Renders the PIN button pad. */
@Composable
-fun PinPad(
- viewModel: PinBouncerViewModel,
- verticalSpacing: Dp,
- modifier: Modifier = Modifier,
-) {
+fun PinPad(viewModel: PinBouncerViewModel, verticalSpacing: Dp, modifier: Modifier = Modifier) {
DisposableEffect(Unit) { onDispose { viewModel.onHidden() } }
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle()
@@ -104,7 +100,7 @@ fun PinPad(
columns = columns,
verticalSpacing = verticalSpacing,
horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp),
- modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid")
+ modifier = modifier.focusRequester(focusRequester).sysuiResTag("pin_pad_grid"),
) {
repeat(9) { index ->
DigitButton(
@@ -126,10 +122,11 @@ fun PinPad(
),
isInputEnabled = isInputEnabled,
onClicked = viewModel::onBackspaceButtonClicked,
+ onPointerDown = viewModel::onBackspaceButtonPressed,
onLongPressed = viewModel::onBackspaceButtonLongPressed,
appearance = backspaceButtonAppearance,
scaling = buttonScaleAnimatables[9]::value,
- elementId = "delete_button"
+ elementId = "delete_button",
)
DigitButton(
@@ -138,7 +135,7 @@ fun PinPad(
onClicked = viewModel::onPinButtonClicked,
scaling = buttonScaleAnimatables[10]::value,
isAnimationEnabled = isDigitButtonAnimationEnabled,
- onPointerDown = viewModel::onDigitButtonDown
+ onPointerDown = viewModel::onDigitButtonDown,
)
ActionButton(
@@ -152,7 +149,7 @@ fun PinPad(
onClicked = viewModel::onAuthenticateButtonClicked,
appearance = confirmButtonAppearance,
scaling = buttonScaleAnimatables[11]::value,
- elementId = "key_enter"
+ elementId = "key_enter",
)
}
}
@@ -162,7 +159,7 @@ private fun DigitButton(
digit: Int,
isInputEnabled: Boolean,
onClicked: (Int) -> Unit,
- onPointerDown: () -> Unit,
+ onPointerDown: (View?) -> Unit,
scaling: () -> Float,
isAnimationEnabled: Boolean,
) {
@@ -178,7 +175,7 @@ private fun DigitButton(
val scale = if (isAnimationEnabled) scaling() else 1f
scaleX = scale
scaleY = scale
- }
+ },
) { contentColor ->
// TODO(b/281878426): once "color: () -> Color" (added to BasicText in aosp/2568972) makes
// it into Text, use that here, to animate more efficiently.
@@ -197,6 +194,7 @@ private fun ActionButton(
onClicked: () -> Unit,
elementId: String,
onLongPressed: (() -> Unit)? = null,
+ onPointerDown: ((View?) -> Unit)? = null,
appearance: ActionButtonAppearance,
scaling: () -> Float,
) {
@@ -222,18 +220,16 @@ private fun ActionButton(
foregroundColor = foregroundColor,
isAnimationEnabled = true,
elementId = elementId,
+ onPointerDown = onPointerDown,
modifier =
Modifier.graphicsLayer {
alpha = hiddenAlpha
val scale = scaling()
scaleX = scale
scaleY = scale
- }
+ },
) { contentColor ->
- Icon(
- icon = icon,
- tint = contentColor(),
- )
+ Icon(icon = icon, tint = contentColor())
}
}
@@ -247,22 +243,13 @@ private fun PinPadButton(
modifier: Modifier = Modifier,
elementId: String? = null,
onLongPressed: (() -> Unit)? = null,
- onPointerDown: (() -> Unit)? = null,
+ onPointerDown: ((View?) -> Unit)? = null,
content: @Composable (contentColor: () -> Color) -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val indication = LocalIndication.current.takeUnless { isPressed }
-
val view = LocalView.current
- LaunchedEffect(isPressed) {
- if (isPressed) {
- view.performHapticFeedback(
- HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
- )
- }
- }
// Pin button animation specification is asymmetric: fast animation to the pressed state, and a
// slow animation upon release. Note that isPressed is guaranteed to be true for at least the
@@ -277,7 +264,7 @@ private fun PinPadButton(
animateDpAsState(
if (isAnimationEnabled && isPressed) 24.dp else pinButtonMaxSize / 2,
label = "PinButton round corners",
- animationSpec = tween(animDurationMillis, easing = animEasing)
+ animationSpec = tween(animDurationMillis, easing = animEasing),
)
val colorAnimationSpec: AnimationSpec<Color> = tween(animDurationMillis, easing = animEasing)
val containerColor: Color by
@@ -287,7 +274,7 @@ private fun PinPadButton(
else -> backgroundColor
},
label = "Pin button container color",
- animationSpec = colorAnimationSpec
+ animationSpec = colorAnimationSpec,
)
val contentColor =
animateColorAsState(
@@ -296,7 +283,7 @@ private fun PinPadButton(
else -> foregroundColor
},
label = "Pin button container color",
- animationSpec = colorAnimationSpec
+ animationSpec = colorAnimationSpec,
)
Box(
@@ -319,11 +306,11 @@ private fun PinPadButton(
interactionSource = interactionSource,
indication = indication,
onClick = onClicked,
- onLongClick = onLongPressed
+ onLongClick = onLongPressed,
)
.pointerInteropFilter { motionEvent ->
if (motionEvent.action == MotionEvent.ACTION_DOWN) {
- onPointerDown?.let { it() }
+ onPointerDown?.let { it(view) }
}
false
}
@@ -353,10 +340,7 @@ private suspend fun showFailureAnimation(
animatable.animateTo(
targetValue = 1f,
animationSpec =
- tween(
- durationMillis = pinButtonErrorRevertMs,
- easing = Easings.Legacy,
- ),
+ tween(durationMillis = pinButtonErrorRevertMs, easing = Easings.Legacy),
)
}
}
@@ -364,9 +348,7 @@ private suspend fun showFailureAnimation(
}
/** Returns the amount of horizontal spacing between columns, in dips. */
-private fun calculateHorizontalSpacingBetweenColumns(
- gridWidth: Dp,
-): Dp {
+private fun calculateHorizontalSpacingBetweenColumns(gridWidth: Dp): Dp {
return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
index 296fc27ac0ff..dcf32b2bcda4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
@@ -16,15 +16,10 @@
package com.android.systemui.common.ui.compose.windowinsets
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.displayCutout
-import androidx.compose.foundation.layout.systemBars
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -36,9 +31,6 @@ val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() }
/** The corner radius in px of the current display. */
val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp }
-/** The screen height in px without accounting for any screen insets (cutouts, status/nav bars) */
-val LocalRawScreenHeight = staticCompositionLocalOf { 0f }
-
@Composable
fun ScreenDecorProvider(
displayCutout: StateFlow<DisplayCutout>,
@@ -48,22 +40,9 @@ fun ScreenDecorProvider(
val cutout by displayCutout.collectAsStateWithLifecycle()
val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
- val density = LocalDensity.current
- val navBarHeight =
- with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
- val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
- val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding()
- val screenHeight =
- with(density) {
- (LocalConfiguration.current.screenHeightDp.dp +
- maxOf(statusBarHeight, displayCutoutHeight))
- .toPx()
- } + navBarHeight
-
CompositionLocalProvider(
LocalScreenCornerRadius provides screenCornerRadiusDp,
LocalDisplayCutout provides cutout,
- LocalRawScreenHeight provides screenHeight,
) {
content()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index 897a8613263f..a2ae8bbf66e4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -24,9 +24,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.tanh
@@ -36,9 +38,10 @@ import kotlinx.coroutines.launch
@Composable
fun Modifier.stackVerticalOverscroll(
coroutineScope: CoroutineScope,
- canScrollForward: () -> Boolean
+ canScrollForward: () -> Boolean,
): Modifier {
- val screenHeight = LocalRawScreenHeight.current
+ val screenHeight =
+ with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
val overscrollOffset = remember { Animatable(0f) }
val stackNestedScrollConnection = remember {
NotificationStackNestedScrollConnection(
@@ -60,10 +63,10 @@ fun Modifier.stackVerticalOverscroll(
overscrollOffset.animateTo(
targetValue = 0f,
initialVelocity = velocityAvailable,
- animationSpec = tween()
+ animationSpec = tween(),
)
}
- }
+ },
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 91ecfc18a76e..1b99a9644575 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -19,6 +19,7 @@ package com.android.systemui.notifications.ui.composable
import android.util.Log
import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
@@ -29,6 +30,8 @@ import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.absoluteOffset
@@ -36,9 +39,11 @@ import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.imeAnimationTarget
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
@@ -68,6 +73,7 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
@@ -81,7 +87,6 @@ import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
@@ -96,6 +101,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
object Notifications {
@@ -171,7 +177,7 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace(
setCurrent = { scrollOffset = it },
min = minScrollOffset,
max = maxScrollOffset,
- delta
+ delta,
)
}
@@ -209,8 +215,8 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace(
calculateHeadsUpPlaceholderYOffset(
scrollOffset.roundToInt(),
minScrollOffset.roundToInt(),
- stackScrollView.topHeadsUpHeight
- )
+ stackScrollView.topHeadsUpHeight,
+ ),
)
}
.thenIf(isHeadsUp) {
@@ -218,11 +224,8 @@ fun SceneScope.SnoozeableHeadsUpNotificationSpace(
bottomBehavior = NestedScrollBehavior.EdgeAlways
)
.nestedScroll(nestedScrollConnection)
- .scrollable(
- orientation = Orientation.Vertical,
- state = scrollableState,
- )
- }
+ .scrollable(orientation = Orientation.Vertical, state = scrollableState)
+ },
)
}
@@ -259,6 +262,7 @@ fun SceneScope.ConstrainedNotificationStack(
* Adds the space where notification stack should appear in the scene, with a scrim and nested
* scrolling.
*/
+@OptIn(ExperimentalLayoutApi::class)
@Composable
fun SceneScope.NotificationScrollingStack(
shadeSession: SaveableSession,
@@ -291,7 +295,7 @@ fun SceneScope.NotificationScrollingStack(
val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
val bottomPadding = if (shouldReserveSpaceForNavBar) navBarHeight else 0.dp
- val screenHeight = LocalRawScreenHeight.current
+ val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
/**
* The height in px of the contents of notification stack. Depending on the number of
@@ -325,6 +329,14 @@ fun SceneScope.NotificationScrollingStack(
screenHeight - maxScrimTop() - with(density) { navBarHeight.toPx() }
}
+ val isRemoteInputActive by viewModel.isRemoteInputActive.collectAsStateWithLifecycle(false)
+
+ // The bottom Y bound of the currently focused remote input notification.
+ val remoteInputRowBottom by viewModel.remoteInputRowBottomBound.collectAsStateWithLifecycle(0f)
+
+ // The top y bound of the IME.
+ val imeTop = remember { mutableFloatStateOf(0f) }
+
// we are not scrolled to the top unless the scrim is at its maximum offset.
LaunchedEffect(viewModel, scrimOffset) {
snapshotFlow { scrimOffset.value >= 0f }
@@ -342,15 +354,34 @@ fun SceneScope.NotificationScrollingStack(
LaunchedEffect(syntheticScroll, scrimOffset, scrollState) {
snapshotFlow { syntheticScroll.value }
.collect { delta ->
- val minOffset = minScrimOffset()
- if (scrimOffset.value > minOffset) {
- val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f)
- scrimOffset.snapTo((scrimOffset.value - delta).coerceAtLeast(minOffset))
- if (remainingDelta > 0f) {
- scrollState.scrollBy(remainingDelta)
- }
- } else {
- scrollState.scrollTo(delta.roundToInt())
+ scrollNotificationStack(
+ scope = coroutineScope,
+ delta = delta,
+ animate = false,
+ scrimOffset = scrimOffset,
+ minScrimOffset = minScrimOffset,
+ scrollState = scrollState,
+ )
+ }
+ }
+
+ // if remote input state changes, compare the row and IME's overlap and offset the scrim and
+ // placeholder accordingly.
+ LaunchedEffect(isRemoteInputActive, remoteInputRowBottom, imeTop) {
+ imeTop.floatValue = 0f
+ snapshotFlow { imeTop.floatValue }
+ .collect { imeTopValue ->
+ // only scroll the stack if ime value has been populated (ime placeholder has been
+ // composed at least once), and our remote input row overlaps with the ime bounds.
+ if (isRemoteInputActive && imeTopValue > 0f && remoteInputRowBottom > imeTopValue) {
+ scrollNotificationStack(
+ scope = coroutineScope,
+ delta = remoteInputRowBottom - imeTopValue,
+ animate = true,
+ scrimOffset = scrimOffset,
+ minScrimOffset = minScrimOffset,
+ scrollState = scrollState,
+ )
}
}
}
@@ -394,12 +425,12 @@ fun SceneScope.NotificationScrollingStack(
scrimOffset.value < 0 &&
layoutState.isTransitioning(
from = Scenes.Shade,
- to = Scenes.QuickSettings
+ to = Scenes.QuickSettings,
)
) {
IntOffset(
x = 0,
- y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt()
+ y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt(),
)
} else {
IntOffset(x = 0, y = scrimOffset.value.roundToInt())
@@ -458,13 +489,11 @@ fun SceneScope.NotificationScrollingStack(
.thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() }
.debugBackground(viewModel, DEBUG_BOX_COLOR)
) {
- NotificationPlaceholder(
- stackScrollView = stackScrollView,
- viewModel = viewModel,
+ Column(
modifier =
Modifier.verticalNestedScrollToScene(
topBehavior = NestedScrollBehavior.EdgeWithPreview,
- isExternalOverscrollGesture = { isCurrentGestureOverscroll.value }
+ isExternalOverscrollGesture = { isCurrentGestureOverscroll.value },
)
.thenIf(shadeMode == ShadeMode.Single) {
Modifier.nestedScroll(scrimNestedScrollConnection)
@@ -473,18 +502,31 @@ fun SceneScope.NotificationScrollingStack(
.verticalScroll(scrollState)
.padding(top = topPadding)
.fillMaxWidth()
- .notificationStackHeight(
- view = stackScrollView,
- totalVerticalPadding = topPadding + bottomPadding,
- )
- .onSizeChanged { size -> stackHeight.intValue = size.height },
- )
+ ) {
+ NotificationPlaceholder(
+ stackScrollView = stackScrollView,
+ viewModel = viewModel,
+ modifier =
+ Modifier.notificationStackHeight(
+ view = stackScrollView,
+ totalVerticalPadding = topPadding + bottomPadding,
+ )
+ .onSizeChanged { size -> stackHeight.intValue = size.height },
+ )
+ Spacer(
+ modifier =
+ Modifier.windowInsetsBottomHeight(WindowInsets.imeAnimationTarget)
+ .onGloballyPositioned { coordinates: LayoutCoordinates ->
+ imeTop.floatValue = screenHeight - coordinates.size.height
+ }
+ )
+ }
}
if (shouldIncludeHeadsUpSpace) {
HeadsUpNotificationSpace(
stackScrollView = stackScrollView,
viewModel = viewModel,
- modifier = Modifier.padding(top = topPadding)
+ modifier = Modifier.padding(top = topPadding),
)
}
}
@@ -572,6 +614,42 @@ private fun SceneScope.NotificationPlaceholder(
)
}
+private suspend fun scrollNotificationStack(
+ scope: CoroutineScope,
+ delta: Float,
+ animate: Boolean,
+ scrimOffset: Animatable<Float, AnimationVector1D>,
+ minScrimOffset: () -> Float,
+ scrollState: ScrollState,
+) {
+ val minOffset = minScrimOffset()
+ if (scrimOffset.value > minOffset) {
+ val remainingDelta =
+ (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f).roundToInt()
+ if (remainingDelta > 0) {
+ if (animate) {
+ // launch a new coroutine for the remainder animation so that it doesn't suspend the
+ // scrim animation, allowing both to play simultaneously.
+ scope.launch { scrollState.animateScrollTo(remainingDelta) }
+ } else {
+ scrollState.scrollTo(remainingDelta)
+ }
+ }
+ val newScrimOffset = (scrimOffset.value - delta).coerceAtLeast(minOffset)
+ if (animate) {
+ scrimOffset.animateTo(newScrimOffset)
+ } else {
+ scrimOffset.snapTo(newScrimOffset)
+ }
+ } else {
+ if (animate) {
+ scrollState.animateScrollBy(delta)
+ } else {
+ scrollState.scrollBy(delta)
+ }
+ }
+}
+
private fun calculateCornerRadius(
scrimCornerRadius: Dp,
screenCornerRadius: Dp,
@@ -618,7 +696,7 @@ private fun consumeDeltaWithinRange(
setCurrent: (Float) -> Unit,
min: Float,
max: Float,
- delta: Float
+ delta: Float,
): Float {
return if (delta < 0 && current > min) {
val remainder = (current + delta - min).coerceAtMost(0f)
@@ -631,10 +709,7 @@ private fun consumeDeltaWithinRange(
} else 0f
}
-private inline fun debugLog(
- viewModel: NotificationsPlaceholderViewModel,
- msg: () -> Any,
-) {
+private inline fun debugLog(viewModel: NotificationsPlaceholderViewModel, msg: () -> Any) {
if (viewModel.isDebugLoggingEnabled) {
Log.d(TAG, msg().toString())
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index fa92bef34f38..0c1c16522567 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -61,6 +61,7 @@ import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
@@ -79,7 +80,6 @@ import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
-import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -229,17 +229,16 @@ private fun SceneScope.QuickSettingsScene(
}
.thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() }
) {
+ val density = LocalDensity.current
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
val isCustomizerShowing by
viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle()
val customizingAnimationDuration by
viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle()
- val screenHeight = LocalRawScreenHeight.current
+ val screenHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
BackHandler(enabled = isCustomizing) { viewModel.qsSceneAdapter.requestCloseCustomizer() }
- val collapsedHeaderHeight =
- with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
val lifecycleOwner = LocalLifecycleOwner.current
val footerActionsViewModel =
remember(lifecycleOwner, viewModel) {
@@ -268,7 +267,6 @@ private fun SceneScope.QuickSettingsScene(
val navBarBottomHeight =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
- val density = LocalDensity.current
val bottomPadding by
animateDpAsState(
targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index b85523bc1694..6c4edf49fd83 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -18,6 +18,7 @@
package com.android.systemui.shade.ui.composable
+import android.view.HapticFeedbackConstants
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@@ -39,17 +40,20 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.SceneScope
import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.systemui.scene.shared.model.Scenes
/** Renders a lightweight shade UI container, as an overlay. */
@Composable
@@ -58,6 +62,13 @@ fun SceneScope.OverlayShade(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
+ val view = LocalView.current
+ LaunchedEffect(Unit) {
+ if (layoutState.currentTransition?.fromContent == Scenes.Gone) {
+ view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START)
+ }
+ }
+
Box(modifier) {
Scrim(onClicked = onScrimClicked)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index fb9dde345251..0bb1d928c2b4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -51,6 +51,7 @@ import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastSumBy
import com.android.compose.ui.util.SpaceVectorConverter
@@ -234,8 +235,15 @@ internal class MultiPointerDraggableNode(
pointersDown == 0 -> {
startedPosition = null
- val lastPointerUp = changes.single { it.id == velocityPointerId }
- velocityTracker.addPointerInputChange(lastPointerUp)
+ // In case of multiple events with 0 pointers down (not pressed) we may have
+ // already removed the velocityPointer
+ val lastPointerUp = changes.fastFilter { it.id == velocityPointerId }
+ check(lastPointerUp.isEmpty() || lastPointerUp.size == 1) {
+ "There are ${lastPointerUp.size} pointers up: $lastPointerUp"
+ }
+ if (lastPointerUp.size == 1) {
+ velocityTracker.addPointerInputChange(lastPointerUp.first())
+ }
}
// The first pointer down, startedPosition was not set.
diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md
index 0ac15c583b29..234c7a032d2e 100644
--- a/packages/SystemUI/docs/scene.md
+++ b/packages/SystemUI/docs/scene.md
@@ -68,15 +68,13 @@ file evalutes to `true`.
1. Set a collection of **aconfig flags** to `true` by running the following
commands:
```console
- $ adb shell device_config override systemui com.android.systemui.scene_container true
- $ adb shell device_config override systemui com.android.systemui.compose_lockscreen true
$ adb shell device_config override systemui com.android.systemui.keyguard_bottom_area_refactor true
$ adb shell device_config override systemui com.android.systemui.keyguard_wm_state_refactor true
- $ adb shell device_config override systemui com.android.systemui.media_in_scene_container true
$ adb shell device_config override systemui com.android.systemui.migrate_clocks_to_blueprint true
- $ adb shell device_config override systemui com.android.systemui.notifications_heads_up_refactor true
+ $ adb shell device_config override systemui com.android.systemui.notification_avalanche_throttle_hun true
$ adb shell device_config override systemui com.android.systemui.predictive_back_sysui true
$ adb shell device_config override systemui com.android.systemui.device_entry_udfps_refactor true
+ $ adb shell device_config override systemui com.android.systemui.scene_container true
```
2. **Restart** System UI by issuing the following command:
```console
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 156e06843d15..312e62d0b624 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -196,6 +196,28 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
}
@Test
+ public void testUserSwitcherToOneHandedRemovesViews() {
+ // Can happen when a SIM is inserted into a large screen device
+ initMode(MODE_USER_SWITCHER);
+ {
+ View view1 = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ View view2 = mKeyguardSecurityContainer.findViewById(R.id.user_switcher_header);
+ assertThat(view1).isNotNull();
+ assertThat(view2).isNotNull();
+ }
+
+ initMode(MODE_ONE_HANDED);
+ {
+ View view1 = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ View view2 = mKeyguardSecurityContainer.findViewById(R.id.user_switcher_header);
+ assertThat(view1).isNull();
+ assertThat(view2).isNull();
+ }
+ }
+
+ @Test
public void updatePosition_movesKeyguard() {
setupForUpdateKeyguardPosition(/* oneHandedMode= */ true);
mKeyguardSecurityContainer.updatePositionByTouchX(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index deef65218c4b..9552564cf1a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -16,18 +16,26 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.keyguard.AuthInteractionProperties
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -39,11 +47,15 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val msdlPlayer = kosmos.fakeMSDLPlayer
+ private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer
+ private val authInteractionProperties = AuthInteractionProperties()
private val underTest =
kosmos.pinBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Pin,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
@Before
@@ -77,4 +89,42 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
underTest.onAuthenticateButtonClicked()
assertThat(animateFailure).isFalse()
}
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onAuthenticationResult_playUnlockTokenIfSuccessful() =
+ testScope.runTest {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ // Correct PIN:
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
+ underTest.onAuthenticateButtonClicked()
+ runCurrent()
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onAuthenticationResult_playFailureTokenIfFailure() =
+ testScope.runTest {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ // Wrong PIN:
+ FakeAuthenticationRepository.DEFAULT_PIN.drop(2).forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
+ underTest.onAuthenticateButtonClicked()
+ runCurrent()
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 7c773a902367..c163c6fc0a30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -16,9 +16,11 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.authenticationRepository
@@ -27,12 +29,16 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.FakeMSDLPlayer
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,10 +61,13 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val bouncerViewModel by lazy { kosmos.bouncerSceneContentViewModel }
+ private val msdlPlayer: FakeMSDLPlayer = kosmos.fakeMSDLPlayer
+ private val bouncerHapticHelper = kosmos.bouncerHapticPlayer
private val underTest =
kosmos.patternBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true).asStateFlow(),
onIntentionalUserInput = {},
+ bouncerHapticPlayer = bouncerHapticHelper,
)
private val containerSize = 90 // px
@@ -115,10 +124,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
.that(selectedDots)
.isEqualTo(
CORRECT_PATTERN.subList(0, index + 1).map {
- PatternDotViewModel(
- x = it.x,
- y = it.y,
- )
+ PatternDotViewModel(x = it.x, y = it.y)
}
)
assertWithMessage("Wrong current dot for index $index")
@@ -174,7 +180,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
listOf(
PatternDotViewModel(0, 0),
PatternDotViewModel(1, 0),
- PatternDotViewModel(2, 0)
+ PatternDotViewModel(2, 0),
)
)
}
@@ -200,7 +206,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
listOf(
PatternDotViewModel(1, 0),
PatternDotViewModel(1, 1),
- PatternDotViewModel(1, 2)
+ PatternDotViewModel(1, 2),
)
)
}
@@ -228,7 +234,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
listOf(
PatternDotViewModel(2, 0),
PatternDotViewModel(1, 1),
- PatternDotViewModel(0, 2)
+ PatternDotViewModel(0, 2),
)
)
}
@@ -300,10 +306,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT + 1
repeat(attempts) { attempt ->
underTest.onDragStart()
- CORRECT_PATTERN.subList(
- 0,
- kosmos.authenticationRepository.minPatternLength - 1,
- )
+ CORRECT_PATTERN.subList(0, kosmos.authenticationRepository.minPatternLength - 1)
.forEach { coordinate ->
underTest.onDrag(
xPx = 30f * coordinate.x + 15,
@@ -341,6 +344,16 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
assertThat(authResult).isTrue()
}
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun performDotFeedback_deliversDragToken() =
+ testScope.runTest {
+ underTest.performDotFeedback(null)
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
private fun dragOverCoordinates(vararg coordinatesDragged: Point) {
underTest.onDragStart()
coordinatesDragged.forEach(::dragToCoordinate)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 2ee4aee2abab..af5f2acb444d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -27,6 +27,7 @@ import androidx.compose.ui.input.key.KeyEventType
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -35,12 +36,15 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
import com.android.systemui.classifier.fakeFalsingCollector
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import kotlin.random.Random
import kotlin.random.nextInt
@@ -64,11 +68,14 @@ class PinBouncerViewModelTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+ private val msdlPlayer = kosmos.fakeMSDLPlayer
+ private val bouncerHapticPlayer = kosmos.bouncerHapticPlayer
private val underTest by lazy {
kosmos.pinBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Pin,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
@@ -97,6 +104,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Sim,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
assertThat(underTest.isSimAreaVisible).isTrue()
@@ -122,6 +130,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
authenticationMethod = AuthenticationMethodModel.Pin,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
@@ -487,11 +496,39 @@ class PinBouncerViewModelTest : SysuiTestCase() {
testScope.runTest {
lockDeviceAndOpenPinBouncer()
- underTest.onDigitButtonDown()
+ underTest.onDigitButtonDown(null)
assertTrue(kosmos.fakeFalsingCollector.wasLastGestureAvoided())
}
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onDigiButtonDown_deliversKeyStandardToken() =
+ testScope.runTest {
+ underTest.onDigitButtonDown(null)
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_STANDARD)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onBackspaceButtonPressed_deliversKeyDeleteToken() {
+ underTest.onBackspaceButtonPressed(null)
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_DELETE)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun onBackspaceButtonLongPressed_deliversLongPressToken() {
+ underTest.onBackspaceButtonLongPressed()
+
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.LONG_PRESS)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
private fun TestScope.switchToScene(toScene: SceneKey) {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index c2acc5ff6689..160865d625f5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -31,8 +31,11 @@ import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -211,7 +214,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
kosmos.fakeUserRepository.setSelectedUserInfo(
primaryUser,
- SelectionStatus.SELECTION_COMPLETE
+ SelectionStatus.SELECTION_COMPLETE,
)
kosmos.fakeTrustRepository.setCurrentUserTrusted(true)
@@ -240,6 +243,49 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
}
@Test
+ fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleepInAod() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = this,
+ )
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ }
+
+ @Test
+ fun deviceUnlockStatus_staysLocked_whenFingerprintUnlocked_whileDeviceAsleep() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ assertThat(kosmos.keyguardTransitionInteractor.getCurrentState())
+ .isEqualTo(KeyguardState.LOCKSCREEN)
+
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ }
+
+ @Test
fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
testScope.runTest {
kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
@@ -273,7 +319,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
DeviceEntryRestrictionReason.UserLockdown,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- DeviceEntryRestrictionReason.PolicyLockdown
+ DeviceEntryRestrictionReason.PolicyLockdown,
)
}
@@ -285,7 +331,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
+ "not mainline reboot",
)
runCurrent()
@@ -321,7 +367,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
+ "not mainline reboot",
)
runCurrent()
@@ -358,7 +404,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
+ "not mainline reboot",
)
runCurrent()
@@ -394,12 +440,12 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
collectLastValue(underTest.deviceEntryRestrictionReason)
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
- DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE
+ DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE,
)
kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
AuthenticationFlags(
userId = 1,
- flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT,
)
)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index 50b727c3fed9..9cfd328a9484 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.education.domain.interactor.mockEduInputManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -62,7 +63,13 @@ class ContextualEducationRepositoryTest : SysuiTestCase() {
// Create TestContext here because TemporaryFolder.create() is called in @Before. It is
// needed before calling TemporaryFolder.newFolder().
val testContext = TestContext(context, tmpFolder.newFolder())
- underTest = UserContextualEducationRepository(testContext, dsScopeProvider)
+ underTest =
+ UserContextualEducationRepository(
+ testContext,
+ dsScopeProvider,
+ kosmos.mockEduInputManager,
+ kosmos.testDispatcher
+ )
underTest.setUser(testUserId)
}
@@ -99,7 +106,8 @@ class ContextualEducationRepositoryTest : SysuiTestCase() {
lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(),
lastEducationTime = kosmos.fakeEduClock.instant(),
usageSessionStartTime = kosmos.fakeEduClock.instant(),
- userId = testUserId
+ userId = testUserId,
+ gestureType = BACK
)
underTest.updateGestureEduModel(BACK) { newModel }
val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 64915fbf551f..8201bbe4dc47 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -17,15 +17,13 @@
package com.android.systemui.education.domain.interactor
import android.content.pm.UserInfo
-import android.hardware.input.InputManager
-import android.hardware.input.KeyGestureEvent
-import android.view.KeyEvent
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.education.data.model.GestureEduModel
@@ -40,20 +38,21 @@ import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.kotlin.any
-import org.mockito.kotlin.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
-class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
+class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val contextualEduInteractor = kosmos.contextualEducationInteractor
@@ -71,21 +70,27 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
underTest.start()
contextualEduInteractor.start()
userRepository.setUserInfos(USER_INFOS)
+ testScope.launch {
+ contextualEduInteractor.updateKeyboardFirstConnectionTime()
+ contextualEduInteractor.updateTouchpadFirstConnectionTime()
+ }
}
@Test
fun newEducationInfoOnMaxSignalCountReached() =
testScope.runTest {
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
val model by collectLastValue(underTest.educationTriggered)
- assertThat(model?.gestureType).isEqualTo(BACK)
+
+ assertThat(model?.gestureType).isEqualTo(gestureType)
}
@Test
fun newEducationToastOn1stEducation() =
testScope.runTest {
val model by collectLastValue(underTest.educationTriggered)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
+
assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast)
}
@@ -93,12 +98,12 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
fun newEducationNotificationOn2ndEducation() =
testScope.runTest {
val model by collectLastValue(underTest.educationTriggered)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
// runCurrent() to trigger 1st education
runCurrent()
eduClock.offset(minDurationForNextEdu)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification)
}
@@ -106,7 +111,7 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
@Test
fun noEducationInfoBeforeMaxSignalCountReached() =
testScope.runTest {
- contextualEduInteractor.incrementSignalCount(BACK)
+ contextualEduInteractor.incrementSignalCount(gestureType)
val model by collectLastValue(underTest.educationTriggered)
assertThat(model).isNull()
}
@@ -115,8 +120,8 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
fun noEducationInfoWhenShortcutTriggeredPreviously() =
testScope.runTest {
val model by collectLastValue(underTest.educationTriggered)
- contextualEduInteractor.updateShortcutTriggerTime(BACK)
- triggerMaxEducationSignals(BACK)
+ contextualEduInteractor.updateShortcutTriggerTime(gestureType)
+ triggerMaxEducationSignals(gestureType)
assertThat(model).isNull()
}
@@ -124,12 +129,12 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
fun no2ndEducationBeforeMinEduIntervalReached() =
testScope.runTest {
val models by collectValues(underTest.educationTriggered)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
runCurrent()
// Offset a duration that is less than the required education interval
eduClock.offset(1.seconds)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
runCurrent()
assertThat(models.filterNotNull().size).isEqualTo(1)
@@ -140,15 +145,15 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
testScope.runTest {
val models by collectValues(underTest.educationTriggered)
// Trigger 2 educations
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
runCurrent()
eduClock.offset(minDurationForNextEdu)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
runCurrent()
// Try triggering 3rd education
eduClock.offset(minDurationForNextEdu)
- triggerMaxEducationSignals(BACK)
+ triggerMaxEducationSignals(gestureType)
assertThat(models.filterNotNull().size).isEqualTo(2)
}
@@ -157,18 +162,21 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() =
testScope.runTest {
val model by
- collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
- contextualEduInteractor.incrementSignalCount(BACK)
+ collectLastValue(
+ kosmos.contextualEducationRepository.readGestureEduModelFlow(gestureType)
+ )
+ contextualEduInteractor.incrementSignalCount(gestureType)
eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds))
val secondSignalReceivedTime = eduClock.instant()
- contextualEduInteractor.incrementSignalCount(BACK)
+ contextualEduInteractor.incrementSignalCount(gestureType)
assertThat(model)
.isEqualTo(
GestureEduModel(
signalCount = 1,
usageSessionStartTime = secondSignalReceivedTime,
- userId = 0
+ userId = 0,
+ gestureType = gestureType
)
)
}
@@ -252,22 +260,9 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
@Test
fun updateShortcutTimeOnKeyboardShortcutTriggered() =
testScope.runTest {
- // runCurrent() to trigger inputManager#registerKeyGestureEventListener in the
- // interactor
- runCurrent()
- val listenerCaptor =
- ArgumentCaptor.forClass(InputManager.KeyGestureEventListener::class.java)
- verify(kosmos.mockEduInputManager)
- .registerKeyGestureEventListener(any(), listenerCaptor.capture())
-
- val allAppsKeyGestureEvent =
- KeyGestureEvent.Builder()
- .setDeviceId(1)
- .setModifierState(KeyEvent.META_META_ON)
- .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
- .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- .build()
- listenerCaptor.value.onKeyGestureEvent(allAppsKeyGestureEvent)
+ // Only All Apps needs to update the keyboard shortcut
+ assumeTrue(gestureType == ALL_APPS)
+ kosmos.contextualEducationRepository.setKeyboardShortcutTriggered(ALL_APPS)
val model by
collectLastValue(
@@ -293,10 +288,18 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
runCurrent()
}
+ private suspend fun setUpForDeviceConnection() {
+ contextualEduInteractor.updateKeyboardFirstConnectionTime()
+ contextualEduInteractor.updateTouchpadFirstConnectionTime()
+ }
+
companion object {
- private val USER_INFOS =
- listOf(
- UserInfo(101, "Second User", 0),
- )
+ private val USER_INFOS = listOf(UserInfo(101, "Second User", 0))
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getGestureTypes(): List<GestureType> {
+ return listOf(BACK, HOME, OVERVIEW, ALL_APPS)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index c4ac585f7e4a..ab33269ec954 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -37,6 +37,7 @@ import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -69,6 +70,11 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
@Before
fun setUp() {
+ testScope.launch {
+ interactor.updateKeyboardFirstConnectionTime()
+ interactor.updateTouchpadFirstConnectionTime()
+ }
+
val viewModel =
ContextualEduViewModel(
kosmos.applicationContext.resources,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 686b518b56e0..366b55db4f20 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.fakeVibratorHelper
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.core.FakeLogBuffer
@@ -68,11 +69,13 @@ class QSLongPressEffectTest : SysuiTestCase() {
vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_SPIN] = spinDuration
whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(true)
+ kosmos.falsingManager.setFalseLongTap(false)
longPressEffect =
QSLongPressEffect(
vibratorHelper,
kosmos.keyguardStateController,
+ kosmos.falsingManager,
FakeLogBuffer.Factory.create(),
)
longPressEffect.callback = callback
@@ -180,11 +183,7 @@ class QSLongPressEffectTest : SysuiTestCase() {
// THEN the expected texture is played
val reverseHaptics =
- LongPressHapticBuilder.createReversedEffect(
- progress,
- lowTickDuration,
- effectDuration,
- )
+ LongPressHapticBuilder.createReversedEffect(progress, lowTickDuration, effectDuration)
assertThat(reverseHaptics).isNotNull()
assertThat(vibratorHelper.hasVibratedWithEffects(reverseHaptics!!)).isTrue()
}
@@ -224,6 +223,20 @@ class QSLongPressEffectTest : SysuiTestCase() {
}
@Test
+ fun onAnimationComplete_isFalseLongClick_effectEndsInIdleWithReset() =
+ testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
+ // GIVEN that the long-click is false
+ kosmos.falsingManager.setFalseLongTap(true)
+
+ // GIVEN that the animation completes
+ longPressEffect.handleAnimationComplete()
+
+ // THEN the long-press effect ends in the idle state and the properties are reset
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ verify(callback, times(1)).onResetProperties()
+ }
+
+ @Test
fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversingAndClick() =
testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) {
// GIVEN that the animation completes
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
index 0c716137f434..639737b37efd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
@@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
@@ -53,6 +54,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -81,6 +83,7 @@ class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() {
Optional.of(kosmos.touchpadGesturesInteractor),
KeyboardTouchpadConnectionInteractor(keyboardRepo, touchpadRepo),
hasTouchpadTutorialScreens,
+ mock<InputDeviceTutorialLogger>(),
SavedStateHandle(mapOf(INTENT_TUTORIAL_TYPE_KEY to startingPeripheral))
)
lifecycle.addObserver(viewModel)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index 6c3c7ef0162d..fcf4662be145 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -16,7 +16,10 @@
*/
package com.android.systemui.keyguard.data.quickaffordance
+import android.app.Flags
import android.net.Uri
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_OFF
@@ -25,6 +28,7 @@ import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.EnableZenModeDialog
+import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
@@ -35,7 +39,11 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
@@ -43,6 +51,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
+import java.time.Duration
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -66,8 +75,13 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testDispatcher = kosmos.testDispatcher
private val testScope = kosmos.testScope
+
private val settings = kosmos.fakeSettings
+ private val zenModeRepository = kosmos.fakeZenModeRepository
+ private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
+ private val secureSettingsRepository = kosmos.secureSettingsRepository
+
@Mock private lateinit var zenModeController: ZenModeController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var conditionUri: Uri
@@ -85,17 +99,36 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
DoNotDisturbQuickAffordanceConfig(
context,
zenModeController,
+ kosmos.zenModeInteractor,
settings,
userTracker,
testDispatcher,
+ testScope.backgroundScope,
conditionUri,
enableZenModeDialog,
)
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
fun dndNotAvailable_pickerStateHidden() =
testScope.runTest {
+ deviceProvisioningRepository.setDeviceProvisioned(false)
+ runCurrent()
+
+ val result = underTest.getPickerScreenState()
+ runCurrent()
+
+ assertEquals(
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice,
+ result,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun controllerDndNotAvailable_pickerStateHidden() =
+ testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(false)
@@ -105,13 +138,33 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
// then
assertEquals(
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice,
- result
+ result,
)
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
fun dndAvailable_pickerStateVisible() =
testScope.runTest {
+ deviceProvisioningRepository.setDeviceProvisioned(true)
+ runCurrent()
+
+ val result = underTest.getPickerScreenState()
+ runCurrent()
+
+ assertThat(result)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ val defaultPickerState =
+ result as KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ assertThat(defaultPickerState.configureIntent).isNotNull()
+ assertThat(defaultPickerState.configureIntent?.action)
+ .isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun controllerDndAvailable_pickerStateVisible() =
+ testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -129,7 +182,27 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun onTriggered_dndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsNotOff_setToOff() =
+ testScope.runTest {
+ val currentModes by collectLastValue(zenModeRepository.modes)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -2)
+ collectLastValue(underTest.lockScreenState)
+ runCurrent()
+
+ val result = underTest.onTriggered(null)
+ runCurrent()
+
+ val dndMode = currentModes!!.single()
+ assertThat(dndMode.isActive).isFalse()
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -140,11 +213,12 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
// when
val result = underTest.onTriggered(null)
+
verify(zenModeController)
.setZen(
spyZenMode.capture(),
spyConditionId.capture(),
- eq(DoNotDisturbQuickAffordanceConfig.TAG)
+ eq(DoNotDisturbQuickAffordanceConfig.TAG),
)
// then
@@ -154,7 +228,28 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun onTriggered_dndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsOff_settingFOREVER_setZenWithoutCondition() =
+ testScope.runTest {
+ val currentModes by collectLastValue(zenModeRepository.modes)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_FOREVER)
+ collectLastValue(underTest.lockScreenState)
+ runCurrent()
+
+ val result = underTest.onTriggered(null)
+ runCurrent()
+
+ val dndMode = currentModes!!.single()
+ assertThat(dndMode.isActive).isTrue()
+ assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)).isNull()
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -169,7 +264,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
.setZen(
spyZenMode.capture(),
spyConditionId.capture(),
- eq(DoNotDisturbQuickAffordanceConfig.TAG)
+ eq(DoNotDisturbQuickAffordanceConfig.TAG),
)
// then
@@ -179,7 +274,27 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun onTriggered_dndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsOff_settingNotFOREVERorPROMPT_dndWithDuration() =
+ testScope.runTest {
+ val currentModes by collectLastValue(zenModeRepository.modes)
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -900)
+ runCurrent()
+
+ val result = underTest.onTriggered(null)
+ runCurrent()
+
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ val dndMode = currentModes!!.single()
+ assertThat(dndMode.isActive).isTrue()
+ assertThat(zenModeRepository.getModeActiveDuration(dndMode.id))
+ .isEqualTo(Duration.ofMinutes(-900))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -194,7 +309,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
.setZen(
spyZenMode.capture(),
spyConditionId.capture(),
- eq(DoNotDisturbQuickAffordanceConfig.TAG)
+ eq(DoNotDisturbQuickAffordanceConfig.TAG),
)
// then
@@ -204,7 +319,28 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun onTriggered_dndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_dndModeIsOff_settingIsPROMPT_showDialog() =
+ testScope.runTest {
+ val expandable: Expandable = mock()
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
+ whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
+ collectLastValue(underTest.lockScreenState)
+ runCurrent()
+
+ val result = underTest.onTriggered(expandable)
+
+ assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog)
+ assertEquals(
+ expandable,
+ (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() =
testScope.runTest {
// given
val expandable: Expandable = mock()
@@ -222,13 +358,31 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog)
assertEquals(
expandable,
- (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable
+ (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable,
)
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
fun lockScreenState_dndAvailableStartsAsTrue_changeToFalse_StateIsHidden() =
testScope.runTest {
+ deviceProvisioningRepository.setDeviceProvisioned(true)
+ val valueSnapshot = collectLastValue(underTest.lockScreenState)
+ val secondLastValue = valueSnapshot()
+ runCurrent()
+
+ deviceProvisioningRepository.setDeviceProvisioned(false)
+ runCurrent()
+ val lastValue = valueSnapshot()
+
+ assertTrue(secondLastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun lockScreenState_controllerDndAvailableStartsAsTrue_changeToFalse_StateIsHidden() =
+ testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
val callbackCaptor: ArgumentCaptor<ZenModeController.Callback> = argumentCaptor()
@@ -246,7 +400,44 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun lockScreenState_dndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() =
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun lockScreenState_dndModeStartsAsOff_changeToOn_StateVisible() =
+ testScope.runTest {
+ val lockScreenState by collectLastValue(underTest.lockScreenState)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ runCurrent()
+
+ assertThat(lockScreenState)
+ .isEqualTo(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_off,
+ ContentDescription.Resource(R.string.dnd_is_off),
+ ),
+ ActivationState.Inactive,
+ )
+ )
+
+ zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ runCurrent()
+
+ assertThat(lockScreenState)
+ .isEqualTo(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_on,
+ ContentDescription.Resource(R.string.dnd_is_on),
+ ),
+ ActivationState.Active,
+ )
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ fun lockScreenState_controllerDndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() =
testScope.runTest {
// given
whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -265,9 +456,9 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
R.drawable.qs_dnd_icon_off,
- ContentDescription.Resource(R.string.dnd_is_off)
+ ContentDescription.Resource(R.string.dnd_is_off),
),
- ActivationState.Inactive
+ ActivationState.Inactive,
),
secondLastValue,
)
@@ -275,9 +466,9 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
R.drawable.qs_dnd_icon_on,
- ContentDescription.Resource(R.string.dnd_is_on)
+ ContentDescription.Resource(R.string.dnd_is_on),
),
- ActivationState.Active
+ ActivationState.Active,
),
lastValue,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index 59f16d70fab5..84b7f5c28265 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -17,17 +17,16 @@
package com.android.systemui.keyguard.domain.interactor
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
@@ -59,8 +58,8 @@ import org.mockito.MockitoAnnotations
class KeyguardBlueprintInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val underTest = kosmos.keyguardBlueprintInteractor
- private val keyguardBlueprintRepository = kosmos.keyguardBlueprintRepository
+ private val underTest by lazy { kosmos.keyguardBlueprintInteractor }
+ private val keyguardBlueprintRepository by lazy { kosmos.keyguardBlueprintRepository }
private val clockRepository by lazy { kosmos.fakeKeyguardClockRepository }
private val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
private val fingerprintPropertyRepository by lazy { kosmos.fakeFingerprintPropertyRepository }
@@ -75,7 +74,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() {
sensorId = 1,
strength = SensorStrength.STRONG,
sensorType = FingerprintSensorType.POWER_BUTTON,
- sensorLocations = mapOf()
+ sensorLocations = mapOf(),
)
}
@@ -93,7 +92,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
fun testAppliesSplitShadeBlueprint() {
testScope.runTest {
val blueprintId by collectLastValue(underTest.blueprintId)
@@ -107,7 +106,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ @EnableSceneContainer
fun testDoesNotApplySplitShadeBlueprint() {
testScope.runTest {
val blueprintId by collectLastValue(underTest.blueprintId)
@@ -122,7 +121,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
fun fingerprintPropertyInitialized_updatesBlueprint() {
testScope.runTest {
underTest.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 41c5b7332a4f..ff6ea3a14ff2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -25,6 +25,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
@@ -44,6 +46,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
@@ -71,10 +74,8 @@ class AodBurnInViewModelTest : SysuiTestCase() {
private val burnInFlow = MutableStateFlow(BurnInModel())
@Before
- @DisableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
- )
+ @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @DisableSceneContainer
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
@@ -112,18 +113,13 @@ class AodBurnInViewModelTest : SysuiTestCase() {
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = 30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(0)
assertThat(movement?.translationY).isEqualTo(0)
@@ -143,17 +139,12 @@ class AodBurnInViewModelTest : SysuiTestCase() {
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = 30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(20)
assertThat(movement?.translationY).isEqualTo(30)
@@ -166,7 +157,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 0f,
- transitionState = TransitionState.STARTED
+ transitionState = TransitionState.STARTED,
),
validateStep = false,
)
@@ -180,11 +171,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
@DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() =
testScope.runTest {
- burnInParameters =
- burnInParameters.copy(
- minViewY = 100,
- topInset = 80,
- )
+ burnInParameters = burnInParameters.copy(minViewY = 100, topInset = 80)
val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
@@ -193,18 +180,13 @@ class AodBurnInViewModelTest : SysuiTestCase() {
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = -30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = -30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(20)
// -20 instead of -30, due to inset of 80
assertThat(movement?.translationY).isEqualTo(-20)
@@ -217,7 +199,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 0f,
- transitionState = TransitionState.STARTED
+ transitionState = TransitionState.STARTED,
),
validateStep = false,
)
@@ -231,11 +213,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
@EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() =
testScope.runTest {
- burnInParameters =
- burnInParameters.copy(
- minViewY = 100,
- topInset = 80,
- )
+ burnInParameters = burnInParameters.copy(minViewY = 100, topInset = 80)
val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
@@ -244,18 +222,13 @@ class AodBurnInViewModelTest : SysuiTestCase() {
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = -30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = -30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(20)
// -20 instead of -30, due to inset of 80
assertThat(movement?.translationY).isEqualTo(-20)
@@ -268,7 +241,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 0f,
- transitionState = TransitionState.STARTED
+ transitionState = TransitionState.STARTED,
),
validateStep = false,
)
@@ -291,18 +264,13 @@ class AodBurnInViewModelTest : SysuiTestCase() {
from = KeyguardState.GONE,
to = KeyguardState.AOD,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = 30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(20)
assertThat(movement?.translationY).isEqualTo(30)
@@ -311,9 +279,9 @@ class AodBurnInViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
@EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun translationAndScale_composeFlagOff_weatherLargeClock() =
+ fun translationAndScale_sceneContainerOff_weatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
isWeatherClock = true,
@@ -321,9 +289,9 @@ class AodBurnInViewModelTest : SysuiTestCase() {
)
@Test
- @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
@EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun translationAndScale_composeFlagOff_weatherSmallClock() =
+ fun translationAndScale_sceneContainerOff_weatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
isWeatherClock = true,
@@ -331,9 +299,9 @@ class AodBurnInViewModelTest : SysuiTestCase() {
)
@Test
- @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
@EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun translationAndScale_composeFlagOff_nonWeatherLargeClock() =
+ fun translationAndScale_sceneContainerOff_nonWeatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
isWeatherClock = false,
@@ -341,9 +309,9 @@ class AodBurnInViewModelTest : SysuiTestCase() {
)
@Test
- @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+ @DisableSceneContainer
@EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun translationAndScale_composeFlagOff_nonWeatherSmallClock() =
+ fun translationAndScale_sceneContainerOff_nonWeatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
isWeatherClock = false,
@@ -351,11 +319,9 @@ class AodBurnInViewModelTest : SysuiTestCase() {
)
@Test
- @EnableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
- )
- fun translationAndScale_composeFlagOn_weatherLargeClock() =
+ @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @EnableSceneContainer
+ fun translationAndScale_sceneContainerOn_weatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
isWeatherClock = true,
@@ -363,11 +329,9 @@ class AodBurnInViewModelTest : SysuiTestCase() {
)
@Test
- @EnableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
- )
- fun translationAndScale_composeFlagOn_weatherSmallClock() =
+ @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @EnableSceneContainer
+ fun translationAndScale_sceneContainerOn_weatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
isWeatherClock = true,
@@ -375,11 +339,9 @@ class AodBurnInViewModelTest : SysuiTestCase() {
)
@Test
- @EnableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
- )
- fun translationAndScale_composeFlagOn_nonWeatherLargeClock() =
+ @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @EnableSceneContainer
+ fun translationAndScale_sceneContainerOn_nonWeatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
isWeatherClock = false,
@@ -387,11 +349,10 @@ class AodBurnInViewModelTest : SysuiTestCase() {
)
@Test
- @EnableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- AConfigFlags.FLAG_COMPOSE_LOCKSCREEN
- )
- fun translationAndScale_composeFlagOn_nonWeatherSmallClock() =
+ @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @EnableSceneContainer
+ @Ignore("b/367659687")
+ fun translationAndScale_sceneContainerOn_nonWeatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
isWeatherClock = false,
@@ -421,18 +382,13 @@ class AodBurnInViewModelTest : SysuiTestCase() {
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
value = 1f,
- transitionState = TransitionState.FINISHED
+ transitionState = TransitionState.FINISHED,
),
validateStep = false,
)
// Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = 30,
- scale = 0.5f,
- )
+ burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f)
assertThat(movement?.translationX).isEqualTo(20)
assertThat(movement?.translationY).isEqualTo(30)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 17e1b53a3ba9..05a6b8785daf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -16,14 +16,13 @@
package com.android.systemui.keyguard.ui.viewmodel
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.BrokenWithSceneContainer
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
@@ -229,8 +228,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
}
@Test
- @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
- fun testSmallClockTop_splitShade_composeLockscreenOn() =
+ @EnableSceneContainer
+ fun testSmallClockTop_splitShade_sceneContainerOn() =
testScope.runTest {
with(kosmos) {
shadeRepository.setShadeLayoutWide(true)
@@ -244,8 +243,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
- fun testSmallClockTop_splitShade_composeLockscreenOff() =
+ @DisableSceneContainer
+ fun testSmallClockTop_splitShade_sceneContainerOff() =
testScope.runTest {
with(kosmos) {
shadeRepository.setShadeLayoutWide(true)
@@ -257,8 +256,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
}
@Test
- @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
- fun testSmallClockTop_nonSplitShade_composeLockscreenOn() =
+ @EnableSceneContainer
+ fun testSmallClockTop_nonSplitShade_sceneContainerOn() =
testScope.runTest {
with(kosmos) {
shadeRepository.setShadeLayoutWide(false)
@@ -270,8 +269,8 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
- fun testSmallClockTop_nonSplitShade_composeLockscreenOff() =
+ @DisableSceneContainer
+ fun testSmallClockTop_nonSplitShade_sceneContainerOff() =
testScope.runTest {
with(kosmos) {
shadeRepository.setShadeLayoutWide(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
deleted file mode 100644
index 42db96e917ee..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2024 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.qs.panels.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
-import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
-import com.android.systemui.qs.panels.shared.model.GridLayoutType
-import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
-import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
-import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class GridConsistencyInteractorTest : SysuiTestCase() {
-
- data object NoopGridLayoutType : GridLayoutType
-
- private val kosmos =
- testKosmos().apply {
- defaultLargeTilesRepository =
- object : DefaultLargeTilesRepository {
- override val defaultLargeTiles =
- setOf(
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
- }
- gridConsistencyInteractorsMap =
- mapOf(
- Pair(NoopGridLayoutType, noopGridConsistencyInteractor),
- Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)
- )
- }
-
- private val underTest = with(kosmos) { gridConsistencyInteractor }
-
- @Before
- fun setUp() {
- // Mostly testing InfiniteGridConsistencyInteractor because it reorders tiles
- with(kosmos) { gridLayoutTypeRepository.setLayout(InfiniteGridLayoutType) }
- underTest.start()
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @Test
- fun changeLayoutType_usesCorrectGridConsistencyInteractor() =
- with(kosmos) {
- testScope.runTest {
- // Using the no-op grid consistency interactor
- gridLayoutTypeRepository.setLayout(NoopGridLayoutType)
-
- // Setting an invalid layout with holes
- // [ Large A ] [ sa ]
- // [ Large B ] [ Large C ]
- // [ sb ] [ Large D ]
- val newTiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("smallA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("smallB"),
- TileSpec.create("largeD"),
- )
- tileSpecRepository.setTiles(0, newTiles)
-
- runCurrent()
-
- val tiles = currentTilesInteractor.currentTiles.value
- val tileSpecs = tiles.map { it.spec }
-
- // Saved tiles should be unchanged
- assertThat(tileSpecs).isEqualTo(newTiles)
- }
- }
-
- @Test
- fun validTilesWithInfiniteGridConsistencyInteractor_unchangedList() =
- with(kosmos) {
- testScope.runTest {
- // Setting a valid layout with holes
- // [ Large A ] [ sa ][ sb ]
- // [ Large B ] [ Large C ]
- // [ Large D ]
- val newTiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("smallA"),
- TileSpec.create("smallB"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
- tileSpecRepository.setTiles(0, newTiles)
-
- runCurrent()
-
- val tiles = currentTilesInteractor.currentTiles.value
- val tileSpecs = tiles.map { it.spec }
-
- // Saved tiles should be unchanged
- assertThat(tileSpecs).isEqualTo(newTiles)
- }
- }
-
- @Test
- fun invalidTilesWithInfiniteGridConsistencyInteractor_savesNewList() =
- with(kosmos) {
- testScope.runTest {
- // Setting an invalid layout with holes
- // [ sa ] [ Large A ]
- // [ Large B ] [ sb ] [ sc ]
- // [ sd ] [ se ] [ Large C ]
- val newTiles =
- listOf(
- TileSpec.create("smallA"),
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("smallB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- TileSpec.create("largeC"),
- )
- tileSpecRepository.setTiles(0, newTiles)
-
- runCurrent()
-
- val tiles = currentTilesInteractor.currentTiles.value
- val tileSpecs = tiles.map { it.spec }
-
- // Expected grid
- // [ sa ] [ Large A ] [ sb ]
- // [ Large B ] [ sc ] [ sd ]
- // [ se ] [ Large C ]
- val expectedTiles =
- listOf(
- TileSpec.create("smallA"),
- TileSpec.create("largeA"),
- TileSpec.create("smallB"),
- TileSpec.create("largeB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- TileSpec.create("largeC"),
- )
-
- // Saved tiles should be unchanged
- assertThat(tileSpecs).isEqualTo(expectedTiles)
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
deleted file mode 100644
index ea51398e6256..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2024 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.qs.panels.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
-import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class InfiniteGridConsistencyInteractorTest : SysuiTestCase() {
-
- private val kosmos =
- testKosmos().apply {
- defaultLargeTilesRepository =
- object : DefaultLargeTilesRepository {
- override val defaultLargeTiles: Set<TileSpec> =
- setOf(
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
- }
- }
- private val underTest = with(kosmos) { infiniteGridConsistencyInteractor }
-
- @Test
- fun validTiles_returnsUnchangedList() =
- with(kosmos) {
- testScope.runTest {
- // Original grid
- // [ Large A ] [ sa ][ sb ]
- // [ Large B ] [ Large C ]
- // [ Large D ]
- val tiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("smallA"),
- TileSpec.create("smallB"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
-
- val newTiles = underTest.reconcileTiles(tiles)
-
- assertThat(newTiles).isEqualTo(tiles)
- }
- }
-
- @Test
- fun invalidTiles_moveIconTileForward() =
- with(kosmos) {
- testScope.runTest {
- // Original grid
- // [ Large A ] [ sa ]
- // [ Large B ] [ Large C ]
- // [ sb ] [ Large D ]
- val tiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("smallA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("smallB"),
- TileSpec.create("largeD"),
- )
- // Expected grid
- // [ Large A ] [ sa ][ sb ]
- // [ Large B ] [ Large C ]
- // [ Large D ]
- val expectedTiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("smallA"),
- TileSpec.create("smallB"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
-
- val newTiles = underTest.reconcileTiles(tiles)
-
- assertThat(newTiles).isEqualTo(expectedTiles)
- }
- }
-
- @Test
- fun invalidTiles_moveIconTileBack() =
- with(kosmos) {
- testScope.runTest {
- // Original grid
- // [ sa ] [ Large A ]
- // [ Large B ] [ Large C ]
- // [ Large D ]
- val tiles =
- listOf(
- TileSpec.create("smallA"),
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- )
- // Expected grid
- // [ Large A ] [ Large B ]
- // [ Large C ] [ Large D ]
- // [ sa ]
- val expectedTiles =
- listOf(
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("largeC"),
- TileSpec.create("largeD"),
- TileSpec.create("smallA"),
- )
-
- val newTiles = underTest.reconcileTiles(tiles)
-
- assertThat(newTiles).isEqualTo(expectedTiles)
- }
- }
-
- @Test
- fun invalidTiles_multipleCorrections() =
- with(kosmos) {
- testScope.runTest {
- // Original grid
- // [ sa ] [ Large A ]
- // [ Large B ] [ sb ] [ sc ]
- // [ sd ] [ se ] [ Large C ]
- val tiles =
- listOf(
- TileSpec.create("smallA"),
- TileSpec.create("largeA"),
- TileSpec.create("largeB"),
- TileSpec.create("smallB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- TileSpec.create("largeC"),
- )
- // Expected grid
- // [ sa ] [ Large A ] [ sb ]
- // [ Large B ] [ sc ] [ sd ]
- // [ se ] [ Large C ]
- val expectedTiles =
- listOf(
- TileSpec.create("smallA"),
- TileSpec.create("largeA"),
- TileSpec.create("smallB"),
- TileSpec.create("largeB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- TileSpec.create("largeC"),
- )
-
- val newTiles = underTest.reconcileTiles(tiles)
-
- assertThat(newTiles).isEqualTo(expectedTiles)
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
index 53384afb66be..9e90090549dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
@@ -22,6 +22,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
@@ -44,12 +45,7 @@ class InfiniteGridLayoutTest : SysuiTestCase() {
}
private val underTest =
- with(kosmos) {
- InfiniteGridLayout(
- iconTilesViewModel,
- fixedColumnsSizeViewModel,
- )
- }
+ with(kosmos) { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) }
@Test
fun correctPagination_underOnePage_sameOrder() =
@@ -65,7 +61,7 @@ class InfiniteGridLayoutTest : SysuiTestCase() {
smallTile(),
largeTile(),
largeTile(),
- smallTile()
+ smallTile(),
)
val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 763a1a943bf8..385089122fc4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -27,6 +27,7 @@ import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.uiEventLoggerFake
import com.android.internal.policy.IKeyguardDismissCallback
@@ -88,9 +89,11 @@ import com.android.systemui.scene.data.model.asIterable
import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
@@ -161,6 +164,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(DualShade.FLAG_NAME)
fun hydrateVisibility() =
testScope.runTest {
val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -221,6 +225,87 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun hydrateVisibility_dualShade() =
+ testScope.runTest {
+ val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene)
+ val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
+ val transitionStateFlow =
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = true,
+ initialSceneKey = Scenes.Gone,
+ )
+ assertThat(currentDesiredSceneKey).isEqualTo(Scenes.Gone)
+ assertThat(currentDesiredOverlays).isEmpty()
+ assertThat(isVisible).isTrue()
+
+ underTest.start()
+ assertThat(isVisible).isFalse()
+
+ // Expand the notifications shade.
+ fakeSceneDataSource.pause()
+ sceneInteractor.showOverlay(Overlays.NotificationsShade, "reason")
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = Overlays.NotificationsShade,
+ fromContent = Scenes.Gone,
+ toContent = Overlays.NotificationsShade,
+ currentScene = Scenes.Gone,
+ currentOverlays = flowOf(emptySet()),
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ assertThat(isVisible).isTrue()
+ fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
+ transitionStateFlow.value =
+ ObservableTransitionState.Idle(
+ currentScene = Scenes.Gone,
+ currentOverlays = setOf(Overlays.NotificationsShade),
+ )
+ assertThat(isVisible).isTrue()
+
+ // Collapse the notifications shade.
+ fakeSceneDataSource.pause()
+ sceneInteractor.hideOverlay(Overlays.NotificationsShade, "reason")
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = Overlays.NotificationsShade,
+ fromContent = Overlays.NotificationsShade,
+ toContent = Scenes.Gone,
+ currentScene = Scenes.Gone,
+ currentOverlays = flowOf(setOf(Overlays.NotificationsShade)),
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ assertThat(isVisible).isTrue()
+ fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
+ transitionStateFlow.value =
+ ObservableTransitionState.Idle(
+ currentScene = Scenes.Gone,
+ currentOverlays = emptySet(),
+ )
+ assertThat(isVisible).isFalse()
+
+ kosmos.headsUpNotificationRepository.setNotifications(
+ buildNotificationRows(isPinned = true)
+ )
+ assertThat(isVisible).isTrue()
+
+ kosmos.headsUpNotificationRepository.setNotifications(
+ buildNotificationRows(isPinned = false)
+ )
+ assertThat(isVisible).isFalse()
+ }
+
+ @Test
fun hydrateVisibility_basedOnDeviceProvisioning() =
testScope.runTest {
val isVisible by collectLastValue(sceneInteractor.isVisible)
@@ -1621,6 +1706,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(DualShade.FLAG_NAME)
fun hydrateInteractionState_whileLocked() =
testScope.runTest {
val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen)
@@ -1707,6 +1793,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(DualShade.FLAG_NAME)
fun hydrateInteractionState_whileUnlocked() =
testScope.runTest {
val transitionStateFlow =
@@ -1795,6 +1882,186 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun hydrateInteractionState_dualShade_whileLocked() =
+ testScope.runTest {
+ val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen)
+ underTest.start()
+ runCurrent()
+ verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+ assertThat(currentDesiredOverlays).isEmpty()
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Bouncer,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateOverlayTransition(
+ transitionStateFlow = transitionStateFlow,
+ toOverlay = Overlays.NotificationsShade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateOverlayTransition(
+ transitionStateFlow = transitionStateFlow,
+ toOverlay = Overlays.QuickSettingsShade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun hydrateInteractionState_dualShade_whileUnlocked() =
+ testScope.runTest {
+ val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val transitionStateFlow =
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = true,
+ initialSceneKey = Scenes.Gone,
+ )
+ underTest.start()
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ assertThat(currentDesiredOverlays).isEmpty()
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Bouncer,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Shade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = Scenes.QuickSettings,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+ }
+
+ @Test
fun respondToFalsingDetections() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene)
@@ -2131,19 +2398,40 @@ class SceneContainerStartableTest : SysuiTestCase() {
verifyAfterTransition: (() -> Unit)? = null,
) {
val fromScene = sceneInteractor.currentScene.value
+ val fromOverlays = sceneInteractor.currentOverlays.value
sceneInteractor.changeScene(toScene, "reason")
runCurrent()
verifyBeforeTransition?.invoke()
transitionStateFlow.value =
- ObservableTransitionState.Transition(
- fromScene = fromScene,
- toScene = toScene,
- currentScene = flowOf(fromScene),
- progress = flowOf(0.5f),
- isInitiatedByUserInput = true,
- isUserInputOngoing = flowOf(true),
- )
+ if (fromOverlays.isEmpty()) {
+ // Regular scene-to-scene transition.
+ ObservableTransitionState.Transition.ChangeScene(
+ fromScene = fromScene,
+ toScene = toScene,
+ currentScene = flowOf(fromScene),
+ currentOverlays = fromOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ } else {
+ // An overlay is present; hide it.
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = fromOverlays.first(),
+ fromContent = fromOverlays.first(),
+ toContent = toScene,
+ currentScene = fromScene,
+ currentOverlays = sceneInteractor.currentOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ }
runCurrent()
verifyDuringTransition?.invoke()
@@ -2152,6 +2440,60 @@ class SceneContainerStartableTest : SysuiTestCase() {
verifyAfterTransition?.invoke()
}
+ private fun TestScope.emulateOverlayTransition(
+ transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
+ toOverlay: OverlayKey,
+ verifyBeforeTransition: (() -> Unit)? = null,
+ verifyDuringTransition: (() -> Unit)? = null,
+ verifyAfterTransition: (() -> Unit)? = null,
+ ) {
+ val fromScene = sceneInteractor.currentScene.value
+ val fromOverlays = sceneInteractor.currentOverlays.value
+ sceneInteractor.showOverlay(toOverlay, "reason")
+ runCurrent()
+ verifyBeforeTransition?.invoke()
+
+ transitionStateFlow.value =
+ if (fromOverlays.isEmpty()) {
+ // Show a new overlay.
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = toOverlay,
+ fromContent = fromScene,
+ toContent = toOverlay,
+ currentScene = fromScene,
+ currentOverlays = sceneInteractor.currentOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ } else {
+ // Overlay-to-overlay transition.
+ ObservableTransitionState.Transition.ReplaceOverlay(
+ fromOverlay = fromOverlays.first(),
+ toOverlay = toOverlay,
+ currentScene = fromScene,
+ currentOverlays = sceneInteractor.currentOverlays,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ }
+ runCurrent()
+ verifyDuringTransition?.invoke()
+
+ transitionStateFlow.value =
+ ObservableTransitionState.Idle(
+ currentScene = fromScene,
+ currentOverlays = setOf(toOverlay),
+ )
+ runCurrent()
+ verifyAfterTransition?.invoke()
+ }
+
private fun TestScope.prepareState(
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt
index 4d69f0ddc4b7..f86337ec63dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt
@@ -19,8 +19,8 @@ package com.android.systemui.scene.shared.flag
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN
import com.android.systemui.Flags.FLAG_EXAMPLE_FLAG
+import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.andSceneContainer
@@ -66,7 +66,7 @@ internal class SceneContainerFlagParameterizationTest : SysuiTestCase() {
@Test
fun oneDependencyAndSceneContainer() {
- val dependentFlag = FLAG_COMPOSE_LOCKSCREEN
+ val dependentFlag = FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
val result = FlagsParameterization.allCombinationsOf(dependentFlag).andSceneContainer()
Truth.assertThat(result).hasSize(3)
Truth.assertThat(result[0].mOverrides[dependentFlag]).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index fb32855ee2b7..0f6dc0723f42 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -17,7 +17,9 @@
package com.android.systemui.statusbar.policy.domain.interactor
import android.app.AutomaticZenRule
+import android.app.Flags
import android.app.NotificationManager.Policy
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
@@ -32,6 +34,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -50,10 +53,31 @@ class ZenModeInteractorTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val zenModeRepository = kosmos.fakeZenModeRepository
private val settingsRepository = kosmos.secureSettingsRepository
+ private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
private val underTest = kosmos.zenModeInteractor
@Test
+ fun isZenAvailable_off() =
+ testScope.runTest {
+ val isZenAvailable by collectLastValue(underTest.isZenAvailable)
+ deviceProvisioningRepository.setDeviceProvisioned(false)
+ runCurrent()
+
+ assertThat(isZenAvailable).isFalse()
+ }
+
+ @Test
+ fun isZenAvailable_on() =
+ testScope.runTest {
+ val isZenAvailable by collectLastValue(underTest.isZenAvailable)
+ deviceProvisioningRepository.setDeviceProvisioned(true)
+ runCurrent()
+
+ assertThat(isZenAvailable).isTrue()
+ }
+
+ @Test
fun isZenModeEnabled_off() =
testScope.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
@@ -337,4 +361,22 @@ class ZenModeInteractorTest : SysuiTestCase() {
runCurrent()
assertThat(mainActiveMode).isNull()
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun dndMode_flows() =
+ testScope.runTest {
+ val dndMode by collectLastValue(underTest.dndMode)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ runCurrent()
+
+ assertThat(dndMode!!.isActive).isFalse()
+
+ zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ runCurrent()
+
+ assertThat(dndMode!!.isActive).isTrue()
+ }
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f9c2aef5f070..ba3822bd3c23 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3114,6 +3114,10 @@
<string name="media_output_group_title_speakers_and_displays">Speakers &amp; Displays</string>
<!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] -->
<string name="media_output_group_title_suggested_device">Suggested Devices</string>
+ <!-- Title for input device group. [CHAR LIMIT=NONE] -->
+ <string name="media_input_group_title">Input</string>
+ <!-- Title for output device group. [CHAR LIMIT=NONE] -->
+ <string name="media_output_group_title">Output</string>
<!-- Summary for end session dialog. [CHAR LIMIT=NONE] -->
<string name="media_output_end_session_dialog_summary">Stop your shared session to move media to another device</string>
<!-- Button text for stopping session [CHAR LIMIT=60] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 7efe2dde1320..ffbc85ca530f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -968,6 +968,15 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT);
constraintSet.applyTo(mView);
}
+
+ @Override
+ public void onDestroy() {
+ if (mView == null) return;
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mView);
+ constraintSet.clear(mViewFlipper.getId());
+ constraintSet.applyTo(mView);
+ }
}
/**
@@ -1043,12 +1052,20 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
@Override
public void onDensityOrFontScaleChanged() {
mView.removeView(mUserSwitcherViewGroup);
+ mView.removeView(mUserSwitcher);
inflateUserSwitcher();
}
@Override
public void onDestroy() {
- mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mView);
+ constraintSet.clear(mUserSwitcherViewGroup.getId());
+ constraintSet.clear(mViewFlipper.getId());
+ constraintSet.applyTo(mView);
+
+ mView.removeView(mUserSwitcherViewGroup);
+ mView.removeView(mUserSwitcher);
}
private Drawable findLargeUserIcon(int userId) {
@@ -1344,5 +1361,13 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
constraintSet.constrainPercentWidth(mViewFlipper.getId(), 0.5f);
constraintSet.applyTo(mView);
}
+
+ @Override
+ public void onDestroy() {
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mView);
+ constraintSet.clear(mViewFlipper.getId());
+ constraintSet.applyTo(mView);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
index 19e7537007bf..b8c30fe9d4a8 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
@@ -76,11 +76,7 @@ class BouncerHapticPlayer @Inject constructor(private val msdlPlayer: dagger.Laz
/** Deliver MSDL feedback when the delete key of the pin bouncer is pressed */
fun playDeleteKeyPressFeedback() = msdlPlayer.get().playToken(MSDLToken.KEYPRESS_DELETE)
- /**
- * Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed
- *
- * @return whether MSDL feedback is allowed to play.
- */
+ /** Deliver MSDL feedback when the delete key of the pin bouncer is long-pressed. */
fun playDeleteKeyLongPressedFeedback() = msdlPlayer.get().playToken(MSDLToken.LONG_PRESS)
/** Deliver MSDL feedback when a numpad key is pressed on the pin bouncer */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index c67b35424cc9..873d1b3cc03d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -21,6 +21,7 @@ import com.android.app.tracing.coroutines.flow.collectLatest
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.lifecycle.ExclusiveActivatable
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
@@ -42,6 +43,7 @@ sealed class AuthMethodBouncerViewModel(
/** Name to use for performance tracing purposes. */
val traceName: String,
+ protected val bouncerHapticPlayer: BouncerHapticPlayer? = null,
) : ExclusiveActivatable() {
private val _animateFailure = MutableStateFlow(false)
@@ -80,6 +82,8 @@ sealed class AuthMethodBouncerViewModel(
return@collectLatest
}
+ performAuthenticationHapticFeedback(authenticationResult)
+
_animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
clearInput()
}
@@ -112,20 +116,23 @@ sealed class AuthMethodBouncerViewModel(
/** Returns the input entered so far. */
protected abstract fun getInput(): List<Any>
+ /** Perform authentication result haptics */
+ private fun performAuthenticationHapticFeedback(result: AuthenticationResult) {
+ if (result == AuthenticationResult.SKIPPED) return
+
+ bouncerHapticPlayer?.playAuthenticationFeedback(
+ authenticationSucceeded = result == AuthenticationResult.SUCCEEDED
+ )
+ }
+
/**
* Attempts to authenticate the user using the current input value.
*
* @see BouncerInteractor.authenticate
*/
- protected fun tryAuthenticate(
- input: List<Any> = getInput(),
- useAutoConfirm: Boolean = false,
- ) {
+ protected fun tryAuthenticate(input: List<Any> = getInput(), useAutoConfirm: Boolean = false) {
authenticationRequests.trySend(AuthenticationRequest(input, useAutoConfirm))
}
- private data class AuthenticationRequest(
- val input: List<Any>,
- val useAutoConfirm: Boolean,
- )
+ private data class AuthenticationRequest(val input: List<Any>, val useAutoConfirm: Boolean)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 0aada06a7eb7..0bcb58dee934 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -30,6 +30,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
@@ -61,6 +62,7 @@ constructor(
private val pinViewModelFactory: PinBouncerViewModel.Factory,
private val patternViewModelFactory: PatternBouncerViewModel.Factory,
private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
+ private val bouncerHapticPlayer: BouncerHapticPlayer,
) : ExclusiveActivatable() {
private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
@@ -162,10 +164,7 @@ constructor(
}
launch {
- combine(
- userSwitcher.users,
- userSwitcher.menu,
- ) { users, actions ->
+ combine(userSwitcher.users, userSwitcher.menu) { users, actions ->
users.map { user ->
UserSwitcherDropdownItemViewModel(
icon = Icon.Loaded(user.image, contentDescription = null),
@@ -178,7 +177,7 @@ constructor(
icon =
Icon.Resource(
action.iconResourceId,
- contentDescription = null
+ contentDescription = null,
),
text = Text.Resource(action.textResourceId),
onClick = action.onClicked,
@@ -226,7 +225,7 @@ constructor(
}
private fun getChildViewModel(
- authenticationMethod: AuthenticationMethodModel,
+ authenticationMethod: AuthenticationMethodModel
): AuthMethodBouncerViewModel? {
// If the current child view-model matches the authentication method, reuse it instead of
// creating a new instance.
@@ -241,12 +240,14 @@ constructor(
authenticationMethod = authenticationMethod,
onIntentionalUserInput = ::onIntentionalUserInput,
isInputEnabled = isInputEnabled,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
is AuthenticationMethodModel.Sim ->
pinViewModelFactory.create(
authenticationMethod = authenticationMethod,
onIntentionalUserInput = ::onIntentionalUserInput,
isInputEnabled = isInputEnabled,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
is AuthenticationMethodModel.Password ->
passwordViewModelFactory.create(
@@ -257,6 +258,7 @@ constructor(
patternViewModelFactory.create(
onIntentionalUserInput = ::onIntentionalUserInput,
isInputEnabled = isInputEnabled,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
else -> null
}
@@ -317,10 +319,7 @@ constructor(
return when {
// The wipe dialog takes priority over the lockout dialog.
wipeText != null ->
- DialogViewModel(
- text = wipeText,
- onDismiss = { wipeDialogMessage.value = null },
- )
+ DialogViewModel(text = wipeText, onDismiss = { wipeDialogMessage.value = null })
lockoutText != null ->
DialogViewModel(
text = lockoutText,
@@ -338,7 +337,7 @@ constructor(
fun onKeyEvent(keyEvent: KeyEvent): Boolean {
return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
keyEvent.type,
- keyEvent.nativeKeyEvent.keyCode
+ keyEvent.nativeKeyEvent.keyCode,
) ?: false
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 0a866b43429f..158f102ccdb3 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -18,9 +18,11 @@ package com.android.systemui.bouncer.ui.viewmodel
import android.content.Context
import android.util.TypedValue
+import android.view.View
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.res.R
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -35,7 +37,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
/** Holds UI state and handles user input for the pattern bouncer UI. */
@@ -44,6 +45,7 @@ class PatternBouncerViewModel
constructor(
private val applicationContext: Context,
interactor: BouncerInteractor,
+ @Assisted bouncerHapticPlayer: BouncerHapticPlayer,
@Assisted isInputEnabled: StateFlow<Boolean>,
@Assisted private val onIntentionalUserInput: () -> Unit,
) :
@@ -51,6 +53,7 @@ constructor(
interactor = interactor,
isInputEnabled = isInputEnabled,
traceName = "PatternBouncerViewModel",
+ bouncerHapticPlayer = bouncerHapticPlayer,
) {
/** The number of columns in the dot grid. */
@@ -190,14 +193,7 @@ constructor(
private fun defaultDots(): List<PatternDotViewModel> {
return buildList {
(0 until columnCount).forEach { x ->
- (0 until rowCount).forEach { y ->
- add(
- PatternDotViewModel(
- x = x,
- y = y,
- )
- )
- }
+ (0 until rowCount).forEach { y -> add(PatternDotViewModel(x = x, y = y)) }
}
}
}
@@ -207,14 +203,17 @@ constructor(
applicationContext.resources.getValue(
com.android.internal.R.dimen.lock_pattern_dot_hit_factor,
outValue,
- true
+ true,
)
max(min(outValue.float, 1f), MIN_DOT_HIT_FACTOR)
}
+ fun performDotFeedback(view: View?) = bouncerHapticPlayer?.playPatternDotFeedback(view)
+
@AssistedFactory
interface Factory {
fun create(
+ bouncerHapticPlayer: BouncerHapticPlayer,
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
): PatternBouncerViewModel
@@ -231,7 +230,7 @@ constructor(
*/
private fun PatternDotViewModel.isOnLineSegment(
first: PatternDotViewModel,
- second: PatternDotViewModel
+ second: PatternDotViewModel,
): Boolean {
val anotherPoint = this
// No need to consider any points outside the bounds of two end points
@@ -253,14 +252,8 @@ private fun Int.isBetween(a: Int, b: Int): Boolean {
return (this in a..b) || (this in b..a)
}
-data class PatternDotViewModel(
- val x: Int,
- val y: Int,
-) {
+data class PatternDotViewModel(val x: Int, val y: Int) {
fun toCoordinate(): AuthenticationPatternCoordinate {
- return AuthenticationPatternCoordinate(
- x = x,
- y = y,
- )
+ return AuthenticationPatternCoordinate(x = x, y = y)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index da29c6230cd8..0cb4260e4d7f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -19,12 +19,14 @@
package com.android.systemui.bouncer.ui.viewmodel
import android.content.Context
+import android.view.HapticFeedbackConstants
import android.view.KeyEvent.KEYCODE_0
import android.view.KeyEvent.KEYCODE_9
import android.view.KeyEvent.KEYCODE_DEL
import android.view.KeyEvent.KEYCODE_NUMPAD_0
import android.view.KeyEvent.KEYCODE_NUMPAD_9
import android.view.KeyEvent.isConfirmKey
+import android.view.View
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import com.android.keyguard.PinShapeAdapter
@@ -32,6 +34,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.res.R
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -56,6 +59,7 @@ constructor(
applicationContext: Context,
interactor: BouncerInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
+ @Assisted bouncerHapticPlayer: BouncerHapticPlayer,
@Assisted isInputEnabled: StateFlow<Boolean>,
@Assisted private val onIntentionalUserInput: () -> Unit,
@Assisted override val authenticationMethod: AuthenticationMethodModel,
@@ -64,6 +68,7 @@ constructor(
interactor = interactor,
isInputEnabled = isInputEnabled,
traceName = "PinBouncerViewModel",
+ bouncerHapticPlayer = bouncerHapticPlayer,
) {
/**
* Whether the sim-related UI in the pin view is showing.
@@ -126,10 +131,9 @@ constructor(
.collect { _hintedPinLength.value = it }
}
launch {
- combine(
- mutablePinInput,
- interactor.isAutoConfirmEnabled,
- ) { mutablePinEntries, isAutoConfirmEnabled ->
+ combine(mutablePinInput, interactor.isAutoConfirmEnabled) {
+ mutablePinEntries,
+ isAutoConfirmEnabled ->
computeBackspaceButtonAppearance(
pinInput = mutablePinEntries,
isAutoConfirmEnabled = isAutoConfirmEnabled,
@@ -183,8 +187,22 @@ constructor(
mutablePinInput.value = mutablePinInput.value.deleteLast()
}
+ fun onBackspaceButtonPressed(view: View?) {
+ if (bouncerHapticPlayer?.isEnabled == true) {
+ bouncerHapticPlayer.playDeleteKeyPressFeedback()
+ } else {
+ view?.performHapticFeedback(
+ HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+ )
+ }
+ }
+
/** Notifies that the user long-pressed the backspace button. */
fun onBackspaceButtonLongPressed() {
+ if (bouncerHapticPlayer?.isEnabled == true) {
+ bouncerHapticPlayer.playDeleteKeyLongPressedFeedback()
+ }
clearInput()
}
@@ -266,13 +284,24 @@ constructor(
}
}
- /** Notifies that the user has pressed down on a digit button. */
- fun onDigitButtonDown() {
+ /**
+ * Notifies that the user has pressed down on a digit button. This function also performs haptic
+ * feedback on the view.
+ */
+ fun onDigitButtonDown(view: View?) {
if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
// Current PIN bouncer informs FalsingInteractor#avoidGesture() upon every Pin button
// touch.
super.onDown()
}
+ if (bouncerHapticPlayer?.isEnabled == true) {
+ bouncerHapticPlayer.playNumpadKeyFeedback()
+ } else {
+ view?.performHapticFeedback(
+ HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+ )
+ }
}
@AssistedFactory
@@ -281,6 +310,7 @@ constructor(
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
authenticationMethod: AuthenticationMethodModel,
+ bouncerHapticPlayer: BouncerHapticPlayer,
): PinBouncerViewModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index c7a47b18f467..1ada56dea45a 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -30,6 +30,7 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -37,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.user.utils.UserScopedService;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -67,13 +69,13 @@ public class ClipboardListener implements
public ClipboardListener(Context context,
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardToast clipboardToast,
- ClipboardManager clipboardManager,
+ UserScopedService<ClipboardManager> clipboardManager,
KeyguardManager keyguardManager,
UiEventLogger uiEventLogger) {
mContext = context;
mOverlayProvider = clipboardOverlayControllerProvider;
mClipboardToast = clipboardToast;
- mClipboardManager = clipboardManager;
+ mClipboardManager = clipboardManager.forUser(UserHandle.CURRENT);
mKeyguardManager = keyguardManager;
mUiEventLogger = uiEventLogger;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 8818c3af4916..8f913ff01337 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -733,9 +733,8 @@ public class FrameworkServicesModule {
}
@Provides
- @Singleton
- static ClipboardManager provideClipboardManager(Context context) {
- return context.getSystemService(ClipboardManager.class);
+ static UserScopedService<ClipboardManager> provideClipboardManager(Context context) {
+ return new UserScopedServiceImpl<>(context, ClipboardManager.class);
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index e17e530a03d9..5259c5dca39f 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -26,7 +26,9 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.flags.SystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
@@ -36,6 +38,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -57,6 +60,7 @@ constructor(
private val powerInteractor: PowerInteractor,
private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
private val systemPropertiesHelper: SystemPropertiesHelper,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
private val deviceUnlockSource =
@@ -74,7 +78,7 @@ constructor(
trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent },
authenticationInteractor.onAuthenticationResult
.filter { it }
- .map { DeviceUnlockSource.BouncerInput }
+ .map { DeviceUnlockSource.BouncerInput },
)
private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
@@ -170,10 +174,20 @@ constructor(
combine(
powerInteractor.isAsleep,
isInLockdown,
- ::Pair,
+ keyguardTransitionInteractor
+ .transitionValue(KeyguardState.AOD)
+ .map { it == 1f }
+ .distinctUntilChanged(),
+ ::Triple,
)
- .flatMapLatestConflated { (isAsleep, isInLockdown) ->
- if (isAsleep || isInLockdown) {
+ .flatMapLatestConflated { (isAsleep, isInLockdown, isAod) ->
+ val isForceLocked =
+ when {
+ isAsleep && !isAod -> true
+ isInLockdown -> true
+ else -> false
+ }
+ if (isForceLocked) {
flowOf(DeviceUnlockStatus(false, null))
} else {
deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
index 1daaa1128ba0..500c5b387ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.education.data.model
+import com.android.systemui.contextualeducation.GestureType
import java.time.Instant
/**
@@ -23,6 +24,7 @@ import java.time.Instant
* gesture stores its own model separately.
*/
data class GestureEduModel(
+ val gestureType: GestureType,
val signalCount: Int = 0,
val educationShownCount: Int = 0,
val lastShortcutTriggeredTime: Instant? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 01f838ff1ea7..29785959de18 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -17,6 +17,8 @@
package com.android.systemui.education.data.repository
import android.content.Context
+import android.hardware.input.InputManager
+import android.hardware.input.KeyGestureEvent
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.MutablePreferences
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
@@ -25,23 +27,31 @@ import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.preferencesDataStoreFile
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.time.Instant
+import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Provider
import kotlin.properties.Delegates.notNull
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
/**
@@ -64,6 +74,8 @@ interface ContextualEducationRepository {
suspend fun updateEduDeviceConnectionTime(
transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
)
+
+ val keyboardShortcutTriggered: Flow<GestureType>
}
/**
@@ -75,9 +87,13 @@ class UserContextualEducationRepository
@Inject
constructor(
@Application private val applicationContext: Context,
- @EduDataStoreScope private val dataStoreScopeProvider: Provider<CoroutineScope>
+ @EduDataStoreScope private val dataStoreScopeProvider: Provider<CoroutineScope>,
+ private val inputManager: InputManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : ContextualEducationRepository {
companion object {
+ const val TAG = "UserContextualEducationRepository"
+
const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT"
const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN"
const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
@@ -98,6 +114,30 @@ constructor(
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
private val prefData: Flow<Preferences> = datastore.filterNotNull().flatMapLatest { it.data }
+ override val keyboardShortcutTriggered: Flow<GestureType> =
+ conflatedCallbackFlow {
+ val listener =
+ InputManager.KeyGestureEventListener { event ->
+ // Only store keyboard shortcut time for gestures providing keyboard
+ // education
+ val shortcutType =
+ when (event.keyGestureType) {
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS -> ALL_APPS
+
+ else -> null
+ }
+
+ if (shortcutType != null) {
+ trySendWithFailureLogging(shortcutType, TAG)
+ }
+ }
+
+ inputManager.registerKeyGestureEventListener(Executor(Runnable::run), listener)
+ awaitClose { inputManager.unregisterKeyGestureEventListener(listener) }
+ }
+ .flowOn(backgroundDispatcher)
+
override fun setUser(userId: Int) {
dataStoreScope?.cancel()
val newDsScope = dataStoreScopeProvider.get()
@@ -136,7 +176,8 @@ constructor(
preferences[getLastEducationTimeKey(gestureType)]?.let {
Instant.ofEpochSecond(it)
},
- userId = userId
+ userId = userId,
+ gestureType = gestureType,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
index 10be26e1ce0f..c88b36495ac2 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -18,7 +18,10 @@ package com.android.systemui.education.domain.interactor
import com.android.systemui.CoreStartable
import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
@@ -53,6 +56,13 @@ constructor(
) : CoreStartable {
val backGestureModelFlow = readEduModelsOnSignalCountChanged(BACK)
+ val homeGestureModelFlow = readEduModelsOnSignalCountChanged(HOME)
+ val overviewGestureModelFlow = readEduModelsOnSignalCountChanged(OVERVIEW)
+ val allAppsGestureModelFlow = readEduModelsOnSignalCountChanged(ALL_APPS)
+ val eduDeviceConnectionTimeFlow =
+ repository.readEduDeviceConnectionTime().distinctUntilChanged()
+
+ val keyboardShortcutTriggered = repository.keyboardShortcutTriggered
override fun start() {
backgroundScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index 43855d971a2a..faee32694964 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -16,15 +16,8 @@
package com.android.systemui.education.domain.interactor
-import android.hardware.input.InputManager
-import android.hardware.input.InputManager.KeyGestureEventListener
-import android.hardware.input.KeyGestureEvent
import android.os.SystemProperties
import com.android.systemui.CoreStartable
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.contextualeducation.GestureType
-import com.android.systemui.contextualeducation.GestureType.ALL_APPS
-import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
@@ -32,19 +25,19 @@ import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.shared.model.EducationInfo
import com.android.systemui.education.shared.model.EducationUiType
import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.time.Clock
-import java.util.concurrent.Executor
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.DurationUnit
import kotlin.time.toDuration
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
/** Allow listening to new contextual education triggered */
@@ -55,7 +48,6 @@ constructor(
@Background private val backgroundScope: CoroutineScope,
private val contextualEducationInteractor: ContextualEducationInteractor,
private val userInputDeviceRepository: UserInputDeviceRepository,
- private val inputManager: InputManager,
@EduClock private val clock: Clock,
) : CoreStartable {
@@ -82,34 +74,32 @@ constructor(
private val _educationTriggered = MutableStateFlow<EducationInfo?>(null)
val educationTriggered = _educationTriggered.asStateFlow()
- private val keyboardShortcutTriggered: Flow<GestureType> = conflatedCallbackFlow {
- val listener = KeyGestureEventListener { event ->
- // Only store keyboard shortcut time for gestures providing keyboard education
- val shortcutType =
- when (event.keyGestureType) {
- KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS -> ALL_APPS
- else -> null
- }
-
- if (shortcutType != null) {
- trySendWithFailureLogging(shortcutType, TAG)
- }
- }
-
- inputManager.registerKeyGestureEventListener(Executor(Runnable::run), listener)
- awaitClose { inputManager.unregisterKeyGestureEventListener(listener) }
- }
-
+ @OptIn(ExperimentalCoroutinesApi::class)
override fun start() {
backgroundScope.launch {
- contextualEducationInteractor.backGestureModelFlow.collect {
- if (isUsageSessionExpired(it)) {
- contextualEducationInteractor.startNewUsageSession(BACK)
- } else if (isEducationNeeded(it)) {
- _educationTriggered.value = EducationInfo(BACK, getEduType(it), it.userId)
- contextualEducationInteractor.updateOnEduTriggered(BACK)
+ contextualEducationInteractor.eduDeviceConnectionTimeFlow
+ .flatMapLatest {
+ val gestureFlows = mutableListOf<Flow<GestureEduModel>>()
+ if (it.touchpadFirstConnectionTime != null) {
+ gestureFlows.add(contextualEducationInteractor.backGestureModelFlow)
+ gestureFlows.add(contextualEducationInteractor.homeGestureModelFlow)
+ gestureFlows.add(contextualEducationInteractor.overviewGestureModelFlow)
+ }
+
+ if (it.keyboardFirstConnectionTime != null) {
+ gestureFlows.add(contextualEducationInteractor.allAppsGestureModelFlow)
+ }
+ gestureFlows.merge()
+ }
+ .collect {
+ if (isUsageSessionExpired(it)) {
+ contextualEducationInteractor.startNewUsageSession(it.gestureType)
+ } else if (isEducationNeeded(it)) {
+ _educationTriggered.value =
+ EducationInfo(it.gestureType, getEduType(it), it.userId)
+ contextualEducationInteractor.updateOnEduTriggered(it.gestureType)
+ }
}
- }
}
backgroundScope.launch {
@@ -139,7 +129,7 @@ constructor(
}
backgroundScope.launch {
- keyboardShortcutTriggered.collect {
+ contextualEducationInteractor.keyboardShortcutTriggered.collect {
contextualEducationInteractor.updateShortcutTriggerTime(it)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 6318dc000c21..0b775ab486bd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -31,9 +31,7 @@ import com.android.systemui.Flags.statusBarCallChipNotificationIcon
import com.android.systemui.Flags.statusBarScreenSharingChips
import com.android.systemui.Flags.statusBarUseReposForCallChip
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
-import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
@@ -62,10 +60,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
- // ComposeLockscreen dependencies
- ComposeLockscreen.token dependsOn KeyguardBottomAreaRefactor.token
- ComposeLockscreen.token dependsOn MigrateClocksToBlueprint.token
-
// CommunalHub dependencies
communalHub dependsOn MigrateClocksToBlueprint.token
@@ -99,7 +93,7 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
get() =
FlagToken(
FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON,
- statusBarCallChipNotificationIcon()
+ statusBarCallChipNotificationIcon(),
)
private inline val statusBarScreenSharingChipsToken
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index e50c05c7f6ed..e09e1987698d 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -29,6 +29,7 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.QSLog
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -44,12 +45,12 @@ import javax.inject.Inject
* @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects.
* @property[effectDuration] The duration of the effect in ms.
*/
-// TODO(b/332902869): In addition from being injectable, we can consider making it a singleton
class QSLongPressEffect
@Inject
constructor(
private val vibratorHelper: VibratorHelper?,
private val keyguardStateController: KeyguardStateController,
+ private val falsingManager: FalsingManager,
@QSLog private val logBuffer: LogBuffer,
) {
@@ -72,7 +73,7 @@ constructor(
private val durations =
vibratorHelper?.getPrimitiveDurations(
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
- VibrationEffect.Composition.PRIMITIVE_SPIN
+ VibrationEffect.Composition.PRIMITIVE_SPIN,
)
private var longPressHint: VibrationEffect? = null
@@ -152,15 +153,27 @@ constructor(
logEvent(qsTile?.tileSpec, state, "animation completed")
when (state) {
State.RUNNING_FORWARD -> {
- vibrate(snapEffect)
- if (keyguardStateController.isUnlocked) {
+ val wasFalseLongTap = falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+ if (wasFalseLongTap) {
+ callback?.onResetProperties()
+ setState(State.IDLE)
+ logEvent(qsTile?.tileSpec, state, "false long click. No action triggered")
+ } else if (keyguardStateController.isUnlocked) {
+ vibrate(snapEffect)
setState(State.LONG_CLICKED)
+ qsTile?.longClick(expandable)
+ logEvent(qsTile?.tileSpec, state, "long click action triggered")
} else {
+ vibrate(snapEffect)
callback?.onResetProperties()
setState(State.IDLE)
+ qsTile?.longClick(expandable)
+ logEvent(
+ qsTile?.tileSpec,
+ state,
+ "properties reset and long click action triggered",
+ )
}
- logEvent(qsTile?.tileSpec, state, "long click action triggered")
- qsTile?.longClick(expandable)
}
State.RUNNING_BACKWARDS_FROM_UP -> {
callback?.onEffectFinishedReversing()
@@ -236,7 +249,7 @@ constructor(
LongPressHapticBuilder.createLongPressHint(
durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION,
durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
- effectDuration
+ effectDuration,
)
setState(State.IDLE)
return true
@@ -265,7 +278,7 @@ constructor(
}
override fun dialogTransitionController(
- cuj: DialogCuj?,
+ cuj: DialogCuj?
): DialogTransitionAnimator.Controller? =
DialogTransitionAnimator.Controller.fromView(view, cuj)
}
@@ -298,7 +311,7 @@ constructor(
str2 = event
str3 = state.name
},
- { "[long-press effect on $str1 tile] $str2 on state: $str3" }
+ { "[long-press effect on $str1 tile] $str2 on state: $str3" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
index 95251749132d..48f5cb6dc219 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/InputDeviceTutorialLogger.kt
@@ -16,27 +16,27 @@
package com.android.systemui.inputdevice.tutorial
+import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen as KeyboardTouchpadTutorialScreen
+import com.android.systemui.log.ConstantStringsLogger
+import com.android.systemui.log.ConstantStringsLoggerImpl
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
import com.android.systemui.log.dagger.InputDeviceTutorialLog
-import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen
-import com.google.errorprone.annotations.CompileTimeConstant
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen as TouchpadTutorialScreen
import javax.inject.Inject
private const val TAG = "InputDeviceTutorial"
class InputDeviceTutorialLogger
@Inject
-constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) {
+constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) :
+ ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
- fun log(@CompileTimeConstant s: String) {
- buffer.log(TAG, LogLevel.INFO, message = s)
- }
-
- fun logGoingToScreen(screen: Screen, context: TutorialContext) {
- buffer.log(
- TAG,
- LogLevel.INFO,
+ fun logGoingToScreen(screen: TouchpadTutorialScreen, context: TutorialContext) {
+ logInfo(
{
str1 = screen.toString()
str2 = context.string
@@ -46,7 +46,58 @@ constructor(@InputDeviceTutorialLog private val buffer: LogBuffer) {
}
fun logCloseTutorial(context: TutorialContext) {
- buffer.log(TAG, LogLevel.INFO, { str1 = context.string }, { "Closing $str1" })
+ logInfo({ str1 = context.string }, { "Closing $str1" })
+ }
+
+ fun logOpenTutorial(context: TutorialContext) {
+ logInfo({ str1 = context.string }, { "Opening $str1" })
+ }
+
+ fun logNextScreenMissingHardware(nextScreen: KeyboardTouchpadTutorialScreen) {
+ buffer.log(
+ TAG,
+ LogLevel.WARNING,
+ { str1 = nextScreen.toString() },
+ { "next screen should be $str1 but required hardware is missing" }
+ )
+ }
+
+ fun logNextScreen(nextScreen: KeyboardTouchpadTutorialScreen) {
+ logInfo({ str1 = nextScreen.toString() }, { "going to $str1 screen" })
+ }
+
+ fun logNewConnectionState(connectionState: ConnectionState) {
+ logInfo(
+ {
+ bool1 = connectionState.touchpadConnected
+ bool2 = connectionState.keyboardConnected
+ },
+ { "Received connection state: touchpad connected: $bool1 keyboard connected: $bool2" }
+ )
+ }
+
+ fun logMovingBetweenScreens(
+ previousScreen: KeyboardTouchpadTutorialScreen?,
+ currentScreen: KeyboardTouchpadTutorialScreen
+ ) {
+ logInfo(
+ {
+ str1 = previousScreen?.toString() ?: "NO_SCREEN"
+ str2 = currentScreen.toString()
+ },
+ { "Moving from $str1 screen to $str2 screen" }
+ )
+ }
+
+ fun logGoingBack(previousScreen: KeyboardTouchpadTutorialScreen) {
+ logInfo({ str1 = previousScreen.toString() }, { "Going back to $str1 screen" })
+ }
+
+ private inline fun logInfo(
+ messageInitializer: MessageInitializer,
+ noinline messagePrinter: MessagePrinter
+ ) {
+ buffer.log(TAG, LogLevel.INFO, messageInitializer, messagePrinter)
}
enum class TutorialContext(val string: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
index 1adc285e6bb5..c130c6c7fe12 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -28,6 +28,8 @@ import androidx.lifecycle.Lifecycle.State.STARTED
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.android.compose.theme.PlatformTheme
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel
@@ -48,6 +50,7 @@ class KeyboardTouchpadTutorialActivity
constructor(
private val viewModelFactoryAssistedProvider: ViewModelFactoryAssistedProvider,
private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
+ private val logger: InputDeviceTutorialLogger,
) : ComponentActivity() {
companion object {
@@ -74,6 +77,7 @@ constructor(
lifecycleScope.launch {
vm.closeActivity.collect { finish ->
if (finish) {
+ logger.logCloseTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL)
finish()
}
}
@@ -81,6 +85,9 @@ constructor(
setContent {
PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) }
}
+ if (savedInstanceState == null) {
+ logger.logOpenTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
index 315c102e94d0..5cf19677a98e 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
@@ -22,6 +22,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState
import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
@@ -47,6 +48,7 @@ class KeyboardTouchpadTutorialViewModel(
private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
private val keyboardTouchpadConnectionInteractor: KeyboardTouchpadConnectionInteractor,
private val hasTouchpadTutorialScreens: Boolean,
+ private val logger: InputDeviceTutorialLogger,
handle: SavedStateHandle
) : ViewModel(), DefaultLifecycleObserver {
@@ -68,7 +70,10 @@ class KeyboardTouchpadTutorialViewModel(
init {
viewModelScope.launch {
- keyboardTouchpadConnectionInteractor.connectionState.collect { connectionState = it }
+ keyboardTouchpadConnectionInteractor.connectionState.collect {
+ logger.logNewConnectionState(connectionState)
+ connectionState = it
+ }
}
viewModelScope.launch {
@@ -89,7 +94,14 @@ class KeyboardTouchpadTutorialViewModel(
viewModelScope.launch {
// close activity if screen requires touchpad but we don't have it. This can only happen
// when current sysui build doesn't contain touchpad module dependency
- _screen.filterNot { it.canBeShown() }.collect { _closeActivity.value = true }
+ _screen
+ .filterNot { it.canBeShown() }
+ .collect {
+ logger.e(
+ "Touchpad is connected but touchpad module is missing, something went wrong"
+ )
+ _closeActivity.value = true
+ }
}
}
@@ -114,11 +126,14 @@ class KeyboardTouchpadTutorialViewModel(
if (requiredHardwarePresent(nextScreen)) {
break
}
+ logger.logNextScreenMissingHardware(nextScreen)
nextScreen = nextScreen.next()
}
if (nextScreen == null) {
+ logger.d("Final screen reached, closing tutorial")
_closeActivity.value = true
} else {
+ logger.logNextScreen(nextScreen)
_screen.value = nextScreen
screensBackStack.add(nextScreen)
}
@@ -127,6 +142,7 @@ class KeyboardTouchpadTutorialViewModel(
private fun Screen.canBeShown() = requiredHardware != TOUCHPAD || hasTouchpadTutorialScreens
private fun setupDeviceState(previousScreen: Screen?, currentScreen: Screen) {
+ logger.logMovingBetweenScreens(previousScreen, currentScreen)
if (previousScreen?.requiredHardware == currentScreen.requiredHardware) return
previousScreen?.let { clearDeviceStateForScreen(it) }
when (currentScreen.requiredHardware) {
@@ -153,6 +169,7 @@ class KeyboardTouchpadTutorialViewModel(
_closeActivity.value = true
} else {
screensBackStack.removeLast()
+ logger.logGoingBack(screensBackStack.last())
_screen.value = screensBackStack.last()
}
}
@@ -162,6 +179,7 @@ class KeyboardTouchpadTutorialViewModel(
constructor(
private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
private val keyboardTouchpadConnected: KeyboardTouchpadConnectionInteractor,
+ private val logger: InputDeviceTutorialLogger,
@Assisted private val hasTouchpadTutorialScreens: Boolean,
) : AbstractSavedStateViewModelFactory() {
@@ -180,6 +198,7 @@ class KeyboardTouchpadTutorialViewModel(
gesturesInteractor,
keyboardTouchpadConnected,
hasTouchpadTutorialScreens,
+ logger,
handle
)
as T
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index df0f10acac1f..416eabae78eb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -24,12 +24,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -47,7 +41,6 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
@@ -128,7 +121,7 @@ constructor(
keyguardStatusViewComponentFactory.build(
LayoutInflater.from(context).inflate(R.layout.keyguard_status_view, null)
as KeyguardStatusView,
- context.display
+ context.display,
)
val controller = statusViewComponent.keyguardStatusViewController
controller.init()
@@ -143,29 +136,12 @@ constructor(
initializeViews()
if (!SceneContainerFlag.isEnabled) {
- if (ComposeLockscreen.isEnabled) {
- val composeView =
- createLockscreen(
- context = context,
- viewModelFactory = lockscreenContentViewModelFactory,
- blueprints = lockscreenSceneBlueprintsLazy.get(),
- )
- composeView.id = View.generateViewId()
- val cs = ConstraintSet()
- cs.clone(keyguardRootView)
- cs.connect(composeView.id, START, PARENT_ID, START)
- cs.connect(composeView.id, END, PARENT_ID, END)
- cs.connect(composeView.id, TOP, PARENT_ID, TOP)
- cs.connect(composeView.id, BOTTOM, PARENT_ID, BOTTOM)
- keyguardRootView.addView(composeView)
- } else {
- KeyguardBlueprintViewBinder.bind(
- keyguardRootView,
- keyguardBlueprintViewModel,
- keyguardClockViewModel,
- smartspaceViewModel,
- )
- }
+ KeyguardBlueprintViewBinder.bind(
+ keyguardRootView,
+ keyguardBlueprintViewModel,
+ keyguardClockViewModel,
+ smartspaceViewModel,
+ )
}
if (deviceEntryUnlockTrackerViewBinder.isPresent) {
deviceEntryUnlockTrackerViewBinder.get().bind(keyguardRootView)
@@ -247,7 +223,7 @@ constructor(
LockscreenContent(
viewModelFactory = viewModelFactory,
blueprints = sceneBlueprints,
- clockInteractor = clockInteractor
+ clockInteractor = clockInteractor,
)
) {
Content(modifier = Modifier.fillMaxSize())
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index 406b9f6e6a0d..be873344b719 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -25,7 +25,9 @@ import android.provider.Settings.Global.ZEN_MODE_OFF
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import android.service.notification.ZenModeConfig
+import android.util.Log
import com.android.settingslib.notification.modes.EnableZenModeDialog
+import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -35,30 +37,38 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
@SysUISingleton
class DoNotDisturbQuickAffordanceConfig
constructor(
private val context: Context,
private val controller: ZenModeController,
+ private val interactor: ZenModeInteractor,
private val secureSettings: SecureSettings,
private val userTracker: UserTracker,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Background private val backgroundScope: CoroutineScope,
private val testConditionId: Uri?,
testDialog: EnableZenModeDialog?,
) : KeyguardQuickAffordanceConfig {
@@ -67,15 +77,45 @@ constructor(
constructor(
context: Context,
controller: ZenModeController,
+ interactor: ZenModeInteractor,
secureSettings: SecureSettings,
userTracker: UserTracker,
@Background backgroundDispatcher: CoroutineDispatcher,
- ) : this(context, controller, secureSettings, userTracker, backgroundDispatcher, null, null)
+ @Background backgroundScope: CoroutineScope,
+ ) : this(
+ context,
+ controller,
+ interactor,
+ secureSettings,
+ userTracker,
+ backgroundDispatcher,
+ backgroundScope,
+ null,
+ null,
+ )
- private var dndMode: Int = 0
- private var isAvailable = false
+ private var zenMode: Int = 0
+ private var oldIsAvailable = false
private var settingsValue: Int = 0
+ private val dndMode: StateFlow<ZenMode?> by lazy {
+ ModesUi.assertInNewMode()
+ interactor.dndMode.stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+ }
+
+ private val isAvailable: StateFlow<Boolean> by lazy {
+ ModesUi.assertInNewMode()
+ interactor.isZenAvailable.stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+ }
+
private val conditionUri: Uri
get() =
testConditionId
@@ -104,42 +144,68 @@ constructor(
override val pickerIconResourceId: Int = R.drawable.ic_do_not_disturb
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
- combine(
- conflatedCallbackFlow {
- val callback =
- object : ZenModeController.Callback {
- override fun onZenChanged(zen: Int) {
- dndMode = zen
- trySendWithFailureLogging(updateState(), TAG)
- }
+ if (ModesUi.isEnabled) {
+ combine(isAvailable, dndMode) { isAvailable, dndMode ->
+ if (!isAvailable) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ } else if (dndMode?.isActive == true) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_on,
+ ContentDescription.Resource(R.string.dnd_is_on),
+ ),
+ ActivationState.Active,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_dnd_icon_off,
+ ContentDescription.Resource(R.string.dnd_is_off),
+ ),
+ ActivationState.Inactive,
+ )
+ }
+ }
+ } else {
+ combine(
+ conflatedCallbackFlow {
+ val callback =
+ object : ZenModeController.Callback {
+ override fun onZenChanged(zen: Int) {
+ zenMode = zen
+ trySendWithFailureLogging(updateState(), TAG)
+ }
- override fun onZenAvailableChanged(available: Boolean) {
- isAvailable = available
- trySendWithFailureLogging(updateState(), TAG)
+ override fun onZenAvailableChanged(available: Boolean) {
+ oldIsAvailable = available
+ trySendWithFailureLogging(updateState(), TAG)
+ }
}
- }
- dndMode = controller.zen
- isAvailable = controller.isZenAvailable
- trySendWithFailureLogging(updateState(), TAG)
-
- controller.addCallback(callback)
-
- awaitClose { controller.removeCallback(callback) }
- },
- secureSettings
- .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION)
- .onStart { emit(Unit) }
- .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) }
- .flowOn(backgroundDispatcher)
- .distinctUntilChanged()
- .onEach { settingsValue = it }
- ) { callbackFlowValue, _ ->
- callbackFlowValue
+ zenMode = controller.zen
+ oldIsAvailable = controller.isZenAvailable
+ trySendWithFailureLogging(updateState(), TAG)
+
+ controller.addCallback(callback)
+
+ awaitClose { controller.removeCallback(callback) }
+ },
+ secureSettings
+ .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION)
+ .onStart { emit(Unit) }
+ .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+ .onEach { settingsValue = it },
+ ) { callbackFlowValue, _ ->
+ callbackFlowValue
+ }
}
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
- return if (controller.isZenAvailable) {
+ val isZenAvailable = if (ModesUi.isEnabled) isAvailable.value else controller.isZenAvailable
+
+ return if (isZenAvailable) {
KeyguardQuickAffordanceConfig.PickerScreenState.Default(
configureIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
)
@@ -151,32 +217,63 @@ constructor(
override fun onTriggered(
expandable: Expandable?
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
- return when {
- !isAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
- dndMode != ZEN_MODE_OFF -> {
- controller.setZen(ZEN_MODE_OFF, null, TAG)
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
- }
- settingsValue == ZEN_DURATION_PROMPT ->
- KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
- dialog.createDialog(),
- expandable
- )
- settingsValue == ZEN_DURATION_FOREVER -> {
- controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG)
+ return if (ModesUi.isEnabled) {
+ if (!isAvailable.value) {
KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ } else {
+ val dnd = dndMode.value
+ if (dnd == null) {
+ Log.wtf(TAG, "Triggered DND but it's null!?")
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+ if (dnd.isActive) {
+ interactor.deactivateMode(dnd)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ } else {
+ if (interactor.shouldAskForZenDuration(dnd)) {
+ // NOTE: The dialog handles turning on the mode itself.
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
+ dialog.createDialog(),
+ expandable,
+ )
+ } else {
+ interactor.activateMode(dnd)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+ }
}
- else -> {
- controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG)
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ } else {
+ when {
+ !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ zenMode != ZEN_MODE_OFF -> {
+ controller.setZen(ZEN_MODE_OFF, null, TAG)
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ settingsValue == ZEN_DURATION_PROMPT ->
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
+ dialog.createDialog(),
+ expandable,
+ )
+
+ settingsValue == ZEN_DURATION_FOREVER -> {
+ controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG)
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ else -> {
+ controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG)
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
}
}
}
private fun updateState(): KeyguardQuickAffordanceConfig.LockScreenState {
- return if (!isAvailable) {
+ ModesUi.assertInLegacyMode()
+ return if (!oldIsAvailable) {
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
- } else if (dndMode == ZEN_MODE_OFF) {
+ } else if (zenMode == ZEN_MODE_OFF) {
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
R.drawable.qs_dnd_icon_off,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 7afc7596a994..6932eb51e141 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -25,13 +25,13 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
-import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -64,7 +64,7 @@ constructor(
/** Current BlueprintId */
val blueprintId =
shadeInteractor.isShadeLayoutWide.map { isShadeLayoutWide ->
- val useSplitShade = isShadeLayoutWide && !ComposeLockscreen.isEnabled
+ val useSplitShade = isShadeLayoutWide && !SceneContainerFlag.isEnabled
when {
useSplitShade -> SplitShadeKeyguardBlueprint.ID
else -> DefaultKeyguardBlueprint.DEFAULT
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
deleted file mode 100644
index 601fbfaf1b64..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2024 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
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the compose lockscreen flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object ComposeLockscreen {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_COMPOSE_LOCKSCREEN
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the refactor enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.composeLockscreen()
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
- * build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index ed82159e6160..deb0b2d8f848 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -59,7 +59,6 @@ import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -72,6 +71,7 @@ import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.VibratorHelper
@@ -241,7 +241,7 @@ object KeyguardRootViewBinder {
disposables +=
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- if (ComposeLockscreen.isEnabled) {
+ if (SceneContainerFlag.isEnabled) {
view.setViewTreeOnBackPressedDispatcherOwner(
object : OnBackPressedDispatcherOwner {
override val onBackPressedDispatcher =
@@ -261,10 +261,7 @@ object KeyguardRootViewBinder {
->
if (biometricMessage?.message != null) {
chipbarCoordinator!!.displayView(
- createChipbarInfo(
- biometricMessage.message,
- R.drawable.ic_lock,
- )
+ createChipbarInfo(biometricMessage.message, R.drawable.ic_lock)
)
} else {
chipbarCoordinator!!.removeView(ID, "occludingAppMsgNull")
@@ -327,12 +324,16 @@ object KeyguardRootViewBinder {
.getDimensionPixelSize(R.dimen.shelf_appear_translation)
.stateIn(this)
viewModel.isNotifIconContainerVisible.collect { isVisible ->
- childViews[aodNotificationIconContainerId]
- ?.setAodNotifIconContainerIsVisible(
- isVisible,
- iconsAppearTranslationPx.value,
- screenOffAnimationController,
- )
+ if (isVisible.value) {
+ blueprintViewModel.refreshBlueprint()
+ } else {
+ childViews[aodNotificationIconContainerId]
+ ?.setAodNotifIconContainerIsVisible(
+ isVisible,
+ iconsAppearTranslationPx.value,
+ screenOffAnimationController,
+ )
+ }
}
}
@@ -382,7 +383,7 @@ object KeyguardRootViewBinder {
if (msdlFeedback()) {
msdlPlayer?.playToken(
MSDLToken.UNLOCK,
- authInteractionProperties
+ authInteractionProperties,
)
} else {
vibratorHelper.performHapticFeedback(
@@ -398,7 +399,7 @@ object KeyguardRootViewBinder {
if (msdlFeedback()) {
msdlPlayer?.playToken(
MSDLToken.FAILURE,
- authInteractionProperties
+ authInteractionProperties,
)
} else {
vibratorHelper.performHapticFeedback(
@@ -425,7 +426,7 @@ object KeyguardRootViewBinder {
blueprintViewModel,
clockViewModel,
childViews,
- burnInParams
+ burnInParams,
)
)
@@ -464,11 +465,7 @@ object KeyguardRootViewBinder {
*/
private fun createChipbarInfo(message: String, @DrawableRes icon: Int): ChipbarInfo {
return ChipbarInfo(
- startIcon =
- TintedIcon(
- Icon.Resource(icon, null),
- ChipbarInfo.DEFAULT_ICON_TINT,
- ),
+ startIcon = TintedIcon(Icon.Resource(icon, null), ChipbarInfo.DEFAULT_ICON_TINT),
text = Text.Loaded(message),
endItem = null,
vibrationEffect = null,
@@ -499,7 +496,7 @@ object KeyguardRootViewBinder {
oldLeft: Int,
oldTop: Int,
oldRight: Int,
- oldBottom: Int
+ oldBottom: Int,
) {
// After layout, ensure the notifications are positioned correctly
childViews[nsslPlaceholderId]?.let { notificationListPlaceholder ->
@@ -515,7 +512,7 @@ object KeyguardRootViewBinder {
viewModel.onNotificationContainerBoundsChanged(
notificationListPlaceholder.top.toFloat(),
notificationListPlaceholder.bottom.toFloat(),
- animate = shouldAnimate
+ animate = shouldAnimate,
)
}
@@ -531,7 +528,7 @@ object KeyguardRootViewBinder {
Int.MAX_VALUE
} else {
view.getTop()
- }
+ },
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
index c6efcfad8da7..4cf3c4e7f6d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -25,20 +25,18 @@ import androidx.constraintlayout.widget.ConstraintLayout
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-data class TransitionData(
- val config: Config,
- val start: Long = System.currentTimeMillis(),
-)
+data class TransitionData(val config: Config, val start: Long = System.currentTimeMillis())
class KeyguardBlueprintViewModel
@Inject
constructor(
@Main private val handler: Handler,
- keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
+ private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
) {
val blueprint = keyguardBlueprintInteractor.blueprint
val blueprintId = keyguardBlueprintInteractor.blueprintId
@@ -76,6 +74,9 @@ constructor(
}
}
+ fun refreshBlueprint(type: Type = Type.NoTransition) =
+ keyguardBlueprintInteractor.refreshBlueprint(type)
+
fun updateTransitions(data: TransitionData?, mutate: MutableSet<Transition>.() -> Unit) {
runningTransitions.mutate()
@@ -95,7 +96,7 @@ constructor(
Log.w(
TAG,
"runTransition: skipping ${transition::class.simpleName}: " +
- "currentPriority=$currentPriority; config=$config"
+ "currentPriority=$currentPriority; config=$config",
)
}
apply()
@@ -106,7 +107,7 @@ constructor(
Log.i(
TAG,
"runTransition: running ${transition::class.simpleName}: " +
- "currentPriority=$currentPriority; config=$config"
+ "currentPriority=$currentPriority; config=$config",
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 73028c5cf496..36f684ee4759 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -25,10 +25,10 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
@@ -56,10 +56,9 @@ constructor(
var burnInLayer: Layer? = null
val clockSize: StateFlow<ClockSize> =
- combine(
- keyguardClockInteractor.selectedClockSize,
- keyguardClockInteractor.clockSize,
- ) { selectedSize, clockSize ->
+ combine(keyguardClockInteractor.selectedClockSize, keyguardClockInteractor.clockSize) {
+ selectedSize,
+ clockSize ->
if (selectedSize == ClockSizeSetting.SMALL) ClockSize.SMALL else clockSize
}
.stateIn(
@@ -80,10 +79,7 @@ constructor(
val currentClock = keyguardClockInteractor.currentClock
val hasCustomWeatherDataDisplay =
- combine(
- isLargeClockVisible,
- currentClock,
- ) { isLargeClock, currentClock ->
+ combine(isLargeClockVisible, currentClock) { isLargeClock, currentClock ->
currentClock?.let { clock ->
val face = if (isLargeClock) clock.largeClock else clock.smallClock
face.config.hasCustomWeatherDataDisplay
@@ -93,14 +89,14 @@ constructor(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue =
- currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false
+ currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false,
)
val clockShouldBeCentered: StateFlow<Boolean> =
keyguardClockInteractor.clockShouldBeCentered.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false
+ initialValue = false,
)
// To translate elements below smartspace in weather clock to avoid overlapping between date
@@ -111,7 +107,7 @@ constructor(
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false
+ initialValue = false,
)
val currentClockLayout: StateFlow<ClockLayout> =
@@ -145,7 +141,7 @@ constructor(
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = ClockLayout.SMALL_CLOCK
+ initialValue = ClockLayout.SMALL_CLOCK,
)
val hasCustomPositionUpdatedAnimation: StateFlow<Boolean> =
@@ -156,7 +152,7 @@ constructor(
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false
+ initialValue = false,
)
/** Calculates the top margin for the small clock. */
@@ -164,10 +160,10 @@ constructor(
val statusBarHeight = systemBarUtils.getStatusBarHeaderHeightKeyguard()
return if (shadeInteractor.isShadeLayoutWide.value) {
resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) -
- if (ComposeLockscreen.isEnabled) statusBarHeight else 0
+ if (SceneContainerFlag.isEnabled) statusBarHeight else 0
} else {
resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
- if (!ComposeLockscreen.isEnabled) statusBarHeight else 0
+ if (!SceneContainerFlag.isEnabled) statusBarHeight else 0
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index ff9495daaa22..2961d05b2e51 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -57,7 +57,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
private static final float DEVICE_CONNECTED_ALPHA = 1f;
protected List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
- public MediaOutputAdapter(MediaOutputController controller) {
+ public MediaOutputAdapter(MediaSwitchingController controller) {
super(controller);
setHasStableIds(true);
}
@@ -531,8 +531,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
@RequiresApi(34)
private static class Api34Impl {
@DoNotInline
- static View.OnClickListener getClickListenerBasedOnSelectionBehavior(MediaDevice device,
- MediaOutputController controller, View.OnClickListener defaultTransferListener) {
+ static View.OnClickListener getClickListenerBasedOnSelectionBehavior(
+ MediaDevice device,
+ MediaSwitchingController controller,
+ View.OnClickListener defaultTransferListener) {
switch (device.getSelectionBehavior()) {
case SELECTION_BEHAVIOR_NONE:
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 5958b0a24a5e..63a7e013022a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -63,7 +63,7 @@ public abstract class MediaOutputBaseAdapter extends
static final int CUSTOMIZED_ITEM_GROUP = 2;
static final int CUSTOMIZED_ITEM_DYNAMIC_GROUP = 3;
- protected final MediaOutputController mController;
+ protected final MediaSwitchingController mController;
private static final int UNMUTE_DEFAULT_VOLUME = 2;
@@ -73,7 +73,7 @@ public abstract class MediaOutputBaseAdapter extends
int mCurrentActivePosition;
private boolean mIsInitVolumeFirstTime;
- public MediaOutputBaseAdapter(MediaOutputController controller) {
+ public MediaOutputBaseAdapter(MediaSwitchingController controller) {
mController = controller;
mIsDragging = false;
mCurrentActivePosition = -1;
@@ -127,7 +127,7 @@ public abstract class MediaOutputBaseAdapter extends
return mCurrentActivePosition;
}
- public MediaOutputController getController() {
+ public MediaSwitchingController getController() {
return mController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 6cc4dcbaa1ea..6bc995f3437c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -65,11 +65,9 @@ import com.android.systemui.statusbar.phone.SystemUIDialog;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
-/**
- * Base dialog for media output UI
- */
-public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
- MediaOutputController.Callback, Window.Callback {
+/** Base dialog for media output UI */
+public abstract class MediaOutputBaseDialog extends SystemUIDialog
+ implements MediaSwitchingController.Callback, Window.Callback {
private static final String TAG = "MediaOutputDialog";
private static final String EMPTY_TITLE = " ";
@@ -82,7 +80,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
private final RecyclerView.LayoutManager mLayoutManager;
final Context mContext;
- final MediaOutputController mMediaOutputController;
+ final MediaSwitchingController mMediaSwitchingController;
final BroadcastSender mBroadcastSender;
/**
@@ -212,22 +210,22 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
- mMediaOutputController.setRefreshing(false);
- mMediaOutputController.refreshDataSetIfNeeded();
+ mMediaSwitchingController.setRefreshing(false);
+ mMediaSwitchingController.refreshDataSetIfNeeded();
}
}
public MediaOutputBaseDialog(
Context context,
BroadcastSender broadcastSender,
- MediaOutputController mediaOutputController,
+ MediaSwitchingController mediaSwitchingController,
boolean includePlaybackAndAppMetadata) {
super(context, R.style.Theme_SystemUI_Dialog_Media);
// Save the context that is wrapped with our theme.
mContext = getContext();
mBroadcastSender = broadcastSender;
- mMediaOutputController = mediaOutputController;
+ mMediaSwitchingController = mediaSwitchingController;
mLayoutManager = new LayoutManagerWrapper(mContext);
mListMaxHeight = context.getResources().getDimensionPixelSize(
R.dimen.media_output_dialog_list_max_height);
@@ -279,9 +277,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
// Init bottom buttons
mDoneButton.setOnClickListener(v -> dismiss());
mStopButton.setOnClickListener(v -> onStopButtonClick());
- mAppButton.setOnClickListener(mMediaOutputController::tryToLaunchMediaApplication);
+ mAppButton.setOnClickListener(mMediaSwitchingController::tryToLaunchMediaApplication);
mMediaMetadataSectionLayout.setOnClickListener(
- mMediaOutputController::tryToLaunchMediaApplication);
+ mMediaSwitchingController::tryToLaunchMediaApplication);
mDismissing = false;
}
@@ -298,10 +296,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
@Override
public void start() {
- mMediaOutputController.start(this);
+ mMediaSwitchingController.start(this);
if (isBroadcastSupported() && !mIsLeBroadcastCallbackRegistered) {
- mMediaOutputController.registerLeBroadcastServiceCallback(mExecutor,
- mBroadcastCallback);
+ mMediaSwitchingController.registerLeBroadcastServiceCallback(
+ mExecutor, mBroadcastCallback);
mIsLeBroadcastCallbackRegistered = true;
}
}
@@ -311,11 +309,11 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
// unregister broadcast callback should only depend on profile and registered flag
// rather than remote device or broadcast state
// otherwise it might have risks of leaking registered callback handle
- if (mMediaOutputController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
- mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback);
+ if (mMediaSwitchingController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
+ mMediaSwitchingController.unregisterLeBroadcastServiceCallback(mBroadcastCallback);
mIsLeBroadcastCallbackRegistered = false;
}
- mMediaOutputController.stop();
+ mMediaSwitchingController.stop();
}
@VisibleForTesting
@@ -326,18 +324,17 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
void refresh(boolean deviceSetChanged) {
// TODO(287191450): remove binder calls in this method from the UI thread.
// If the dialog is going away or is already refreshing, do nothing.
- if (mDismissing || mMediaOutputController.isRefreshing()) {
+ if (mDismissing || mMediaSwitchingController.isRefreshing()) {
return;
}
- mMediaOutputController.setRefreshing(true);
+ mMediaSwitchingController.setRefreshing(true);
// Update header icon
final int iconRes = getHeaderIconRes();
final IconCompat headerIcon = getHeaderIcon();
final IconCompat appSourceIcon = getAppSourceIcon();
boolean colorSetUpdated = false;
mCastAppLayout.setVisibility(
- mMediaOutputController.shouldShowLaunchSection()
- ? View.VISIBLE : View.GONE);
+ mMediaSwitchingController.shouldShowLaunchSection() ? View.VISIBLE : View.GONE);
if (iconRes != 0) {
mHeaderIcon.setVisibility(View.VISIBLE);
mHeaderIcon.setImageResource(iconRes);
@@ -371,10 +368,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mAppResourceIcon.setVisibility(View.GONE);
} else if (appSourceIcon != null) {
Icon appIcon = appSourceIcon.toIcon(mContext);
- mAppResourceIcon.setColorFilter(mMediaOutputController.getColorItemContent());
+ mAppResourceIcon.setColorFilter(mMediaSwitchingController.getColorItemContent());
mAppResourceIcon.setImageIcon(appIcon);
} else {
- Drawable appIconDrawable = mMediaOutputController.getAppSourceIconFromPackage();
+ Drawable appIconDrawable = mMediaSwitchingController.getAppSourceIconFromPackage();
if (appIconDrawable != null) {
mAppResourceIcon.setImageDrawable(appIconDrawable);
} else {
@@ -387,7 +384,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
R.dimen.media_output_dialog_header_icon_padding);
mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size));
}
- mAppButton.setText(mMediaOutputController.getAppSourceName());
+ mAppButton.setText(mMediaSwitchingController.getAppSourceName());
if (!mIncludePlaybackAndAppMetadata) {
mHeaderTitle.setVisibility(View.GONE);
@@ -424,23 +421,26 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mAdapter.updateItems();
}
} else {
- mMediaOutputController.setRefreshing(false);
- mMediaOutputController.refreshDataSetIfNeeded();
+ mMediaSwitchingController.setRefreshing(false);
+ mMediaSwitchingController.refreshDataSetIfNeeded();
}
}
private void updateButtonBackgroundColorFilter() {
- ColorFilter buttonColorFilter = new PorterDuffColorFilter(
- mMediaOutputController.getColorButtonBackground(),
- PorterDuff.Mode.SRC_IN);
+ ColorFilter buttonColorFilter =
+ new PorterDuffColorFilter(
+ mMediaSwitchingController.getColorButtonBackground(),
+ PorterDuff.Mode.SRC_IN);
mDoneButton.getBackground().setColorFilter(buttonColorFilter);
mStopButton.getBackground().setColorFilter(buttonColorFilter);
- mDoneButton.setTextColor(mMediaOutputController.getColorPositiveButtonText());
+ mDoneButton.setTextColor(mMediaSwitchingController.getColorPositiveButtonText());
}
private void updateDialogBackgroundColor() {
- getDialogView().getBackground().setTint(mMediaOutputController.getColorDialogBackground());
- mDeviceListLayout.setBackgroundColor(mMediaOutputController.getColorDialogBackground());
+ getDialogView()
+ .getBackground()
+ .setTint(mMediaSwitchingController.getColorDialogBackground());
+ mDeviceListLayout.setBackgroundColor(mMediaSwitchingController.getColorDialogBackground());
}
private Drawable resizeDrawable(Drawable drawable, int size) {
@@ -499,7 +499,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
protected void startLeBroadcast() {
mStopButton.setText(R.string.media_output_broadcast_starting);
mStopButton.setEnabled(false);
- if (!mMediaOutputController.startBluetoothLeBroadcast()) {
+ if (!mMediaSwitchingController.startBluetoothLeBroadcast()) {
// If the system can't execute "broadcast start", then UI shows the error.
handleLeBroadcastStartFailed();
}
@@ -512,9 +512,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
&& sharedPref.getBoolean(PREF_IS_LE_BROADCAST_FIRST_LAUNCH, true)) {
Log.d(TAG, "PREF_IS_LE_BROADCAST_FIRST_LAUNCH: true");
- mMediaOutputController.launchLeBroadcastNotifyDialog(mDialogView,
+ mMediaSwitchingController.launchLeBroadcastNotifyDialog(
+ mDialogView,
mBroadcastSender,
- MediaOutputController.BroadcastNotifyDialog.ACTION_FIRST_LAUNCH,
+ MediaSwitchingController.BroadcastNotifyDialog.ACTION_FIRST_LAUNCH,
(d, w) -> {
startLeBroadcast();
});
@@ -527,14 +528,13 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
}
protected void startLeBroadcastDialog() {
- mMediaOutputController.launchMediaOutputBroadcastDialog(mDialogView,
- mBroadcastSender);
+ mMediaSwitchingController.launchMediaOutputBroadcastDialog(mDialogView, mBroadcastSender);
refresh();
}
protected void stopLeBroadcast() {
mStopButton.setEnabled(false);
- if (!mMediaOutputController.stopBluetoothLeBroadcast()) {
+ if (!mMediaSwitchingController.stopBluetoothLeBroadcast()) {
// If the system can't execute "broadcast stop", then UI does refresh.
mMainThreadHandler.post(() -> refresh());
}
@@ -559,7 +559,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
}
public void onStopButtonClick() {
- mMediaOutputController.releaseSession();
+ mMediaSwitchingController.releaseSession();
dismiss();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 1e317554859c..9b5b872a00db 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -235,14 +235,17 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
};
- MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar,
- BroadcastSender broadcastSender, MediaOutputController mediaOutputController) {
+ MediaOutputBroadcastDialog(
+ Context context,
+ boolean aboveStatusbar,
+ BroadcastSender broadcastSender,
+ MediaSwitchingController mediaSwitchingController) {
super(
context,
broadcastSender,
- mediaOutputController, /* includePlaybackAndAppMetadata */
+ mediaSwitchingController, /* includePlaybackAndAppMetadata */
true);
- mAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mAdapter = new MediaOutputAdapter(mMediaSwitchingController);
// TODO(b/226710953): Move the part to MediaOutputBaseDialog for every class
// that extends MediaOutputBaseDialog
if (!aboveStatusbar) {
@@ -262,8 +265,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
super.start();
if (!mIsLeBroadcastAssistantCallbackRegistered) {
mIsLeBroadcastAssistantCallbackRegistered = true;
- mMediaOutputController.registerLeBroadcastAssistantServiceCallback(mExecutor,
- mBroadcastAssistantCallback);
+ mMediaSwitchingController.registerLeBroadcastAssistantServiceCallback(
+ mExecutor, mBroadcastAssistantCallback);
}
/* Add local source broadcast to connected capable devices that may be possible receivers
* of stream.
@@ -276,7 +279,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
super.stop();
if (mIsLeBroadcastAssistantCallbackRegistered) {
mIsLeBroadcastAssistantCallbackRegistered = false;
- mMediaOutputController.unregisterLeBroadcastAssistantServiceCallback(
+ mMediaSwitchingController.unregisterLeBroadcastAssistantServiceCallback(
mBroadcastAssistantCallback);
}
}
@@ -288,7 +291,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
@Override
IconCompat getHeaderIcon() {
- return mMediaOutputController.getHeaderIcon();
+ return mMediaSwitchingController.getHeaderIcon();
}
@Override
@@ -299,17 +302,17 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
@Override
CharSequence getHeaderText() {
- return mMediaOutputController.getHeaderTitle();
+ return mMediaSwitchingController.getHeaderTitle();
}
@Override
CharSequence getHeaderSubtitle() {
- return mMediaOutputController.getHeaderSubTitle();
+ return mMediaSwitchingController.getHeaderSubTitle();
}
@Override
IconCompat getAppSourceIcon() {
- return mMediaOutputController.getNotificationSmallIcon();
+ return mMediaSwitchingController.getNotificationSmallIcon();
}
@Override
@@ -319,16 +322,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
@Override
public void onStopButtonClick() {
- mMediaOutputController.stopBluetoothLeBroadcast();
+ mMediaSwitchingController.stopBluetoothLeBroadcast();
dismiss();
}
private String getBroadcastMetadataInfo(int metadata) {
switch (metadata) {
case METADATA_BROADCAST_NAME:
- return mMediaOutputController.getBroadcastName();
+ return mMediaSwitchingController.getBroadcastName();
case METADATA_BROADCAST_CODE:
- return mMediaOutputController.getBroadcastCode();
+ return mMediaSwitchingController.getBroadcastCode();
default:
return "";
}
@@ -342,13 +345,15 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
mBroadcastQrCodeView = getDialogView().requireViewById(R.id.qrcode_view);
mBroadcastNotify = getDialogView().requireViewById(R.id.broadcast_info);
- mBroadcastNotify.setOnClickListener(v -> {
- mMediaOutputController.launchLeBroadcastNotifyDialog(
- /* view= */ null,
- /* broadcastSender= */ null,
- MediaOutputController.BroadcastNotifyDialog.ACTION_BROADCAST_INFO_ICON,
- /* onClickListener= */ null);
- });
+ mBroadcastNotify.setOnClickListener(
+ v -> {
+ mMediaSwitchingController.launchLeBroadcastNotifyDialog(
+ /* mediaOutputDialog= */ null,
+ /* broadcastSender= */ null,
+ MediaSwitchingController.BroadcastNotifyDialog
+ .ACTION_BROADCAST_INFO_ICON,
+ /* listener= */ null);
+ });
mBroadcastName = getDialogView().requireViewById(R.id.broadcast_name_summary);
mBroadcastNameEdit = getDialogView().requireViewById(R.id.broadcast_name_edit);
mBroadcastNameEdit.setOnClickListener(v -> {
@@ -409,16 +414,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
return;
}
- for (BluetoothDevice sink : mMediaOutputController.getConnectedBroadcastSinkDevices()) {
+ for (BluetoothDevice sink : mMediaSwitchingController.getConnectedBroadcastSinkDevices()) {
Log.d(TAG, "The broadcastMetadata broadcastId: " + broadcastMetadata.getBroadcastId()
+ ", the device: " + sink.getAnonymizedAddress());
- if (mMediaOutputController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) {
+ if (mMediaSwitchingController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) {
Log.d(TAG, "The sink device has the broadcast source now.");
return;
}
- if (!mMediaOutputController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(sink,
- broadcastMetadata, /*isGroupOp=*/ false)) {
+ if (!mMediaSwitchingController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(
+ sink, broadcastMetadata, /* isGroupOp= */ false)) {
Log.e(TAG, "Error: Source add failed");
}
}
@@ -457,11 +462,11 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
private String getLocalBroadcastMetadataQrCodeString() {
- return mMediaOutputController.getLocalBroadcastMetadataQrCodeString();
+ return mMediaSwitchingController.getLocalBroadcastMetadataQrCodeString();
}
private BluetoothLeBroadcastMetadata getBroadcastMetadata() {
- return mMediaOutputController.getBroadcastMetadata();
+ return mMediaSwitchingController.getBroadcastMetadata();
}
@VisibleForTesting
@@ -476,8 +481,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
* stopped then used the new Broadcast code to start the Broadcast.
*/
mIsStopbyUpdateBroadcastCode = true;
- mMediaOutputController.setBroadcastCode(updatedString);
- if (!mMediaOutputController.stopBluetoothLeBroadcast()) {
+ mMediaSwitchingController.setBroadcastCode(updatedString);
+ if (!mMediaSwitchingController.stopBluetoothLeBroadcast()) {
handleLeBroadcastStopFailed();
return;
}
@@ -485,8 +490,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
/* If the user wants to update the Broadcast Name, we don't need to stop the Broadcast
* session. Only use the new Broadcast name to update the broadcast session.
*/
- mMediaOutputController.setBroadcastName(updatedString);
- if (!mMediaOutputController.updateBluetoothLeBroadcast()) {
+ mMediaSwitchingController.setBroadcastName(updatedString);
+ if (!mMediaSwitchingController.updateBluetoothLeBroadcast()) {
handleLeBroadcastUpdateFailed();
}
}
@@ -496,12 +501,13 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
public boolean isBroadcastSupported() {
if (!legacyLeAudioSharing()) return false;
boolean isBluetoothLeDevice = false;
- if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
- isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
- mMediaOutputController.getCurrentConnectedMediaDevice());
+ if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) {
+ isBluetoothLeDevice =
+ mMediaSwitchingController.isBluetoothLeDevice(
+ mMediaSwitchingController.getCurrentConnectedMediaDevice());
}
- return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice;
+ return mMediaSwitchingController.isBroadcastSupported() && isBluetoothLeDevice;
}
@Override
@@ -515,7 +521,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
@Override
public void handleLeBroadcastStartFailed() {
- mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode);
+ mMediaSwitchingController.setBroadcastCode(mCurrentBroadcastCode);
mRetryCount++;
handleUpdateFailedUi();
@@ -538,8 +544,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
@Override
public void handleLeBroadcastUpdateFailed() {
- //Change the value in shared preferences back to it original value
- mMediaOutputController.setBroadcastName(mCurrentBroadcastName);
+ // Change the value in shared preferences back to it original value
+ mMediaSwitchingController.setBroadcastName(mCurrentBroadcastName);
mRetryCount++;
handleUpdateFailedUi();
@@ -550,7 +556,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
if (mIsStopbyUpdateBroadcastCode) {
mIsStopbyUpdateBroadcastCode = false;
mRetryCount = 0;
- if (!mMediaOutputController.startBluetoothLeBroadcast()) {
+ if (!mMediaSwitchingController.startBluetoothLeBroadcast()) {
handleLeBroadcastStartFailed();
return;
}
@@ -561,8 +567,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
@Override
public void handleLeBroadcastStopFailed() {
- //Change the value in shared preferences back to it original value
- mMediaOutputController.setBroadcastCode(mCurrentBroadcastCode);
+ // Change the value in shared preferences back to it original value
+ mMediaSwitchingController.setBroadcastCode(mCurrentBroadcastCode);
mRetryCount++;
handleUpdateFailedUi();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
index 6ef9ea36882b..2e7e66f5b384 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -29,7 +29,7 @@ constructor(
private val context: Context,
private val broadcastSender: BroadcastSender,
private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val mediaOutputControllerFactory: MediaOutputController.Factory
+ private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory
) {
var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
@@ -41,7 +41,7 @@ constructor(
// TODO: b/321969740 - Populate the userHandle parameter. The user handle is necessary to
// disambiguate the same package running on different users.
val controller =
- mediaOutputControllerFactory.create(
+ mediaSwitchingControllerFactory.create(
packageName,
/* userHandle= */ null,
/* token */ null,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index eb6a32023eb7..c9af7b322811 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -46,14 +46,14 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
Context context,
boolean aboveStatusbar,
BroadcastSender broadcastSender,
- MediaOutputController mediaOutputController,
+ MediaSwitchingController mediaSwitchingController,
DialogTransitionAnimator dialogTransitionAnimator,
UiEventLogger uiEventLogger,
boolean includePlaybackAndAppMetadata) {
- super(context, broadcastSender, mediaOutputController, includePlaybackAndAppMetadata);
+ super(context, broadcastSender, mediaSwitchingController, includePlaybackAndAppMetadata);
mDialogTransitionAnimator = dialogTransitionAnimator;
mUiEventLogger = uiEventLogger;
- mAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mAdapter = new MediaOutputAdapter(mMediaSwitchingController);
if (!aboveStatusbar) {
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
}
@@ -72,7 +72,7 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
@Override
IconCompat getHeaderIcon() {
- return mMediaOutputController.getHeaderIcon();
+ return mMediaSwitchingController.getHeaderIcon();
}
@Override
@@ -83,27 +83,29 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
@Override
CharSequence getHeaderText() {
- return mMediaOutputController.getHeaderTitle();
+ return mMediaSwitchingController.getHeaderTitle();
}
@Override
CharSequence getHeaderSubtitle() {
- return mMediaOutputController.getHeaderSubTitle();
+ return mMediaSwitchingController.getHeaderSubTitle();
}
@Override
IconCompat getAppSourceIcon() {
- return mMediaOutputController.getNotificationSmallIcon();
+ return mMediaSwitchingController.getNotificationSmallIcon();
}
@Override
int getStopButtonVisibility() {
boolean isActiveRemoteDevice = false;
- if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
- isActiveRemoteDevice = mMediaOutputController.isActiveRemoteDevice(
- mMediaOutputController.getCurrentConnectedMediaDevice());
+ if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) {
+ isActiveRemoteDevice =
+ mMediaSwitchingController.isActiveRemoteDevice(
+ mMediaSwitchingController.getCurrentConnectedMediaDevice());
}
- boolean showBroadcastButton = isBroadcastSupported() && mMediaOutputController.isPlaying();
+ boolean showBroadcastButton =
+ isBroadcastSupported() && mMediaSwitchingController.isPlaying();
return (isActiveRemoteDevice || showBroadcastButton) ? View.VISIBLE : View.GONE;
}
@@ -115,13 +117,14 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
boolean isBroadcastEnabled = false;
if (FeatureFlagUtils.isEnabled(mContext,
FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)) {
- if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
- isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
- mMediaOutputController.getCurrentConnectedMediaDevice());
+ if (mMediaSwitchingController.getCurrentConnectedMediaDevice() != null) {
+ isBluetoothLeDevice =
+ mMediaSwitchingController.isBluetoothLeDevice(
+ mMediaSwitchingController.getCurrentConnectedMediaDevice());
// if broadcast is active, broadcast should be considered as supported
// there could be a valid case that broadcast is ongoing
// without active LEA device connected
- isBroadcastEnabled = mMediaOutputController.isBluetoothLeBroadcastEnabled();
+ isBroadcastEnabled = mMediaSwitchingController.isBluetoothLeBroadcastEnabled();
}
} else {
// To decouple LE Audio Broadcast and Unicast, it always displays the button when there
@@ -129,15 +132,16 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
isBluetoothLeDevice = true;
}
- return mMediaOutputController.isBroadcastSupported()
+ return mMediaSwitchingController.isBroadcastSupported()
&& (isBluetoothLeDevice || isBroadcastEnabled);
}
@Override
public CharSequence getStopButtonText() {
int resId = R.string.media_output_dialog_button_stop_casting;
- if (isBroadcastSupported() && mMediaOutputController.isPlaying()
- && !mMediaOutputController.isBluetoothLeBroadcastEnabled()) {
+ if (isBroadcastSupported()
+ && mMediaSwitchingController.isPlaying()
+ && !mMediaSwitchingController.isBluetoothLeBroadcastEnabled()) {
resId = R.string.media_output_broadcast;
}
return mContext.getText(resId);
@@ -145,8 +149,8 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
@Override
public void onStopButtonClick() {
- if (isBroadcastSupported() && mMediaOutputController.isPlaying()) {
- if (!mMediaOutputController.isBluetoothLeBroadcastEnabled()) {
+ if (isBroadcastSupported() && mMediaSwitchingController.isPlaying()) {
+ if (!mMediaSwitchingController.isBluetoothLeBroadcastEnabled()) {
if (startLeBroadcastDialogForFirstTime()) {
return;
}
@@ -155,7 +159,7 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
stopLeBroadcast();
}
} else {
- mMediaOutputController.releaseSession();
+ mMediaSwitchingController.releaseSession();
mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
dismiss();
}
@@ -163,8 +167,9 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
@Override
public int getBroadcastIconVisibility() {
- return (isBroadcastSupported() && mMediaOutputController.isBluetoothLeBroadcastEnabled())
- ? View.VISIBLE : View.GONE;
+ return (isBroadcastSupported() && mMediaSwitchingController.isBluetoothLeBroadcastEnabled())
+ ? View.VISIBLE
+ : View.GONE;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index 47e069102035..4e9451a838ad 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -35,7 +35,7 @@ constructor(
private val broadcastSender: BroadcastSender,
private val uiEventLogger: UiEventLogger,
private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val mediaOutputControllerFactory: MediaOutputController.Factory,
+ private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory,
) {
companion object {
const val INTERACTION_JANK_TAG = "media_output"
@@ -118,7 +118,7 @@ constructor(
// Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
- val controller = mediaOutputControllerFactory.create(packageName, userHandle, token)
+ val controller = mediaSwitchingControllerFactory.create(packageName, userHandle, token)
val mediaOutputDialog =
MediaOutputDialog(
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
index 875e505db1c6..2cbc75755cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -77,6 +77,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager;
+import com.android.settingslib.media.InputRouteManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.flags.Flags;
@@ -116,12 +117,13 @@ import java.util.function.Function;
import java.util.stream.Collectors;
/**
- * Controller for media output dialog
+ * Controller for a dialog that allows users to switch media output and input devices, control
+ * volume, connect to new devices, etc.
*/
-public class MediaOutputController implements LocalMediaManager.DeviceCallback,
- INearbyMediaDevicesUpdateCallback {
+public class MediaSwitchingController
+ implements LocalMediaManager.DeviceCallback, INearbyMediaDevicesUpdateCallback {
- private static final String TAG = "MediaOutputController";
+ private static final String TAG = "MediaSwitchingController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String PAGE_CONNECTED_DEVICES_KEY =
"top_level_connected_devices";
@@ -137,10 +139,12 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
private final DialogTransitionAnimator mDialogTransitionAnimator;
private final CommonNotifCollection mNotifCollection;
protected final Object mMediaDevicesLock = new Object();
+ protected final Object mInputMediaDevicesLock = new Object();
@VisibleForTesting
final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
- private final List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
+ private final List<MediaItem> mOutputMediaItemList = new CopyOnWriteArrayList<>();
+ private final List<MediaItem> mInputMediaItemList = new CopyOnWriteArrayList<>();
private final AudioManager mAudioManager;
private final PowerExemptionManager mPowerExemptionManager;
private final KeyguardManager mKeyGuardManager;
@@ -153,6 +157,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
@VisibleForTesting
boolean mNeedRefresh = false;
private MediaController mMediaController;
+ private InputRouteManager mInputRouteManager;
@VisibleForTesting
Callback mCallback;
@VisibleForTesting
@@ -181,8 +186,20 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
ACTION_BROADCAST_INFO_ICON
}
+ @VisibleForTesting
+ final InputRouteManager.InputDeviceCallback mInputDeviceCallback =
+ new InputRouteManager.InputDeviceCallback() {
+ @Override
+ public void onInputDeviceListUpdated(@NonNull List<MediaDevice> devices) {
+ synchronized (mInputMediaDevicesLock) {
+ buildInputMediaItems(devices);
+ mCallback.onDeviceListChanged();
+ }
+ }
+ };
+
@AssistedInject
- public MediaOutputController(
+ public MediaSwitchingController(
Context context,
@Assisted String packageName,
@Assisted @Nullable UserHandle userHandle,
@@ -241,19 +258,23 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
R.dimen.media_output_dialog_default_margin_end);
mItemMarginEndSelectable = (int) mContext.getResources().getDimension(
R.dimen.media_output_dialog_selectable_margin_end);
+
+ if (enableInputRouting()) {
+ mInputRouteManager = new InputRouteManager(mContext, audioManager);
+ }
}
@AssistedFactory
public interface Factory {
- /** Construct a MediaOutputController */
- MediaOutputController create(
+ /** Construct a MediaSwitchingController */
+ MediaSwitchingController create(
String packageName, UserHandle userHandle, MediaSession.Token token);
}
protected void start(@NonNull Callback cb) {
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
- mMediaItemList.clear();
+ mOutputMediaItemList.clear();
}
mNearbyDeviceInfoMap.clear();
if (mNearbyMediaDevicesManager != null) {
@@ -277,6 +298,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
mCallback = cb;
mLocalMediaManager.registerCallback(this);
mLocalMediaManager.startScan();
+
+ if (enableInputRouting()) {
+ mInputRouteManager.registerCallback(mInputDeviceCallback);
+ }
}
boolean shouldShowLaunchSection() {
@@ -300,12 +325,19 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
mLocalMediaManager.stopScan();
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
- mMediaItemList.clear();
+ mOutputMediaItemList.clear();
}
if (mNearbyMediaDevicesManager != null) {
mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this);
}
mNearbyDeviceInfoMap.clear();
+
+ if (enableInputRouting()) {
+ mInputRouteManager.unregisterCallback(mInputDeviceCallback);
+ synchronized (mInputMediaDevicesLock) {
+ mInputMediaItemList.clear();
+ }
+ }
}
private MediaController getMediaController() {
@@ -335,7 +367,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
@Override
public void onDeviceListUpdate(List<MediaDevice> devices) {
- boolean isListEmpty = mMediaItemList.isEmpty();
+ boolean isListEmpty = mOutputMediaItemList.isEmpty();
if (isListEmpty || !mIsRefreshing) {
buildMediaItems(devices);
mCallback.onDeviceListChanged();
@@ -352,7 +384,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
public void onSelectedDeviceStateChanged(MediaDevice device,
@LocalMediaManager.MediaDeviceState int state) {
mCallback.onRouteChanged();
- mMetricLogger.logOutputItemSuccess(device.toString(), new ArrayList<>(mMediaItemList));
+ mMetricLogger.logOutputItemSuccess(
+ device.toString(), new ArrayList<>(mOutputMediaItemList));
}
@Override
@@ -363,7 +396,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
@Override
public void onRequestFailed(int reason) {
mCallback.onRouteChanged();
- mMetricLogger.logOutputItemFailure(new ArrayList<>(mMediaItemList), reason);
+ mMetricLogger.logOutputItemFailure(new ArrayList<>(mOutputMediaItemList), reason);
}
/**
@@ -382,7 +415,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
}
try {
synchronized (mMediaDevicesLock) {
- mMediaItemList.removeIf((MediaItem::isMutingExpectedDevice));
+ mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice));
}
mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice());
} catch (Exception e) {
@@ -638,9 +671,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
private void buildMediaItems(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
- List<MediaItem> updatedMediaItems = buildMediaItems(mMediaItemList, devices);
- mMediaItemList.clear();
- mMediaItemList.addAll(updatedMediaItems);
+ List<MediaItem> updatedMediaItems = buildMediaItems(mOutputMediaItemList, devices);
+ mOutputMediaItemList.clear();
+ mOutputMediaItemList.addAll(updatedMediaItems);
}
}
@@ -714,6 +747,19 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
}
}
+ private boolean enableInputRouting() {
+ return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl();
+ }
+
+ private void buildInputMediaItems(List<MediaDevice> devices) {
+ synchronized (mInputMediaDevicesLock) {
+ List<MediaItem> updatedInputMediaItems =
+ devices.stream().map(MediaItem::createDeviceMediaItem).toList();
+ mInputMediaItemList.clear();
+ mInputMediaItemList.addAll(updatedInputMediaItems);
+ }
+ }
+
/**
* Initial categorization of current devices, will not be called for updates to the devices
* list.
@@ -778,7 +824,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
mediaDevice.setRangeZone(mNearbyDeviceInfoMap.get(mediaDevice.getId()));
}
}
-
}
boolean isCurrentConnectedDeviceRemote() {
@@ -837,8 +882,31 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
});
}
+ private void addInputDevices(List<MediaItem> mediaItems) {
+ mediaItems.add(
+ MediaItem.createGroupDividerMediaItem(
+ mContext.getString(R.string.media_input_group_title)));
+ mediaItems.addAll(mInputMediaItemList);
+ }
+
+ private void addOutputDevices(List<MediaItem> mediaItems) {
+ mediaItems.add(
+ MediaItem.createGroupDividerMediaItem(
+ mContext.getString(R.string.media_output_group_title)));
+ mediaItems.addAll(mOutputMediaItemList);
+ }
+
public List<MediaItem> getMediaItemList() {
- return mMediaItemList;
+ // If input routing is not enabled, only return output media items.
+ if (!enableInputRouting()) {
+ return mOutputMediaItemList;
+ }
+
+ // If input routing is enabled, return both output and input media items.
+ List<MediaItem> mediaItems = new ArrayList<>();
+ addOutputDevices(mediaItems);
+ addInputDevices(mediaItems);
+ return mediaItems;
}
public MediaDevice getCurrentConnectedMediaDevice() {
@@ -921,7 +989,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
public boolean isAnyDeviceTransferring() {
synchronized (mMediaDevicesLock) {
- for (MediaItem mediaItem : mMediaItemList) {
+ for (MediaItem mediaItem : mOutputMediaItemList) {
if (mediaItem.getMediaDevice().isPresent()
&& mediaItem.getMediaDevice().get().getState()
== LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
@@ -986,8 +1054,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
}
void launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender) {
- MediaOutputController controller =
- new MediaOutputController(
+ MediaSwitchingController controller =
+ new MediaSwitchingController(
mContext,
mPackageName,
mUserHandle,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index d59658947771..4251b81226b3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -53,7 +53,6 @@ import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Log;
import android.view.Window;
-import android.view.WindowManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -309,9 +308,6 @@ public class MediaProjectionPermissionActivity extends Activity {
private void setUpDialog(AlertDialog dialog) {
SystemUIDialog.registerDismissListener(dialog);
SystemUIDialog.applyFlags(dialog, /* showWhenLocked= */ false);
-
- final Window w = dialog.getWindow();
- w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
SystemUIDialog.setDialogSize(dialog);
dialog.setOnCancelListener(this::onDialogDismissedOrCancelled);
@@ -319,6 +315,7 @@ public class MediaProjectionPermissionActivity extends Activity {
dialog.create();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+ final Window w = dialog.getWindow();
w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 072d322d69a6..1fe54e46fee1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -23,17 +23,14 @@ import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositor
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositoryImpl
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl
-import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor
-import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor
-import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.panels.ui.compose.GridLayout
-import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModelImpl
import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModel
@@ -56,11 +53,6 @@ interface PanelsModule {
@Binds
fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository
- @Binds
- fun bindDefaultGridConsistencyInteractor(
- impl: NoopGridConsistencyInteractor
- ): GridTypeConsistencyInteractor
-
@Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel
@Binds fun bindGridSizeViewModel(impl: FixedColumnsSizeViewModelImpl): FixedColumnsSizeViewModel
@@ -74,12 +66,6 @@ interface PanelsModule {
@PaginatedBaseLayoutType
fun bindPaginatedBaseGridLayout(impl: InfiniteGridLayout): PaginatableGridLayout
- @Binds
- @PaginatedBaseLayoutType
- fun bindPaginatedBaseConsistencyInteractor(
- impl: NoopGridConsistencyInteractor
- ): GridTypeConsistencyInteractor
-
@Binds @Named("Default") fun bindDefaultGridLayout(impl: PaginatedGridLayout): GridLayout
companion object {
@@ -117,28 +103,5 @@ interface PanelsModule {
): Set<GridLayoutType> {
return entries.map { it.first }.toSet()
}
-
- @Provides
- @IntoSet
- fun provideGridConsistencyInteractor(
- consistencyInteractor: InfiniteGridConsistencyInteractor
- ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
- return Pair(InfiniteGridLayoutType, consistencyInteractor)
- }
-
- @Provides
- @IntoSet
- fun providePaginatedGridConsistencyInteractor(
- @PaginatedBaseLayoutType consistencyInteractor: GridTypeConsistencyInteractor,
- ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
- return Pair(PaginatedGridLayoutType, consistencyInteractor)
- }
-
- @Provides
- fun provideGridConsistencyInteractorMap(
- entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>>
- ): Map<GridLayoutType, GridTypeConsistencyInteractor> {
- return entries.toMap()
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
deleted file mode 100644
index a2e7ea6fe797..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2024 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.qs.panels.domain.interactor
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.qs.panels.shared.model.GridLayoutType
-import com.android.systemui.qs.panels.shared.model.PanelsLog
-import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class GridConsistencyInteractor
-@Inject
-constructor(
- private val gridLayoutTypeInteractor: GridLayoutTypeInteractor,
- private val currentTilesInteractor: CurrentTilesInteractor,
- private val consistencyInteractors:
- Map<GridLayoutType, @JvmSuppressWildcards GridTypeConsistencyInteractor>,
- private val defaultConsistencyInteractor: GridTypeConsistencyInteractor,
- @PanelsLog private val logBuffer: LogBuffer,
- @Application private val applicationScope: CoroutineScope,
-) {
- fun start() {
- applicationScope.launch {
- gridLayoutTypeInteractor.layout.collectLatest { type ->
- val consistencyInteractor =
- consistencyInteractors[type] ?: defaultConsistencyInteractor
- currentTilesInteractor.currentTiles
- .map { tiles -> tiles.map { it.spec } }
- .collectLatest { tiles ->
- val newTiles = consistencyInteractor.reconcileTiles(tiles)
- if (newTiles != tiles) {
- currentTilesInteractor.setTiles(newTiles)
- logChange(newTiles)
- }
- }
- }
- }
- }
-
- private fun logChange(tiles: List<TileSpec>) {
- logBuffer.log(
- LOG_BUFFER_CURRENT_TILES_CHANGE_TAG,
- LogLevel.DEBUG,
- { str1 = tiles.toString() },
- { "Tiles reordered: $str1" }
- )
- }
-
- private companion object {
- const val LOG_BUFFER_CURRENT_TILES_CHANGE_TAG = "GridConsistencyTilesChange"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
deleted file mode 100644
index 4cdabaedc49e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2024 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.qs.panels.domain.interactor
-
-import com.android.systemui.qs.pipeline.shared.TileSpec
-
-interface GridTypeConsistencyInteractor {
- /**
- * Given a list of tiles, return the best list of the same tiles (preserving as much order as
- * possible, such that it's consistent with the current layout.
- */
- fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec>
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
deleted file mode 100644
index 874b3b0a4636..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2024 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.qs.panels.domain.interactor
-
-import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.SizedTileImpl
-import com.android.systemui.qs.panels.shared.model.TileRow
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import javax.inject.Inject
-
-@SysUISingleton
-class InfiniteGridConsistencyInteractor
-@Inject
-constructor(
- private val iconTilesInteractor: IconTilesInteractor,
- private val gridSizeInteractor: FixedColumnsSizeInteractor
-) : GridTypeConsistencyInteractor {
-
- /**
- * Tries to fill in every columns of all rows (except the last row), potentially reordering
- * tiles.
- */
- override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
- val newTiles: MutableList<TileSpec> = mutableListOf()
- val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
- val tilesQueue: ArrayDeque<SizedTile<TileSpec>> =
- ArrayDeque(
- tiles.map {
- SizedTileImpl(
- it,
- if (iconTilesInteractor.isIconTile(it)) 1 else 2,
- )
- }
- )
-
- while (tilesQueue.isNotEmpty()) {
- if (row.isFull()) {
- newTiles.addAll(row.tiles.map { it.tile })
- row.clear()
- }
-
- val tile = tilesQueue.removeFirst()
-
- // If the tile fits in the row, add it.
- if (!row.maybeAddTile(tile)) {
- // If the tile does not fit the row, find an icon tile to move.
- // We'll try to either add an icon tile from the queue to complete the row, or
- // remove an icon tile from the current row to free up space.
-
- val iconTile: SizedTile<TileSpec>? = tilesQueue.firstOrNull { it.width == 1 }
- if (iconTile != null) {
- tilesQueue.remove(iconTile)
- tilesQueue.addFirst(tile)
- row.maybeAddTile(iconTile)
- } else {
- val tileToRemove: SizedTile<TileSpec>? = row.findLastIconTile()
- if (tileToRemove != null) {
- row.removeTile(tileToRemove)
- row.maybeAddTile(tile)
-
- // Moving the icon tile to the end because there's no other
- // icon tiles in the queue.
- tilesQueue.addLast(tileToRemove)
- } else {
- // If the row does not have an icon tile, add the incomplete row.
- // Note: this shouldn't happen because an icon tile is guaranteed to be in a
- // row that doesn't have enough space for a large tile.
- val tileSpecs = row.tiles.map { it.tile }
- Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs")
- newTiles.addAll(tileSpecs)
- row.clear()
- tilesQueue.addFirst(tile)
- }
- }
- }
- }
-
- // Add last row that might be incomplete
- newTiles.addAll(row.tiles.map { it.tile })
-
- return newTiles.toList()
- }
-
- private companion object {
- const val TAG = "InfiniteGridConsistencyInteractor"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
deleted file mode 100644
index 0386a6ab20d6..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2024 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.qs.panels.domain.interactor
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import javax.inject.Inject
-
-/** [GridTypeConsistencyInteractor] implementation that doesn't do any changes to tiles. */
-@SysUISingleton
-class NoopGridConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor {
- override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 9a2315be29a2..1f8a24a1da67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -161,9 +161,9 @@ private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean {
@Composable
fun Modifier.dragAndDropTileSource(
sizedTile: SizedTile<EditTileViewModel>,
+ dragAndDropState: DragAndDropState,
onTap: (TileSpec) -> Unit,
- onDoubleTap: (TileSpec) -> Unit,
- dragAndDropState: DragAndDropState
+ onDoubleTap: (TileSpec) -> Unit = {},
): Modifier {
val state by rememberUpdatedState(dragAndDropState)
return dragAndDropSource {
@@ -181,11 +181,11 @@ fun Modifier.dragAndDropTileSource(
ClipData(
QsDragAndDrop.CLIPDATA_LABEL,
arrayOf(QsDragAndDrop.TILESPEC_MIME_TYPE),
- ClipData.Item(sizedTile.tile.tileSpec.spec)
+ ClipData.Item(sizedTile.tile.tileSpec.spec),
)
)
)
- }
+ },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index fde40da2e5bc..f4acbec1063c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -25,6 +25,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.util.fastMap
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.Tile
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.TileLazyGrid
import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
@Composable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
deleted file mode 100644
index afd47a7f7758..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ /dev/null
@@ -1,926 +0,0 @@
-/*
- * Copyright (C) 2024 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(ExperimentalFoundationApi::class)
-
-package com.android.systemui.qs.panels.ui.compose
-
-import android.content.res.Resources
-import android.graphics.drawable.Animatable
-import android.service.quicksettings.Tile.STATE_ACTIVE
-import android.service.quicksettings.Tile.STATE_INACTIVE
-import android.text.TextUtils
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.core.animateDpAsState
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
-import androidx.compose.animation.graphics.res.animatedVectorResource
-import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
-import androidx.compose.animation.graphics.vector.AnimatedImageVector
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.LocalOverscrollConfiguration
-import androidx.compose.foundation.background
-import androidx.compose.foundation.basicMarquee
-import androidx.compose.foundation.border
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Arrangement.spacedBy
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.LazyGridItemScope
-import androidx.compose.foundation.lazy.grid.LazyGridScope
-import androidx.compose.foundation.lazy.grid.LazyGridState
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.rememberLazyGridState
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Clear
-import androidx.compose.material3.Icon
-import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.layout.positionInRoot
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.clearAndSetSemantics
-import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.onClick
-import androidx.compose.ui.semantics.role
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.stateDescription
-import androidx.compose.ui.semantics.toggleableState
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.compose.ui.util.fastMap
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.animation.Expandable
-import com.android.compose.modifiers.background
-import com.android.compose.modifiers.thenIf
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.common.ui.compose.load
-import com.android.systemui.compose.modifiers.sysuiResTag
-import com.android.systemui.plugins.qs.QSTile
-import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.SizedTileImpl
-import com.android.systemui.qs.panels.ui.compose.TileDefaults.longPressLabel
-import com.android.systemui.qs.panels.ui.model.GridCell
-import com.android.systemui.qs.panels.ui.model.SpacerGridCell
-import com.android.systemui.qs.panels.ui.model.TileGridCell
-import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState
-import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
-import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.toUiState
-import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.shared.model.groupAndSort
-import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.res.R
-import java.util.function.Supplier
-import kotlinx.coroutines.delay
-
-object TileType
-
-private const val TEST_TAG_SMALL = "qs_tile_small"
-private const val TEST_TAG_LARGE = "qs_tile_large"
-private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target"
-
-@Composable
-fun Tile(tile: TileViewModel, iconOnly: Boolean, showLabels: Boolean = false, modifier: Modifier) {
- val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
- val resources = resources()
- val uiState = remember(state, resources) { state.toUiState(resources) }
- val colors = TileDefaults.getColorForState(uiState)
-
- // TODO(b/361789146): Draw the shapes instead of clipping
- val tileShape = TileDefaults.animateTileShape(uiState.state)
-
- TileContainer(
- colors = colors,
- showLabels = showLabels,
- label = uiState.label,
- iconOnly = iconOnly,
- shape = tileShape,
- clickEnabled = true,
- onClick = tile::onClick,
- onLongClick = tile::onLongClick,
- modifier = modifier.height(tileHeight()),
- uiState = uiState,
- ) {
- val icon = getTileIcon(icon = uiState.icon)
- if (iconOnly) {
- TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center))
- } else {
- val iconShape = TileDefaults.animateIconShape(uiState.state)
- LargeTileContent(
- label = uiState.label,
- secondaryLabel = uiState.secondaryLabel,
- icon = icon,
- colors = colors,
- iconShape = iconShape,
- toggleClickSupported = state.handlesSecondaryClick,
- onClick = {
- if (state.handlesSecondaryClick) {
- tile.onSecondaryClick()
- }
- },
- onLongClick = { tile.onLongClick(it) },
- accessibilityUiState = uiState.accessibilityUiState,
- )
- }
- }
-}
-
-@Composable
-private fun TileContainer(
- colors: TileColors,
- showLabels: Boolean,
- label: String,
- iconOnly: Boolean,
- shape: Shape,
- clickEnabled: Boolean = false,
- onClick: (Expandable) -> Unit = {},
- onLongClick: (Expandable) -> Unit = {},
- modifier: Modifier = Modifier,
- uiState: TileUiState? = null,
- content: @Composable BoxScope.(Expandable) -> Unit,
-) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement =
- spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin), Alignment.Top),
- modifier = modifier,
- ) {
- val backgroundColor =
- if (iconOnly || uiState?.handlesSecondaryClick != true) {
- colors.iconBackground
- } else {
- colors.background
- }
- Expandable(
- color = backgroundColor,
- shape = shape,
- modifier = Modifier.height(tileHeight()).clip(shape),
- ) {
- val longPressLabel = longPressLabel()
- Box(
- modifier =
- Modifier.fillMaxSize()
- .thenIf(clickEnabled) {
- Modifier.combinedClickable(
- onClick = { onClick(it) },
- onLongClick = { onLongClick(it) },
- onClickLabel = uiState?.accessibilityUiState?.clickLabel,
- onLongClickLabel = longPressLabel,
- )
- }
- .thenIf(uiState != null) {
- uiState as TileUiState
- Modifier.semantics {
- role = uiState.accessibilityUiState.accessibilityRole
- if (
- uiState.accessibilityUiState.accessibilityRole ==
- Role.Switch
- ) {
- uiState.accessibilityUiState.toggleableState?.let {
- toggleableState = it
- }
- }
- stateDescription = uiState.accessibilityUiState.stateDescription
- }
- .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE)
- .thenIf(iconOnly) {
- Modifier.semantics {
- contentDescription =
- uiState.accessibilityUiState.contentDescription
- }
- }
- }
- .tilePadding()
- ) {
- content(it)
- }
- }
-
- if (showLabels && iconOnly) {
- Text(
- label,
- maxLines = 2,
- color = colors.label,
- overflow = TextOverflow.Ellipsis,
- textAlign = TextAlign.Center,
- )
- }
- }
-}
-
-@Composable
-private fun LargeTileContent(
- label: String,
- secondaryLabel: String?,
- icon: Icon,
- colors: TileColors,
- iconShape: Shape,
- accessibilityUiState: AccessibilityUiState? = null,
- toggleClickSupported: Boolean = false,
- onClick: () -> Unit = {},
- onLongClick: () -> Unit = {},
-) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(),
- ) {
- // Icon
- val longPressLabel = longPressLabel()
- Box(
- modifier =
- Modifier.size(TileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
- Modifier.clip(iconShape)
- .background(colors.iconBackground, { 1f })
- .combinedClickable(
- onClick = onClick,
- onLongClick = onLongClick,
- onLongClickLabel = longPressLabel,
- )
- .thenIf(accessibilityUiState != null) {
- accessibilityUiState as AccessibilityUiState
- Modifier.semantics {
- contentDescription = accessibilityUiState.contentDescription
- stateDescription = accessibilityUiState.stateDescription
- accessibilityUiState.toggleableState?.let {
- toggleableState = it
- }
- role = Role.Switch
- }
- .sysuiResTag(TEST_TAG_TOGGLE)
- }
- }
- ) {
- TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center))
- }
-
- // Labels
- Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
- Text(label, color = colors.label, modifier = Modifier.tileMarquee())
- if (!TextUtils.isEmpty(secondaryLabel)) {
- Text(
- secondaryLabel ?: "",
- color = colors.secondaryLabel,
- modifier =
- Modifier.tileMarquee().thenIf(
- accessibilityUiState
- ?.stateDescription
- ?.contains(secondaryLabel ?: "") == true
- ) {
- Modifier.clearAndSetSemantics {}
- },
- )
- }
- }
- }
-}
-
-private fun Modifier.tileMarquee(): Modifier {
- return basicMarquee(iterations = 1, initialDelayMillis = 200)
-}
-
-@Composable
-fun TileLazyGrid(
- modifier: Modifier = Modifier,
- state: LazyGridState = rememberLazyGridState(),
- columns: GridCells,
- content: LazyGridScope.() -> Unit,
-) {
- LazyVerticalGrid(
- state = state,
- columns = columns,
- verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
- horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
- modifier = modifier,
- content = content,
- )
-}
-
-@Composable
-fun DefaultEditTileGrid(
- currentListState: EditTileListState,
- otherTiles: List<SizedTile<EditTileViewModel>>,
- columns: Int,
- modifier: Modifier,
- onAddTile: (TileSpec, Int) -> Unit,
- onRemoveTile: (TileSpec) -> Unit,
- onSetTiles: (List<TileSpec>) -> Unit,
- onResize: (TileSpec) -> Unit,
-) {
- val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
- onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
- }
- val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
-
- CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
- Column(
- verticalArrangement =
- spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
- modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()),
- ) {
- AnimatedContent(
- targetState = currentListState.dragInProgress,
- modifier = Modifier.wrapContentSize(),
- ) { dragIsInProgress ->
- EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) {
- if (dragIsInProgress) {
- RemoveTileTarget()
- } else {
- Text(text = "Hold and drag to rearrange tiles.")
- }
- }
- }
-
- CurrentTilesGrid(
- currentListState,
- columns,
- tilePadding,
- onRemoveTile,
- onResize,
- onSetTiles,
- )
-
- // Hide available tiles when dragging
- AnimatedVisibility(
- visible = !currentListState.dragInProgress,
- enter = fadeIn(),
- exit = fadeOut(),
- ) {
- Column(
- verticalArrangement =
- spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
- modifier = modifier.fillMaxSize(),
- ) {
- EditGridHeader { Text(text = "Hold and drag to add tiles.") }
-
- AvailableTileGrid(
- otherTiles,
- columns,
- tilePadding,
- addTileToEnd,
- currentListState,
- )
- }
- }
-
- // Drop zone to remove tiles dragged out of the tile grid
- Spacer(
- modifier =
- Modifier.fillMaxWidth()
- .weight(1f)
- .dragAndDropRemoveZone(currentListState, onRemoveTile)
- )
- }
- }
-}
-
-@Composable
-private fun EditGridHeader(
- modifier: Modifier = Modifier,
- content: @Composable BoxScope.() -> Unit,
-) {
- CompositionLocalProvider(
- LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f)
- ) {
- Box(
- contentAlignment = Alignment.Center,
- modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight),
- ) {
- content()
- }
- }
-}
-
-@Composable
-private fun RemoveTileTarget() {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(),
- modifier =
- Modifier.fillMaxHeight()
- .border(1.dp, LocalContentColor.current, shape = CircleShape)
- .padding(10.dp),
- ) {
- Icon(imageVector = Icons.Default.Clear, contentDescription = null)
- Text(text = "Remove")
- }
-}
-
-@Composable
-private fun CurrentTilesContainer(content: @Composable () -> Unit) {
- Box(
- Modifier.fillMaxWidth()
- .border(
- width = 1.dp,
- color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f),
- shape = RoundedCornerShape(48.dp),
- )
- .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
- ) {
- content()
- }
-}
-
-@Composable
-private fun CurrentTilesGrid(
- listState: EditTileListState,
- columns: Int,
- tilePadding: Dp,
- onClick: (TileSpec) -> Unit,
- onResize: (TileSpec) -> Unit,
- onSetTiles: (List<TileSpec>) -> Unit,
-) {
- val currentListState by rememberUpdatedState(listState)
-
- CurrentTilesContainer {
- val tileHeight = tileHeight()
- val totalRows = listState.tiles.lastOrNull()?.row ?: 0
- val totalHeight = gridHeight(totalRows + 1, tileHeight, tilePadding)
- val gridState = rememberLazyGridState()
- var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) }
-
- TileLazyGrid(
- state = gridState,
- modifier =
- Modifier.height(totalHeight)
- .dragAndDropTileList(gridState, gridContentOffset, listState) {
- onSetTiles(currentListState.tileSpecs())
- }
- .onGloballyPositioned { coordinates ->
- gridContentOffset = coordinates.positionInRoot()
- }
- .testTag(CURRENT_TILES_GRID_TEST_TAG),
- columns = GridCells.Fixed(columns),
- ) {
- editTiles(
- listState.tiles,
- ClickAction.REMOVE,
- onClick,
- listState,
- onResize = onResize,
- indicatePosition = true,
- )
- }
- }
-}
-
-@Composable
-private fun AvailableTileGrid(
- tiles: List<SizedTile<EditTileViewModel>>,
- columns: Int,
- tilePadding: Dp,
- onClick: (TileSpec) -> Unit,
- dragAndDropState: DragAndDropState,
-) {
- val availableTileHeight = tileHeight(true)
- val availableGridHeight = gridHeight(tiles.size, availableTileHeight, columns, tilePadding)
-
- // Available tiles aren't visible during drag and drop, so the row isn't needed
- val groupedTiles =
- remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) {
- groupAndSort(tiles.fastMap { TileGridCell(it, 0) })
- }
- val labelColors = TileDefaults.inactiveTileColors()
- // Available tiles
- TileLazyGrid(
- modifier = Modifier.height(availableGridHeight).testTag(AVAILABLE_TILES_GRID_TEST_TAG),
- columns = GridCells.Fixed(columns),
- ) {
- groupedTiles.forEach { category, tiles ->
- stickyHeader {
- Text(
- text = category.label.load() ?: "",
- fontSize = 20.sp,
- color = labelColors.label,
- modifier =
- Modifier.background(Color.Black)
- .padding(start = 16.dp, bottom = 8.dp, top = 8.dp),
- )
- }
- editTiles(
- tiles,
- ClickAction.ADD,
- onClick,
- dragAndDropState = dragAndDropState,
- showLabels = true,
- )
- }
- }
-}
-
-fun gridHeight(nTiles: Int, tileHeight: Dp, columns: Int, padding: Dp): Dp {
- val rows = (nTiles + columns - 1) / columns
- return gridHeight(rows, tileHeight, padding)
-}
-
-fun gridHeight(rows: Int, tileHeight: Dp, padding: Dp): Dp {
- return ((tileHeight + padding) * rows) - padding
-}
-
-private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any {
- return if (this is TileGridCell && !dragAndDropState.isMoving(tile.tileSpec)) {
- key
- } else {
- index
- }
-}
-
-fun LazyGridScope.editTiles(
- cells: List<GridCell>,
- clickAction: ClickAction,
- onClick: (TileSpec) -> Unit,
- dragAndDropState: DragAndDropState,
- onResize: (TileSpec) -> Unit = {},
- showLabels: Boolean = false,
- indicatePosition: Boolean = false,
-) {
- items(
- count = cells.size,
- key = { cells[it].key(it, dragAndDropState) },
- span = { cells[it].span },
- contentType = { TileType },
- ) { index ->
- when (val cell = cells[index]) {
- is TileGridCell ->
- if (dragAndDropState.isMoving(cell.tile.tileSpec)) {
- // If the tile is being moved, replace it with a visible spacer
- SpacerGridCell(
- Modifier.background(
- color = MaterialTheme.colorScheme.secondary,
- alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA },
- shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
- )
- .animateItem()
- )
- } else {
- TileGridCell(
- cell = cell,
- index = index,
- dragAndDropState = dragAndDropState,
- clickAction = clickAction,
- onClick = onClick,
- onResize = onResize,
- showLabels = showLabels,
- indicatePosition = indicatePosition,
- )
- }
- is SpacerGridCell -> SpacerGridCell()
- }
- }
-}
-
-@Composable
-private fun LazyGridItemScope.TileGridCell(
- cell: TileGridCell,
- index: Int,
- dragAndDropState: DragAndDropState,
- clickAction: ClickAction,
- onClick: (TileSpec) -> Unit,
- onResize: (TileSpec) -> Unit = {},
- showLabels: Boolean = false,
- indicatePosition: Boolean = false,
-) {
- val tileHeight = tileHeight(cell.isIcon && showLabels)
- val onClickActionName =
- when (clickAction) {
- ClickAction.ADD -> stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
- ClickAction.REMOVE ->
- stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
- }
- val stateDescription =
- if (indicatePosition) {
- stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
- } else {
- ""
- }
- EditTile(
- tileViewModel = cell.tile,
- iconOnly = cell.isIcon,
- showLabels = showLabels,
- modifier =
- Modifier.height(tileHeight)
- .animateItem()
- .semantics(mergeDescendants = true) {
- onClick(onClickActionName) { false }
- this.stateDescription = stateDescription
- }
- .dragAndDropTileSource(
- SizedTileImpl(cell.tile, cell.width),
- onClick,
- onResize,
- dragAndDropState,
- ),
- )
-}
-
-@Composable
-private fun SpacerGridCell(modifier: Modifier = Modifier) {
- // By default, spacers are invisible and exist purely to catch drag movements
- Box(modifier.height(tileHeight()).fillMaxWidth().tilePadding())
-}
-
-@Composable
-fun EditTile(
- tileViewModel: EditTileViewModel,
- iconOnly: Boolean,
- showLabels: Boolean,
- modifier: Modifier = Modifier,
-) {
- val label = tileViewModel.label.text
- val colors = TileDefaults.inactiveTileColors()
-
- TileContainer(
- colors = colors,
- showLabels = showLabels,
- label = label,
- iconOnly = iconOnly,
- shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
- modifier = modifier,
- ) {
- if (iconOnly) {
- TileIcon(
- icon = tileViewModel.icon,
- color = colors.icon,
- modifier = Modifier.align(Alignment.Center),
- )
- } else {
- LargeTileContent(
- label = label,
- secondaryLabel = tileViewModel.appName?.text,
- icon = tileViewModel.icon,
- colors = colors,
- iconShape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
- )
- }
- }
-}
-
-enum class ClickAction {
- ADD,
- REMOVE,
-}
-
-@Composable
-private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon {
- val context = LocalContext.current
- return icon.get()?.let {
- if (it is QSTileImpl.ResourceIcon) {
- Icon.Resource(it.resId, null)
- } else {
- Icon.Loaded(it.getDrawable(context), null)
- }
- } ?: Icon.Resource(R.drawable.ic_error_outline, null)
-}
-
-@OptIn(ExperimentalAnimationGraphicsApi::class)
-@Composable
-private fun TileIcon(
- icon: Icon,
- color: Color,
- animateToEnd: Boolean = false,
- modifier: Modifier = Modifier,
-) {
- val iconModifier = modifier.size(TileDefaults.IconSize)
- val context = LocalContext.current
- val loadedDrawable =
- remember(icon, context) {
- when (icon) {
- is Icon.Loaded -> icon.drawable
- is Icon.Resource -> context.getDrawable(icon.res)
- }
- }
- if (loadedDrawable !is Animatable) {
- Icon(icon = icon, tint = color, modifier = iconModifier)
- } else if (icon is Icon.Resource) {
- val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
- val painter =
- if (animateToEnd) {
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
- } else {
- var atEnd by remember(icon.res) { mutableStateOf(false) }
- LaunchedEffect(key1 = icon.res) {
- delay(350)
- atEnd = true
- }
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
- }
- Image(
- painter = painter,
- contentDescription = icon.contentDescription?.load(),
- colorFilter = ColorFilter.tint(color = color),
- modifier = iconModifier,
- )
- }
-}
-
-private fun Modifier.tilePadding(): Modifier {
- return padding(TileDefaults.TilePadding)
-}
-
-private fun tileHorizontalArrangement(): Arrangement.Horizontal {
- return spacedBy(space = TileDefaults.TileArrangementPadding, alignment = Alignment.Start)
-}
-
-@Composable
-fun tileHeight(iconWithLabel: Boolean = false): Dp {
- return if (iconWithLabel) {
- TileDefaults.IconTileWithLabelHeight
- } else {
- TileDefaults.TileHeight
- }
-}
-
-private data class TileColors(
- val background: Color,
- val iconBackground: Color,
- val label: Color,
- val secondaryLabel: Color,
- val icon: Color,
-)
-
-private object EditModeTileDefaults {
- const val PLACEHOLDER_ALPHA = .3f
- val EditGridHeaderHeight = 60.dp
-}
-
-private object TileDefaults {
- val InactiveCornerRadius = 50.dp
- val ActiveIconCornerRadius = 16.dp
- val ActiveTileCornerRadius = 24.dp
-
- val ToggleTargetSize = 56.dp
- val IconSize = 24.dp
-
- val TilePadding = 8.dp
- val TileArrangementPadding = 6.dp
-
- val TileHeight = 72.dp
- val IconTileWithLabelHeight = 140.dp
-
- @Composable fun longPressLabel() = stringResource(id = R.string.accessibility_long_click_tile)
-
- /** An active tile without dual target uses the active color as background */
- @Composable
- fun activeTileColors(): TileColors =
- TileColors(
- background = MaterialTheme.colorScheme.primary,
- iconBackground = MaterialTheme.colorScheme.primary,
- label = MaterialTheme.colorScheme.onPrimary,
- secondaryLabel = MaterialTheme.colorScheme.onPrimary,
- icon = MaterialTheme.colorScheme.onPrimary,
- )
-
- /** An active tile with dual target only show the active color on the icon */
- @Composable
- fun activeDualTargetTileColors(): TileColors =
- TileColors(
- background = MaterialTheme.colorScheme.surfaceVariant,
- iconBackground = MaterialTheme.colorScheme.primary,
- label = MaterialTheme.colorScheme.onSurfaceVariant,
- secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
- icon = MaterialTheme.colorScheme.onPrimary,
- )
-
- @Composable
- fun inactiveTileColors(): TileColors =
- TileColors(
- background = MaterialTheme.colorScheme.surfaceVariant,
- iconBackground = MaterialTheme.colorScheme.surfaceVariant,
- label = MaterialTheme.colorScheme.onSurfaceVariant,
- secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
- icon = MaterialTheme.colorScheme.onSurfaceVariant,
- )
-
- @Composable
- fun unavailableTileColors(): TileColors =
- TileColors(
- background = MaterialTheme.colorScheme.surface,
- iconBackground = MaterialTheme.colorScheme.surface,
- label = MaterialTheme.colorScheme.onSurface,
- secondaryLabel = MaterialTheme.colorScheme.onSurface,
- icon = MaterialTheme.colorScheme.onSurface,
- )
-
- @Composable
- fun getColorForState(uiState: TileUiState): TileColors {
- return when (uiState.state) {
- STATE_ACTIVE -> {
- if (uiState.handlesSecondaryClick) {
- activeDualTargetTileColors()
- } else {
- activeTileColors()
- }
- }
- STATE_INACTIVE -> inactiveTileColors()
- else -> unavailableTileColors()
- }
- }
-
- @Composable
- fun animateIconShape(state: Int): Shape {
- return animateShape(
- state = state,
- activeCornerRadius = ActiveIconCornerRadius,
- label = "QSTileCornerRadius",
- )
- }
-
- @Composable
- fun animateTileShape(state: Int): Shape {
- return animateShape(
- state = state,
- activeCornerRadius = ActiveTileCornerRadius,
- label = "QSTileIconCornerRadius",
- )
- }
-
- @Composable
- fun animateShape(state: Int, activeCornerRadius: Dp, label: String): Shape {
- val animatedCornerRadius by
- animateDpAsState(
- targetValue =
- if (state == STATE_ACTIVE) {
- activeCornerRadius
- } else {
- InactiveCornerRadius
- },
- label = label,
- )
- return RoundedCornerShape(animatedCornerRadius)
- }
-}
-
-private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
-private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid"
-
-/**
- * A composable function that returns the [Resources]. It will be recomposed when [Configuration]
- * gets updated.
- */
-@Composable
-@ReadOnlyComposable
-private fun resources(): Resources {
- LocalConfiguration.current
- return LocalContext.current.resources
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
new file mode 100644
index 000000000000..aeb6031d7b72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2024 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.qs.panels.ui.compose.infinitegrid
+
+import android.graphics.drawable.Animatable
+import android.text.TextUtils
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.unit.dp
+import com.android.compose.modifiers.background
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.load
+import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
+import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState
+import com.android.systemui.res.R
+import kotlinx.coroutines.delay
+
+private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target"
+
+@Composable
+fun LargeTileContent(
+ label: String,
+ secondaryLabel: String?,
+ icon: Icon,
+ colors: TileColors,
+ accessibilityUiState: AccessibilityUiState? = null,
+ toggleClickSupported: Boolean = false,
+ iconShape: Shape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
+ onClick: () -> Unit = {},
+ onLongClick: () -> Unit = {},
+) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(),
+ ) {
+ // Icon
+ val longPressLabel = longPressLabel()
+ Box(
+ modifier =
+ Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
+ Modifier.clip(iconShape)
+ .background(colors.iconBackground, { 1f })
+ .combinedClickable(
+ onClick = onClick,
+ onLongClick = onLongClick,
+ onLongClickLabel = longPressLabel,
+ )
+ .thenIf(accessibilityUiState != null) {
+ Modifier.semantics {
+ accessibilityUiState as AccessibilityUiState
+ contentDescription = accessibilityUiState.contentDescription
+ stateDescription = accessibilityUiState.stateDescription
+ accessibilityUiState.toggleableState?.let {
+ toggleableState = it
+ }
+ role = Role.Switch
+ }
+ .sysuiResTag(TEST_TAG_TOGGLE)
+ }
+ }
+ ) {
+ SmallTileContent(
+ icon = icon,
+ color = colors.icon,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+
+ // Labels
+ LargeTileLabels(
+ label = label,
+ secondaryLabel = secondaryLabel,
+ colors = colors,
+ accessibilityUiState = accessibilityUiState,
+ )
+ }
+}
+
+@Composable
+private fun LargeTileLabels(
+ label: String,
+ secondaryLabel: String?,
+ colors: TileColors,
+ accessibilityUiState: AccessibilityUiState? = null,
+) {
+ Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
+ Text(label, color = colors.label, modifier = Modifier.tileMarquee())
+ if (!TextUtils.isEmpty(secondaryLabel)) {
+ Text(
+ secondaryLabel ?: "",
+ color = colors.secondaryLabel,
+ modifier =
+ Modifier.tileMarquee().thenIf(
+ accessibilityUiState?.stateDescription?.contains(secondaryLabel ?: "") ==
+ true
+ ) {
+ Modifier.clearAndSetSemantics {}
+ },
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalAnimationGraphicsApi::class)
+@Composable
+fun SmallTileContent(
+ modifier: Modifier = Modifier,
+ icon: Icon,
+ color: Color,
+ animateToEnd: Boolean = false,
+) {
+ val iconModifier = modifier.size(CommonTileDefaults.IconSize)
+ val context = LocalContext.current
+ val loadedDrawable =
+ remember(icon, context) {
+ when (icon) {
+ is Icon.Loaded -> icon.drawable
+ is Icon.Resource -> context.getDrawable(icon.res)
+ }
+ }
+ if (loadedDrawable !is Animatable) {
+ Icon(icon = icon, tint = color, modifier = iconModifier)
+ } else if (icon is Icon.Resource) {
+ val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
+ val painter =
+ if (animateToEnd) {
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
+ } else {
+ var atEnd by remember(icon.res) { mutableStateOf(false) }
+ LaunchedEffect(key1 = icon.res) {
+ delay(350)
+ atEnd = true
+ }
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
+ }
+ Image(
+ painter = painter,
+ contentDescription = icon.contentDescription?.load(),
+ colorFilter = ColorFilter.tint(color = color),
+ modifier = iconModifier,
+ )
+ }
+}
+
+object CommonTileDefaults {
+ val IconSize = 24.dp
+ val ToggleTargetSize = 56.dp
+ val TileHeight = 72.dp
+ val TilePadding = 8.dp
+ val TileArrangementPadding = 6.dp
+ val InactiveCornerRadius = 50.dp
+
+ @Composable fun longPressLabel() = stringResource(id = R.string.accessibility_long_click_tile)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
new file mode 100644
index 000000000000..a43b8807b211
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2024 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(ExperimentalFoundationApi::class)
+
+package com.android.systemui.qs.panels.ui.compose.infinitegrid
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.LocalOverscrollConfiguration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyGridItemScope
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Clear
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.fastMap
+import com.android.compose.modifiers.background
+import com.android.systemui.common.ui.compose.load
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.compose.DragAndDropState
+import com.android.systemui.qs.panels.ui.compose.EditTileListState
+import com.android.systemui.qs.panels.ui.compose.dragAndDropRemoveZone
+import com.android.systemui.qs.panels.ui.compose.dragAndDropTileList
+import com.android.systemui.qs.panels.ui.compose.dragAndDropTileSource
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
+import com.android.systemui.qs.panels.ui.model.GridCell
+import com.android.systemui.qs.panels.ui.model.SpacerGridCell
+import com.android.systemui.qs.panels.ui.model.TileGridCell
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.groupAndSort
+import com.android.systemui.res.R
+
+object TileType
+
+@Composable
+fun DefaultEditTileGrid(
+ currentListState: EditTileListState,
+ otherTiles: List<SizedTile<EditTileViewModel>>,
+ columns: Int,
+ modifier: Modifier,
+ onAddTile: (TileSpec, Int) -> Unit,
+ onRemoveTile: (TileSpec) -> Unit,
+ onSetTiles: (List<TileSpec>) -> Unit,
+ onResize: (TileSpec) -> Unit,
+) {
+ val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
+ onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
+ }
+
+ CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
+ Column(
+ verticalArrangement =
+ spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
+ modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()),
+ ) {
+ AnimatedContent(
+ targetState = currentListState.dragInProgress,
+ modifier = Modifier.wrapContentSize(),
+ label = "",
+ ) { dragIsInProgress ->
+ EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) {
+ if (dragIsInProgress) {
+ RemoveTileTarget()
+ } else {
+ Text(text = "Hold and drag to rearrange tiles.")
+ }
+ }
+ }
+
+ CurrentTilesGrid(currentListState, columns, onRemoveTile, onResize, onSetTiles)
+
+ // Hide available tiles when dragging
+ AnimatedVisibility(
+ visible = !currentListState.dragInProgress,
+ enter = fadeIn(),
+ exit = fadeOut(),
+ ) {
+ Column(
+ verticalArrangement =
+ spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
+ modifier = modifier.fillMaxSize(),
+ ) {
+ EditGridHeader { Text(text = "Hold and drag to add tiles.") }
+
+ AvailableTileGrid(otherTiles, columns, addTileToEnd, currentListState)
+ }
+ }
+
+ // Drop zone to remove tiles dragged out of the tile grid
+ Spacer(
+ modifier =
+ Modifier.fillMaxWidth()
+ .weight(1f)
+ .dragAndDropRemoveZone(currentListState, onRemoveTile)
+ )
+ }
+ }
+}
+
+@Composable
+private fun EditGridHeader(
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit,
+) {
+ CompositionLocalProvider(
+ LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f)
+ ) {
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight),
+ ) {
+ content()
+ }
+ }
+}
+
+@Composable
+private fun RemoveTileTarget() {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(),
+ modifier =
+ Modifier.fillMaxHeight()
+ .border(1.dp, LocalContentColor.current, shape = CircleShape)
+ .padding(10.dp),
+ ) {
+ Icon(imageVector = Icons.Default.Clear, contentDescription = null)
+ Text(text = "Remove")
+ }
+}
+
+@Composable
+private fun CurrentTilesContainer(content: @Composable () -> Unit) {
+ Box(
+ Modifier.fillMaxWidth()
+ .border(
+ width = 1.dp,
+ color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f),
+ shape = RoundedCornerShape(48.dp),
+ )
+ .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
+ ) {
+ content()
+ }
+}
+
+@Composable
+private fun CurrentTilesGrid(
+ listState: EditTileListState,
+ columns: Int,
+ onClick: (TileSpec) -> Unit,
+ onResize: (TileSpec) -> Unit,
+ onSetTiles: (List<TileSpec>) -> Unit,
+) {
+ val currentListState by rememberUpdatedState(listState)
+ val tilePadding = CommonTileDefaults.TileArrangementPadding
+
+ CurrentTilesContainer {
+ val tileHeight = CommonTileDefaults.TileHeight
+ val totalRows = listState.tiles.lastOrNull()?.row ?: 0
+ val totalHeight = gridHeight(totalRows + 1, tileHeight, tilePadding)
+ val gridState = rememberLazyGridState()
+ var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) }
+
+ TileLazyGrid(
+ state = gridState,
+ modifier =
+ Modifier.height(totalHeight)
+ .dragAndDropTileList(gridState, gridContentOffset, listState) {
+ onSetTiles(currentListState.tileSpecs())
+ }
+ .onGloballyPositioned { coordinates ->
+ gridContentOffset = coordinates.positionInRoot()
+ }
+ .testTag(CURRENT_TILES_GRID_TEST_TAG),
+ columns = GridCells.Fixed(columns),
+ ) {
+ EditTiles(listState.tiles, onClick, listState, onResize = onResize)
+ }
+ }
+}
+
+@Composable
+private fun AvailableTileGrid(
+ tiles: List<SizedTile<EditTileViewModel>>,
+ columns: Int,
+ onClick: (TileSpec) -> Unit,
+ dragAndDropState: DragAndDropState,
+) {
+ // Available tiles aren't visible during drag and drop, so the row isn't needed
+ val groupedTiles =
+ remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) {
+ groupAndSort(tiles.fastMap { TileGridCell(it, 0) })
+ }
+ val labelColors = EditModeTileDefaults.editTileColors()
+
+ // Available tiles
+ Column(
+ verticalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding),
+ horizontalAlignment = Alignment.Start,
+ modifier =
+ Modifier.fillMaxWidth().wrapContentHeight().testTag(AVAILABLE_TILES_GRID_TEST_TAG),
+ ) {
+ groupedTiles.forEach { (category, tiles) ->
+ Text(
+ text = category.label.load() ?: "",
+ fontSize = 20.sp,
+ color = labelColors.label,
+ modifier =
+ Modifier.fillMaxWidth()
+ .background(Color.Black)
+ .padding(start = 16.dp, bottom = 8.dp, top = 8.dp),
+ )
+ tiles.chunked(columns).forEach { row ->
+ Row(
+ horizontalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding),
+ modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max),
+ ) {
+ row.forEachIndexed { index, tileGridCell ->
+ AvailableTileGridCell(
+ cell = tileGridCell,
+ index = index,
+ dragAndDropState = dragAndDropState,
+ onClick = onClick,
+ modifier = Modifier.weight(1f).fillMaxHeight(),
+ )
+ }
+
+ // Spacers for incomplete rows
+ repeat(columns - row.size) { Spacer(modifier = Modifier.weight(1f)) }
+ }
+ }
+ }
+ }
+}
+
+fun gridHeight(rows: Int, tileHeight: Dp, padding: Dp): Dp {
+ return ((tileHeight + padding) * rows) - padding
+}
+
+private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any {
+ return when (this) {
+ is TileGridCell -> {
+ if (dragAndDropState.isMoving(tile.tileSpec)) index else key
+ }
+ is SpacerGridCell -> index
+ }
+}
+
+fun LazyGridScope.EditTiles(
+ cells: List<GridCell>,
+ onClick: (TileSpec) -> Unit,
+ dragAndDropState: DragAndDropState,
+ onResize: (TileSpec) -> Unit = {},
+) {
+ items(
+ count = cells.size,
+ key = { cells[it].key(it, dragAndDropState) },
+ span = { cells[it].span },
+ contentType = { TileType },
+ ) { index ->
+ when (val cell = cells[index]) {
+ is TileGridCell ->
+ if (dragAndDropState.isMoving(cell.tile.tileSpec)) {
+ // If the tile is being moved, replace it with a visible spacer
+ SpacerGridCell(
+ Modifier.background(
+ color = MaterialTheme.colorScheme.secondary,
+ alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA },
+ shape = RoundedCornerShape(InactiveCornerRadius),
+ )
+ .animateItem()
+ )
+ } else {
+ TileGridCell(
+ cell = cell,
+ index = index,
+ dragAndDropState = dragAndDropState,
+ onClick = onClick,
+ onResize = onResize,
+ )
+ }
+ is SpacerGridCell -> SpacerGridCell()
+ }
+ }
+}
+
+@Composable
+private fun LazyGridItemScope.TileGridCell(
+ cell: TileGridCell,
+ index: Int,
+ dragAndDropState: DragAndDropState,
+ onClick: (TileSpec) -> Unit,
+ onResize: (TileSpec) -> Unit = {},
+) {
+ val onClickActionName = stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
+ val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+
+ EditTile(
+ tileViewModel = cell.tile,
+ iconOnly = cell.isIcon,
+ modifier =
+ Modifier.animateItem()
+ .semantics(mergeDescendants = true) {
+ onClick(onClickActionName) { false }
+ this.stateDescription = stateDescription
+ }
+ .dragAndDropTileSource(
+ SizedTileImpl(cell.tile, cell.width),
+ dragAndDropState,
+ onClick,
+ onResize,
+ ),
+ )
+}
+
+@Composable
+private fun AvailableTileGridCell(
+ cell: TileGridCell,
+ index: Int,
+ dragAndDropState: DragAndDropState,
+ modifier: Modifier = Modifier,
+ onClick: (TileSpec) -> Unit,
+) {
+ val onClickActionName = stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
+ val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+ val colors = EditModeTileDefaults.editTileColors()
+
+ // Displays the tile as an icon tile with the label underneath
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = spacedBy(CommonTileDefaults.TilePadding, Alignment.Top),
+ modifier = modifier,
+ ) {
+ EditTile(
+ tileViewModel = cell.tile,
+ iconOnly = true,
+ colors = colors,
+ modifier =
+ Modifier.semantics(mergeDescendants = true) {
+ onClick(onClickActionName) { false }
+ this.stateDescription = stateDescription
+ }
+ .dragAndDropTileSource(
+ SizedTileImpl(cell.tile, cell.width),
+ dragAndDropState,
+ onTap = onClick,
+ ),
+ )
+ Box(Modifier.fillMaxSize()) {
+ Text(
+ cell.tile.label.text,
+ maxLines = 2,
+ color = colors.label,
+ overflow = TextOverflow.Ellipsis,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+}
+
+@Composable
+private fun SpacerGridCell(modifier: Modifier = Modifier) {
+ // By default, spacers are invisible and exist purely to catch drag movements
+ Box(modifier.height(CommonTileDefaults.TileHeight).fillMaxWidth().tilePadding())
+}
+
+@Composable
+fun EditTile(
+ tileViewModel: EditTileViewModel,
+ iconOnly: Boolean,
+ modifier: Modifier = Modifier,
+ colors: TileColors = EditModeTileDefaults.editTileColors(),
+) {
+ EditTileContainer(colors = colors, modifier = modifier) {
+ if (iconOnly) {
+ SmallTileContent(
+ icon = tileViewModel.icon,
+ color = colors.icon,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ } else {
+ LargeTileContent(
+ label = tileViewModel.label.text,
+ secondaryLabel = tileViewModel.appName?.text,
+ icon = tileViewModel.icon,
+ colors = colors,
+ )
+ }
+ }
+}
+
+@Composable
+private fun EditTileContainer(
+ colors: TileColors,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit,
+) {
+ Box(
+ modifier =
+ modifier
+ .height(CommonTileDefaults.TileHeight)
+ .fillMaxWidth()
+ .drawBehind {
+ drawRoundRect(
+ SolidColor(colors.background),
+ cornerRadius = CornerRadius(InactiveCornerRadius.toPx()),
+ )
+ }
+ .tilePadding(),
+ content = content,
+ )
+}
+
+private object EditModeTileDefaults {
+ const val PLACEHOLDER_ALPHA = .3f
+ val EditGridHeaderHeight = 60.dp
+
+ @Composable
+ fun editTileColors(): TileColors =
+ TileColors(
+ background = MaterialTheme.colorScheme.surfaceVariant,
+ iconBackground = MaterialTheme.colorScheme.surfaceVariant,
+ label = MaterialTheme.colorScheme.onSurfaceVariant,
+ secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
+ icon = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+}
+
+private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
+private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index c75b601ab4a8..f96c27dc3ef6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.ui.compose
+package com.android.systemui.qs.panels.ui.compose.infinitegrid
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
@@ -26,6 +26,8 @@ import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
+import com.android.systemui.qs.panels.ui.compose.rememberEditListState
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
@@ -61,7 +63,7 @@ constructor(
Tile(
tile = sizedTiles[index].tile,
iconOnly = iconTilesViewModel.isIconTile(sizedTiles[index].tile.spec),
- modifier = Modifier
+ modifier = Modifier,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
new file mode 100644
index 000000000000..aa6c08eecd76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2024 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(ExperimentalFoundationApi::class)
+
+package com.android.systemui.qs.panels.ui.compose.infinitegrid
+
+import android.content.res.Resources
+import android.service.quicksettings.Tile.STATE_ACTIVE
+import android.service.quicksettings.Tile.STATE_INACTIVE
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.Expandable
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
+import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
+import java.util.function.Supplier
+
+private const val TEST_TAG_SMALL = "qs_tile_small"
+private const val TEST_TAG_LARGE = "qs_tile_large"
+
+@Composable
+fun TileLazyGrid(
+ columns: GridCells,
+ modifier: Modifier = Modifier,
+ state: LazyGridState = rememberLazyGridState(),
+ content: LazyGridScope.() -> Unit,
+) {
+ LazyVerticalGrid(
+ state = state,
+ columns = columns,
+ verticalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding),
+ horizontalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding),
+ modifier = modifier,
+ content = content,
+ )
+}
+
+@Composable
+fun Tile(tile: TileViewModel, iconOnly: Boolean, modifier: Modifier) {
+ val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
+ val resources = resources()
+ val uiState = remember(state, resources) { state.toUiState(resources) }
+ val colors = TileDefaults.getColorForState(uiState)
+
+ // TODO(b/361789146): Draw the shapes instead of clipping
+ val tileShape = TileDefaults.animateTileShape(uiState.state)
+
+ TileContainer(
+ color =
+ if (iconOnly || !uiState.handlesSecondaryClick) {
+ colors.iconBackground
+ } else {
+ colors.background
+ },
+ shape = tileShape,
+ iconOnly = iconOnly,
+ onClick = tile::onClick,
+ onLongClick = tile::onLongClick,
+ uiState = uiState,
+ modifier = modifier,
+ ) { expandable ->
+ val icon = getTileIcon(icon = uiState.icon)
+ if (iconOnly) {
+ SmallTileContent(
+ icon = icon,
+ color = colors.icon,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ } else {
+ val iconShape = TileDefaults.animateIconShape(uiState.state)
+ LargeTileContent(
+ label = uiState.label,
+ secondaryLabel = uiState.secondaryLabel,
+ icon = icon,
+ colors = colors,
+ iconShape = iconShape,
+ toggleClickSupported = state.handlesSecondaryClick,
+ onClick = {
+ if (state.handlesSecondaryClick) {
+ tile.onSecondaryClick()
+ }
+ },
+ onLongClick = { tile.onLongClick(expandable) },
+ )
+ }
+ }
+}
+
+@Composable
+private fun TileContainer(
+ color: Color,
+ shape: Shape,
+ iconOnly: Boolean,
+ uiState: TileUiState,
+ modifier: Modifier = Modifier,
+ onClick: (Expandable) -> Unit = {},
+ onLongClick: (Expandable) -> Unit = {},
+ content: @Composable BoxScope.(Expandable) -> Unit,
+) {
+ Expandable(color = color, shape = shape, modifier = modifier.clip(shape)) {
+ val longPressLabel = longPressLabel()
+ Box(
+ modifier =
+ Modifier.height(CommonTileDefaults.TileHeight)
+ .fillMaxWidth()
+ .combinedClickable(
+ onClick = { onClick(it) },
+ onLongClick = { onLongClick(it) },
+ onClickLabel = uiState.accessibilityUiState.clickLabel,
+ onLongClickLabel = longPressLabel,
+ )
+ .semantics {
+ role = uiState.accessibilityUiState.accessibilityRole
+ if (uiState.accessibilityUiState.accessibilityRole == Role.Switch) {
+ uiState.accessibilityUiState.toggleableState?.let {
+ toggleableState = it
+ }
+ }
+ stateDescription = uiState.accessibilityUiState.stateDescription
+ }
+ .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE)
+ .thenIf(iconOnly) {
+ Modifier.semantics {
+ contentDescription = uiState.accessibilityUiState.contentDescription
+ }
+ }
+ .tilePadding()
+ ) {
+ content(it)
+ }
+ }
+}
+
+@Composable
+private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon {
+ val context = LocalContext.current
+ return icon.get()?.let {
+ if (it is QSTileImpl.ResourceIcon) {
+ Icon.Resource(it.resId, null)
+ } else {
+ Icon.Loaded(it.getDrawable(context), null)
+ }
+ } ?: Icon.Resource(R.drawable.ic_error_outline, null)
+}
+
+fun tileHorizontalArrangement(): Arrangement.Horizontal {
+ return spacedBy(space = CommonTileDefaults.TileArrangementPadding, alignment = Alignment.Start)
+}
+
+fun Modifier.tileMarquee(): Modifier {
+ return basicMarquee(iterations = 1, initialDelayMillis = 200)
+}
+
+fun Modifier.tilePadding(): Modifier {
+ return padding(CommonTileDefaults.TilePadding)
+}
+
+data class TileColors(
+ val background: Color,
+ val iconBackground: Color,
+ val label: Color,
+ val secondaryLabel: Color,
+ val icon: Color,
+)
+
+private object TileDefaults {
+ val ActiveIconCornerRadius = 16.dp
+ val ActiveTileCornerRadius = 24.dp
+
+ /** An active tile without dual target uses the active color as background */
+ @Composable
+ fun activeTileColors(): TileColors =
+ TileColors(
+ background = MaterialTheme.colorScheme.primary,
+ iconBackground = MaterialTheme.colorScheme.primary,
+ label = MaterialTheme.colorScheme.onPrimary,
+ secondaryLabel = MaterialTheme.colorScheme.onPrimary,
+ icon = MaterialTheme.colorScheme.onPrimary,
+ )
+
+ /** An active tile with dual target only show the active color on the icon */
+ @Composable
+ fun activeDualTargetTileColors(): TileColors =
+ TileColors(
+ background = MaterialTheme.colorScheme.surfaceVariant,
+ iconBackground = MaterialTheme.colorScheme.primary,
+ label = MaterialTheme.colorScheme.onSurfaceVariant,
+ secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
+ icon = MaterialTheme.colorScheme.onPrimary,
+ )
+
+ @Composable
+ fun inactiveTileColors(): TileColors =
+ TileColors(
+ background = MaterialTheme.colorScheme.surfaceVariant,
+ iconBackground = MaterialTheme.colorScheme.surfaceVariant,
+ label = MaterialTheme.colorScheme.onSurfaceVariant,
+ secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
+ icon = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+
+ @Composable
+ fun unavailableTileColors(): TileColors =
+ TileColors(
+ background = MaterialTheme.colorScheme.surface,
+ iconBackground = MaterialTheme.colorScheme.surface,
+ label = MaterialTheme.colorScheme.onSurface,
+ secondaryLabel = MaterialTheme.colorScheme.onSurface,
+ icon = MaterialTheme.colorScheme.onSurface,
+ )
+
+ @Composable
+ fun getColorForState(uiState: TileUiState): TileColors {
+ return when (uiState.state) {
+ STATE_ACTIVE -> {
+ if (uiState.handlesSecondaryClick) {
+ activeDualTargetTileColors()
+ } else {
+ activeTileColors()
+ }
+ }
+ STATE_INACTIVE -> inactiveTileColors()
+ else -> unavailableTileColors()
+ }
+ }
+
+ @Composable
+ fun animateIconShape(state: Int): Shape {
+ return animateShape(
+ state = state,
+ activeCornerRadius = ActiveIconCornerRadius,
+ label = "QSTileCornerRadius",
+ )
+ }
+
+ @Composable
+ fun animateTileShape(state: Int): Shape {
+ return animateShape(
+ state = state,
+ activeCornerRadius = ActiveTileCornerRadius,
+ label = "QSTileIconCornerRadius",
+ )
+ }
+
+ @Composable
+ fun animateShape(state: Int, activeCornerRadius: Dp, label: String): Shape {
+ val animatedCornerRadius by
+ animateDpAsState(
+ targetValue =
+ if (state == STATE_ACTIVE) {
+ activeCornerRadius
+ } else {
+ InactiveCornerRadius
+ },
+ label = label,
+ )
+ return RoundedCornerShape(animatedCornerRadius)
+ }
+}
+
+/**
+ * A composable function that returns the [Resources]. It will be recomposed when [Configuration]
+ * gets updated.
+ */
+@Composable
+@ReadOnlyComposable
+private fun resources(): Resources {
+ LocalConfiguration.current
+ return LocalContext.current.resources
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
index 08ee856a0ec6..b16a7075607f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -24,7 +24,7 @@ import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.shared.model.CategoryAndName
/** Represents an item from a grid associated with a row and a span */
-interface GridCell {
+sealed interface GridCell {
val row: Int
val span: GridItemSpan
}
@@ -38,30 +38,26 @@ data class TileGridCell(
override val tile: EditTileViewModel,
override val row: Int,
override val width: Int,
- override val span: GridItemSpan = GridItemSpan(width)
+ override val span: GridItemSpan = GridItemSpan(width),
) : GridCell, SizedTile<EditTileViewModel>, CategoryAndName by tile {
val key: String = "${tile.tileSpec.spec}-$row"
constructor(
sizedTile: SizedTile<EditTileViewModel>,
- row: Int
- ) : this(
- tile = sizedTile.tile,
- row = row,
- width = sizedTile.width,
- )
+ row: Int,
+ ) : this(tile = sizedTile.tile, row = row, width = sizedTile.width)
}
/** Represents an empty space used to fill incomplete rows. Will always display as a 1x1 tile */
@Immutable
data class SpacerGridCell(
override val row: Int,
- override val span: GridItemSpan = GridItemSpan(1)
+ override val span: GridItemSpan = GridItemSpan(1),
) : GridCell
fun List<SizedTile<EditTileViewModel>>.toGridCells(
columns: Int,
- includeSpacers: Boolean = false
+ includeSpacers: Boolean = false,
): List<GridCell> {
return splitInRowsSequence(this, columns)
.flatMapIndexed { rowIndex, sizedTiles ->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index 0bcb6b7e7874..9677d47b38e8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -18,8 +18,6 @@ package com.android.systemui.qs.pipeline.domain.startable
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.flags.NewQsUI
-import com.android.systemui.qs.panels.domain.interactor.GridConsistencyInteractor
import com.android.systemui.qs.pipeline.domain.interactor.AccessibilityTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -36,16 +34,11 @@ constructor(
private val autoAddInteractor: AutoAddInteractor,
private val featureFlags: QSPipelineFlagsRepository,
private val restoreReconciliationInteractor: RestoreReconciliationInteractor,
- private val gridConsistencyInteractor: GridConsistencyInteractor,
) : CoreStartable {
override fun start() {
accessibilityTilesInteractor.init(currentTilesInteractor)
autoAddInteractor.init(currentTilesInteractor)
restoreReconciliationInteractor.start()
-
- if (NewQsUI.isEnabled) {
- gridConsistencyInteractor.start()
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index e11ffccb0be3..b7e2cf23e3a8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -61,6 +61,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.session.shared.SessionStorage
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.logger.SceneLogger
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -228,8 +229,10 @@ constructor(
is ObservableTransitionState.Idle -> {
if (state.currentScene != Scenes.Gone) {
true to "scene is not Gone"
+ } else if (state.currentOverlays.isNotEmpty()) {
+ true to "overlay is shown"
} else {
- false to "scene is Gone"
+ false to "scene is Gone and no overlays are shown"
}
}
is ObservableTransitionState.Transition -> {
@@ -712,19 +715,21 @@ constructor(
if (isDeviceLocked) {
sceneInteractor.transitionState
.mapNotNull { it as? ObservableTransitionState.Idle }
- .map { it.currentScene }
+ .map { it.currentScene to it.currentOverlays }
.distinctUntilChanged()
- .map { sceneKey ->
- when (sceneKey) {
+ .map { (sceneKey, currentOverlays) ->
+ when {
// When locked, showing the lockscreen scene should be reported
// as "interacting" while showing other scenes should report as
// "not interacting".
//
// This is done here in order to match the legacy
// implementation. The real reason why is lost to lore and myth.
- Scenes.Lockscreen -> true
- Scenes.Bouncer -> false
- Scenes.Shade -> false
+ Overlays.NotificationsShade in currentOverlays -> false
+ Overlays.QuickSettingsShade in currentOverlays -> null
+ sceneKey == Scenes.Lockscreen -> true
+ sceneKey == Scenes.Bouncer -> false
+ sceneKey == Scenes.Shade -> false
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index 751448fe607e..7b6b0f614cc2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -26,7 +26,6 @@ import com.android.systemui.flags.RefactorFlagUtils
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
-import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
@@ -39,7 +38,6 @@ object SceneContainerFlag {
inline val isEnabled
get() =
sceneContainer() && // mainAconfigFlag
- ComposeLockscreen.isEnabled &&
KeyguardBottomAreaRefactor.isEnabled &&
KeyguardWmStateRefactor.isEnabled &&
MigrateClocksToBlueprint.isEnabled &&
@@ -55,7 +53,6 @@ object SceneContainerFlag {
/** The set of secondary flags which must be enabled for scene container to work properly */
inline fun getSecondaryFlags(): Sequence<FlagToken> =
sequenceOf(
- ComposeLockscreen.token,
KeyguardBottomAreaRefactor.token,
KeyguardWmStateRefactor.token,
MigrateClocksToBlueprint.token,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 42499f043457..f76c5fd4ca83 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -137,7 +137,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
-import com.android.systemui.keyguard.shared.ComposeLockscreen;
import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -186,6 +185,7 @@ import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
@@ -207,7 +207,6 @@ import com.android.systemui.statusbar.phone.BounceInterpolator;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -2511,11 +2510,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
return 0;
}
- if (ComposeLockscreen.isEnabled()) {
- return (int) mKeyguardInteractor.getNotificationContainerBounds()
- .getValue().getTop();
- }
-
if (!mKeyguardBypassController.getBypassEnabled()) {
if (MigrateClocksToBlueprint.isEnabled() && !mSplitShadeEnabled) {
return (int) mKeyguardInteractor.getNotificationContainerBounds()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 4056e7b89c2c..c6c303ebb0d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -16,9 +16,11 @@
package com.android.systemui.statusbar.core
import android.app.Fragment
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
import com.android.systemui.statusbar.phone.PhoneStatusBarView
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
@@ -33,50 +35,16 @@ import javax.inject.Provider
* Responsible for creating the status bar window and initializing the root components of that
* window (see [CollapsedStatusBarFragment])
*/
-@SysUISingleton
-class StatusBarInitializer @Inject constructor(
- private val windowController: StatusBarWindowController,
- private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
- private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
-) {
+interface StatusBarInitializer {
- var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+ var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener?
/**
* Creates the status bar window and root views, and initializes the component.
*
* TODO(b/277764509): Initialize the status bar via [CoreStartable#start].
*/
- fun initializeStatusBar() {
- windowController.fragmentHostManager.addTagListener(
- CollapsedStatusBarFragment.TAG,
- object : FragmentHostManager.FragmentListener {
- override fun onFragmentViewCreated(tag: String, fragment: Fragment) {
- val statusBarFragmentComponent = (fragment as CollapsedStatusBarFragment)
- .statusBarFragmentComponent ?: throw IllegalStateException()
- statusBarViewUpdatedListener?.onStatusBarViewUpdated(
- statusBarFragmentComponent.phoneStatusBarView,
- statusBarFragmentComponent.phoneStatusBarViewController,
- statusBarFragmentComponent.phoneStatusBarTransitions
- )
- creationListeners.forEach { listener ->
- listener.onStatusBarViewInitialized(statusBarFragmentComponent)
- }
- }
-
- override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
- // nop
- }
- }
- ).fragmentManager
- .beginTransaction()
- .replace(
- R.id.status_bar_container,
- collapsedStatusBarFragmentProvider.get(),
- CollapsedStatusBarFragment.TAG
- )
- .commit()
- }
+ fun initializeStatusBar()
interface OnStatusBarViewInitializedListener {
@@ -84,7 +52,8 @@ class StatusBarInitializer @Inject constructor(
* The status bar view has been initialized.
*
* @param component Dagger component that is created when the status bar view is created.
- * Can be used to retrieve dependencies from that scope, including the status bar root view.
+ * Can be used to retrieve dependencies from that scope, including the status bar root
+ * view.
*/
fun onStatusBarViewInitialized(component: StatusBarFragmentComponent)
}
@@ -93,7 +62,53 @@ class StatusBarInitializer @Inject constructor(
fun onStatusBarViewUpdated(
statusBarView: PhoneStatusBarView,
statusBarViewController: PhoneStatusBarViewController,
- statusBarTransitions: PhoneStatusBarTransitions
+ statusBarTransitions: PhoneStatusBarTransitions,
)
}
}
+
+@SysUISingleton
+class StatusBarInitializerImpl
+@Inject
+constructor(
+ private val windowController: StatusBarWindowController,
+ private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
+ private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
+) : StatusBarInitializer {
+
+ override var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+
+ override fun initializeStatusBar() {
+ windowController.fragmentHostManager
+ .addTagListener(
+ CollapsedStatusBarFragment.TAG,
+ object : FragmentHostManager.FragmentListener {
+ override fun onFragmentViewCreated(tag: String, fragment: Fragment) {
+ val statusBarFragmentComponent =
+ (fragment as CollapsedStatusBarFragment).statusBarFragmentComponent
+ ?: throw IllegalStateException()
+ statusBarViewUpdatedListener?.onStatusBarViewUpdated(
+ statusBarFragmentComponent.phoneStatusBarView,
+ statusBarFragmentComponent.phoneStatusBarViewController,
+ statusBarFragmentComponent.phoneStatusBarTransitions,
+ )
+ creationListeners.forEach { listener ->
+ listener.onStatusBarViewInitialized(statusBarFragmentComponent)
+ }
+ }
+
+ override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
+ // nop
+ }
+ },
+ )
+ .fragmentManager
+ .beginTransaction()
+ .replace(
+ R.id.status_bar_container,
+ collapsedStatusBarFragmentProvider.get(),
+ CollapsedStatusBarFragment.TAG,
+ )
+ .commit()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 406a66449f82..526c64c15696 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -20,12 +20,16 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.core.StatusBarInitializerImpl
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -57,6 +61,13 @@ abstract class StatusBarModule {
@ClassKey(StatusBarSignalPolicy::class)
abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+ @Binds abstract fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer
+
+ @Binds
+ abstract fun statusBarWindowController(
+ impl: StatusBarWindowControllerImpl
+ ): StatusBarWindowController
+
companion object {
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
index c0302bc348b6..9af4b8c18c86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
@@ -25,6 +25,7 @@ import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
/**
* Repository used for tracking the state of notification remote input (e.g. when the user presses
@@ -33,14 +34,21 @@ import kotlinx.coroutines.flow.Flow
interface RemoteInputRepository {
/** Whether remote input is currently active for any notification. */
val isRemoteInputActive: Flow<Boolean>
+
+ /**
+ * The bottom bound of the currently focused remote input notification row, or null if there
+ * isn't one.
+ */
+ val remoteInputRowBottomBound: Flow<Float?>
+
+ fun setRemoteInputRowBottomBound(bottom: Float?)
}
@SysUISingleton
class RemoteInputRepositoryImpl
@Inject
-constructor(
- private val notificationRemoteInputManager: NotificationRemoteInputManager,
-) : RemoteInputRepository {
+constructor(private val notificationRemoteInputManager: NotificationRemoteInputManager) :
+ RemoteInputRepository {
override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow {
trySend(false) // initial value is false
val callback =
@@ -52,6 +60,12 @@ constructor(
notificationRemoteInputManager.addControllerCallback(callback)
awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) }
}
+
+ override val remoteInputRowBottomBound = MutableStateFlow<Float?>(null)
+
+ override fun setRemoteInputRowBottomBound(bottom: Float?) {
+ remoteInputRowBottomBound.value = bottom
+ }
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
index 68f727b046c0..b83b0cc8d2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
@@ -20,13 +20,24 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.data.repository.RemoteInputRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
/**
* Interactor used for business logic pertaining to the notification remote input (e.g. when the
* user presses "reply" on a notification and the keyboard opens).
*/
@SysUISingleton
-class RemoteInputInteractor @Inject constructor(remoteInputRepository: RemoteInputRepository) {
+class RemoteInputInteractor
+@Inject
+constructor(private val remoteInputRepository: RemoteInputRepository) {
/** Is remote input currently active for a notification? */
val isRemoteInputActive: Flow<Boolean> = remoteInputRepository.isRemoteInputActive
+
+ /** The bottom bound of the currently focused remote input notification row. */
+ val remoteInputRowBottomBound: Flow<Float> =
+ remoteInputRepository.remoteInputRowBottomBound.mapNotNull { it }
+
+ fun setRemoteInputRowBottomBound(bottom: Float?) {
+ remoteInputRepository.setRemoteInputRowBottomBound(bottom)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index cb3e26b9f8ea..5003a6af5c4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -21,6 +21,7 @@ import static android.service.notification.NotificationListenerService.REASON_CA
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
+import static com.android.systemui.statusbar.policy.RemoteInputView.FOCUS_ANIMATION_MIN_SCALE;
import static com.android.systemui.util.ColorUtilKt.hexColorString;
import android.animation.Animator;
@@ -83,6 +84,7 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -118,6 +120,7 @@ import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
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.RemoteInputView;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.Compile;
@@ -830,6 +833,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPrivateLayout.setRemoteInputController(r);
}
+ /**
+ * Return the cumulative y-value that the actions container expands via its scale animator when
+ * remote input is activated.
+ */
+ public float getRemoteInputActionsContainerExpandedOffset() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+ RemoteInputView expandedRemoteInput = mPrivateLayout.getExpandedRemoteInput();
+ if (expandedRemoteInput == null) return 0f;
+ View actionsContainerLayout = expandedRemoteInput.getActionsContainerLayout();
+ if (actionsContainerLayout == null) return 0f;
+
+ return actionsContainerLayout.getHeight() * (1 - FOCUS_ANIMATION_MIN_SCALE) * 0.5f;
+ }
+
public void addChildNotification(ExpandableNotificationRow row) {
addChildNotification(row, -1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7543f3b48e48..e7c67f93eb78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -99,6 +99,7 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FakeShadowView;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -120,7 +121,6 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape;
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
@@ -740,6 +740,15 @@ public class NotificationStackScrollLayout
updateFooter();
}
+ void sendRemoteInputRowBottomBound(Float bottom) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ if (bottom != null) {
+ bottom += getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_content_margin);
+ }
+ mScrollViewFields.sendRemoteInputRowBottomBound(bottom);
+ }
+
/** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */
public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
FooterViewRefactor.assertInLegacyMode();
@@ -1274,6 +1283,11 @@ public class NotificationStackScrollLayout
}
@Override
+ public void setRemoteInputRowBottomBoundConsumer(@Nullable Consumer<Float> consumer) {
+ mScrollViewFields.setRemoteInputRowBottomBoundConsumer(consumer);
+ }
+
+ @Override
public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) {
mScrollViewFields.setHeadsUpHeightConsumer(consumer);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e5f63c1cb480..dad6894a43ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -98,6 +98,9 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -129,9 +132,6 @@ import com.android.systemui.statusbar.notification.row.NotificationSnooze;
import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -1605,6 +1605,9 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationEntry entry,
boolean remoteInputActive) {
+ if (SceneContainerFlag.isEnabled()) {
+ sendRemoteInputRowBottomBound(entry, remoteInputActive);
+ }
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.notifyHeightChanged(true /* needsAnimation */);
if (!FooterViewRefactor.isEnabled()) {
@@ -1620,6 +1623,15 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mView.requestDisallowLongPress();
mView.requestDisallowDismiss();
}
+
+ private void sendRemoteInputRowBottomBound(NotificationEntry entry,
+ boolean remoteInputActive) {
+ ExpandableNotificationRow row = entry.getRow();
+ float top = row.getTranslationY();
+ int height = row.getActualHeight();
+ float bottom = top + height + row.getRemoteInputActionsContainerExpandedOffset();
+ mView.sendRemoteInputRowBottomBound(remoteInputActive ? bottom : null);
+ }
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index aa3953987c10..c08ed6120832 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -57,6 +57,13 @@ class ScrollViewFields {
* guts off of this gesture, we can notify the placeholder through here.
*/
var currentGestureInGutsConsumer: Consumer<Boolean>? = null
+
+ /**
+ * When a notification begins remote input, its bottom Y bound is sent to the placeholder
+ * through here in order to adjust to accommodate the IME.
+ */
+ var remoteInputRowBottomBoundConsumer: Consumer<Float?>? = null
+
/**
* Any time the heads up height is recalculated, it should be updated here to be used by the
* placeholder
@@ -75,6 +82,10 @@ class ScrollViewFields {
fun sendCurrentGestureInGuts(isCurrentGestureInGuts: Boolean) =
currentGestureInGutsConsumer?.accept(isCurrentGestureInGuts)
+ /** send [bottomY] to the [remoteInputRowBottomBoundConsumer], if present. */
+ fun sendRemoteInputRowBottomBound(bottomY: Float?) =
+ remoteInputRowBottomBoundConsumer?.accept(bottomY)
+
/** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */
fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 235b4da3f029..41c02934efa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -74,6 +74,9 @@ interface NotificationScrollView {
/** Set a consumer for current gesture in guts events */
fun setCurrentGestureInGutsConsumer(consumer: Consumer<Boolean>?)
+ /** Set a consumer for current remote input notification row bottom bound events */
+ fun setRemoteInputRowBottomBoundConsumer(consumer: Consumer<Float?>?)
+
/** Set a consumer for heads up height changed events */
fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 6d5553fec6b4..2e37dead8787 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -108,10 +108,14 @@ constructor(
view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
view.setCurrentGestureInGutsConsumer(viewModel.currentGestureInGutsConsumer)
+ view.setRemoteInputRowBottomBoundConsumer(
+ viewModel.remoteInputRowBottomBoundConsumer
+ )
DisposableHandle {
view.setSyntheticScrollConsumer(null)
view.setCurrentGestureOverscrollConsumer(null)
view.setCurrentGestureInGutsConsumer(null)
+ view.setRemoteInputRowBottomBoundConsumer(null)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 8d7007b2fba4..5b2e02d446cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -31,6 +31,7 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
@@ -56,6 +57,7 @@ constructor(
dumpManager: DumpManager,
stackAppearanceInteractor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
+ private val remoteInputInteractor: RemoteInputInteractor,
private val sceneInteractor: SceneInteractor,
// TODO(b/336364825) Remove Lazy when SceneContainerFlag is released -
// while the flag is off, creating this object too early results in a crash
@@ -240,6 +242,10 @@ constructor(
val currentGestureInGutsConsumer: (Boolean) -> Unit =
stackAppearanceInteractor::setCurrentGestureInGuts
+ /** Receives the bottom bound of the currently focused remote input notification row. */
+ val remoteInputRowBottomBoundConsumer: (Float?) -> Unit =
+ remoteInputInteractor::setRemoteInputRowBottomBound
+
/** Whether the notification stack is scrollable or not. */
val isScrollable: Flow<Boolean> =
combine(sceneInteractor.currentScene, sceneInteractor.currentOverlays) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 69c1bf3b61b7..c8e83581e831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -24,6 +24,7 @@ import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -49,6 +50,7 @@ constructor(
private val sceneInteractor: SceneInteractor,
private val shadeInteractor: ShadeInteractor,
private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
+ remoteInputInteractor: RemoteInputInteractor,
featureFlags: FeatureFlagsClassic,
dumpManager: DumpManager,
) :
@@ -132,6 +134,12 @@ constructor(
val isCurrentGestureOverscroll: Flow<Boolean> =
interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll")
+ /** Whether remote input is currently active for any notification. */
+ val isRemoteInputActive = remoteInputInteractor.isRemoteInputActive
+
+ /** The bottom bound of the currently focused remote input notification row. */
+ val remoteInputRowBottomBound = remoteInputInteractor.remoteInputRowBottomBound
+
/** Sets whether the notification stack is scrolled to the top. */
fun setScrolledToTop(scrolledToTop: Boolean) {
interactor.setScrolledToTop(scrolledToTop)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 31776cf5ad1b..16d5f8d30593 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -106,7 +106,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private static final long FOCUS_ANIMATION_CROSSFADE_DURATION = 50;
private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33;
private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83;
- private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
+ public static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
private static final long DEFOCUS_ANIMATION_FADE_OUT_DELAY = 120;
private static final long DEFOCUS_ANIMATION_CROSSFADE_DELAY = 180;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index dbeaa59cd219..ba45942177a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -27,7 +27,10 @@ import com.android.settingslib.notification.modes.ZenIcon
import com.android.settingslib.notification.modes.ZenIconLoader
import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
import java.time.Duration
@@ -51,7 +54,17 @@ constructor(
private val notificationSettingsRepository: NotificationSettingsRepository,
@Background private val bgDispatcher: CoroutineDispatcher,
private val iconLoader: ZenIconLoader,
+ private val deviceProvisioningRepository: DeviceProvisioningRepository,
+ private val userSetupRepository: UserSetupRepository,
) {
+ val isZenAvailable: Flow<Boolean> =
+ combine(
+ deviceProvisioningRepository.isDeviceProvisioned,
+ userSetupRepository.isUserSetUp,
+ ) { isDeviceProvisioned, isUserSetUp ->
+ isDeviceProvisioned && isUserSetUp
+ }
+
val isZenModeEnabled: Flow<Boolean> =
zenModeRepository.globalZenMode
.map {
@@ -80,6 +93,18 @@ constructor(
val modes: Flow<List<ZenMode>> = zenModeRepository.modes
+ /**
+ * Returns the special "manual DND" mode.
+ *
+ * This is only meant as a temporary solution for "legacy" UI pieces that handle DND
+ * specifically; any new or migrated features should use modes more generally, through [modes]
+ * or [activeModes].
+ */
+ val dndMode: Flow<ZenMode?> by lazy {
+ ModesUi.assertInNewMode()
+ zenModeRepository.modes.map { modes -> modes.singleOrNull { it.isManualDnd } }
+ }
+
/** Flow returning the currently active mode(s), if any. */
val activeModes: Flow<ActiveZenModes> =
modes
@@ -113,10 +138,11 @@ constructor(
Log.e(
TAG,
"Interactor cannot handle showing the zen duration prompt. " +
- "Please use EnableZenModeDialog when this setting is active."
+ "Please use EnableZenModeDialog when this setting is active.",
)
null
}
+
ZEN_DURATION_FOREVER -> null
else -> Duration.ofMinutes(zenDuration.toLong())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index af93880bad51..27bc6d36c1e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -59,32 +59,26 @@ fun ModeTile(viewModel: ModeTileViewModel) {
)
CompositionLocalProvider(LocalContentColor provides contentColor) {
- Surface(
- color = tileColor,
- shape = RoundedCornerShape(16.dp),
- ) {
+ Surface(color = tileColor, shape = RoundedCornerShape(16.dp)) {
Row(
modifier =
Modifier.combinedClickable(
onClick = viewModel.onClick,
onLongClick = viewModel.onLongClick,
- onLongClickLabel = viewModel.onLongClickLabel
+ onLongClickLabel = viewModel.onLongClickLabel,
)
- .padding(20.dp)
+ .padding(16.dp)
.semantics { stateDescription = viewModel.stateDescription },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement =
- Arrangement.spacedBy(
- space = 10.dp,
- alignment = Alignment.Start,
- ),
+ Arrangement.spacedBy(space = 8.dp, alignment = Alignment.Start),
) {
Icon(icon = viewModel.icon, modifier = Modifier.size(24.dp))
Column {
Text(
viewModel.text,
fontWeight = FontWeight.W500,
- modifier = Modifier.tileMarquee().testTag("name")
+ modifier = Modifier.tileMarquee().testTag("name"),
)
Text(
viewModel.subtext,
@@ -94,7 +88,7 @@ fun ModeTile(viewModel: ModeTileViewModel) {
.testTag(if (viewModel.enabled) "stateOn" else "stateOff")
.clearAndSetSemantics {
contentDescription = viewModel.subtextDescription
- }
+ },
)
}
}
@@ -103,8 +97,5 @@ fun ModeTile(viewModel: ModeTileViewModel) {
}
private fun Modifier.tileMarquee(): Modifier {
- return this.basicMarquee(
- iterations = 1,
- initialDelayMillis = 200,
- )
+ return this.basicMarquee(iterations = 1)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
index 73d361f69eac..5953ea598929 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.policy.ui.dialog.composable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.runtime.Composable
@@ -27,23 +26,20 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.Flags
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
@Composable
fun ModeTileGrid(viewModel: ModesDialogViewModel) {
val tiles by viewModel.tiles.collectAsStateWithLifecycle(initialValue = emptyList())
- // TODO(b/346519570): Handle what happens when we have more than a few modes.
LazyVerticalGrid(
- columns = GridCells.Fixed(2),
- modifier = Modifier.padding(8.dp).fillMaxWidth().heightIn(max = 300.dp),
+ columns = GridCells.Fixed(if (Flags.modesDialogSingleRows()) 1 else 2),
+ modifier = Modifier.fillMaxWidth().heightIn(max = 300.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
- items(
- tiles.size,
- key = { index -> tiles[index].id },
- ) { index ->
+ items(tiles.size, key = { index -> tiles[index].id }) { index ->
ModeTile(viewModel = tiles[index])
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
new file mode 100644
index 000000000000..421e5c45bbfe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 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.window
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.fragments.FragmentHostManager
+import java.util.Optional
+
+/** Encapsulates all logic for the status bar window state management. */
+interface StatusBarWindowController {
+ val statusBarHeight: Int
+
+ /** Rereads the status bar height and reapplies the current state if the height is different. */
+ fun refreshStatusBarHeight()
+
+ /** Adds the status bar view to the window manager. */
+ fun attach()
+
+ /** Adds the given view to the status bar window view. */
+ fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams)
+
+ /** Returns the status bar window's background view. */
+ val backgroundView: View
+
+ /** Returns a fragment host manager for the status bar window view. */
+ val fragmentHostManager: FragmentHostManager
+
+ /**
+ * Provides an updated animation controller if we're animating a view in the status bar.
+ *
+ * This is needed because we have to make sure that the status bar window matches the full
+ * screen during the animation and that we are expanding the view below the other status bar
+ * text.
+ *
+ * @param rootView the root view of the animation
+ * @param animationController the default animation controller to use
+ * @return If the animation is on a view in the status bar, returns an Optional containing an
+ * updated animation controller that handles status-bar-related animation details. Returns an
+ * empty optional if the animation is *not* on a view in the status bar.
+ */
+ fun wrapAnimationControllerIfInStatusBar(
+ rootView: View,
+ animationController: ActivityTransitionAnimator.Controller,
+ ): Optional<ActivityTransitionAnimator.Controller>
+
+ /** Set force status bar visible. */
+ fun setForceStatusBarVisible(forceStatusBarVisible: Boolean)
+
+ /**
+ * Sets whether an ongoing process requires the status bar to be forced visible.
+ *
+ * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
+ * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
+ * false but this method is set to true, then the status bar **will** be visible.
+ *
+ * TODO(b/195839150): We should likely merge this method and {@link
+ * this#setForceStatusBarVisible} together and use some sort of ranking system instead.
+ */
+ fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index c30a6b7d0f17..1a0327cdd809 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -46,6 +46,8 @@ import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
+import androidx.annotation.NonNull;
+
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.animation.ActivityTransitionAnimator;
@@ -67,7 +69,7 @@ import javax.inject.Inject;
* Encapsulates all logic for the status bar window state management.
*/
@SysUISingleton
-public class StatusBarWindowController {
+public class StatusBarWindowControllerImpl implements StatusBarWindowController {
private static final String TAG = "StatusBarWindowController";
private static final boolean DEBUG = false;
@@ -89,7 +91,7 @@ public class StatusBarWindowController {
private final Binder mInsetsSourceOwner = new Binder();
@Inject
- public StatusBarWindowController(
+ public StatusBarWindowControllerImpl(
Context context,
@StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView,
ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
@@ -117,14 +119,12 @@ public class StatusBarWindowController {
/* attachedViewProvider=*/ () -> mStatusBarWindowView)));
}
+ @Override
public int getStatusBarHeight() {
return mBarHeight;
}
- /**
- * Rereads the status bar height and reapplys the current state if the height
- * is different.
- */
+ @Override
public void refreshStatusBarHeight() {
Trace.beginSection("StatusBarWindowController#refreshStatusBarHeight");
try {
@@ -141,9 +141,7 @@ public class StatusBarWindowController {
}
}
- /**
- * Adds the status bar view to the window manager.
- */
+ @Override
public void attach() {
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
@@ -161,54 +159,47 @@ public class StatusBarWindowController {
apply(mCurrentState);
}
- /** Adds the given view to the status bar window view. */
- public void addViewToWindow(View view, ViewGroup.LayoutParams layoutParams) {
+ @Override
+ public void addViewToWindow(@NonNull View view, @NonNull ViewGroup.LayoutParams layoutParams) {
mStatusBarWindowView.addView(view, layoutParams);
}
- /** Returns the status bar window's background view. */
+ @NonNull
+ @Override
public View getBackgroundView() {
return mStatusBarWindowView.findViewById(R.id.status_bar_container);
}
- /** Returns a fragment host manager for the status bar window view. */
+ @NonNull
+ @Override
public FragmentHostManager getFragmentHostManager() {
return mFragmentService.getFragmentHostManager(mStatusBarWindowView);
}
- /**
- * Provides an updated animation controller if we're animating a view in the status bar.
- *
- * This is needed because we have to make sure that the status bar window matches the full
- * screen during the animation and that we are expanding the view below the other status bar
- * text.
- *
- * @param rootView the root view of the animation
- * @param animationController the default animation controller to use
- * @return If the animation is on a view in the status bar, returns an Optional containing an
- * updated animation controller that handles status-bar-related animation details. Returns an
- * empty optional if the animation is *not* on a view in the status bar.
- */
+ @NonNull
+ @Override
public Optional<ActivityTransitionAnimator.Controller> wrapAnimationControllerIfInStatusBar(
- View rootView, ActivityTransitionAnimator.Controller animationController) {
+ @NonNull View rootView,
+ @NonNull ActivityTransitionAnimator.Controller animationController) {
if (rootView != mStatusBarWindowView) {
return Optional.empty();
}
animationController.setTransitionContainer(mLaunchAnimationContainer);
- return Optional.of(new DelegateTransitionAnimatorController(animationController) {
- @Override
- public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
- getDelegate().onTransitionAnimationStart(isExpandingFullyAbove);
- setLaunchAnimationRunning(true);
- }
-
- @Override
- public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) {
- getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove);
- setLaunchAnimationRunning(false);
- }
- });
+ return Optional.of(
+ new DelegateTransitionAnimatorController(animationController) {
+ @Override
+ public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
+ getDelegate().onTransitionAnimationStart(isExpandingFullyAbove);
+ setLaunchAnimationRunning(true);
+ }
+
+ @Override
+ public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) {
+ getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove);
+ setLaunchAnimationRunning(false);
+ }
+ });
}
private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
@@ -275,22 +266,13 @@ public class StatusBarWindowController {
}
}
- /** Set force status bar visible. */
+ @Override
public void setForceStatusBarVisible(boolean forceStatusBarVisible) {
mCurrentState.mForceStatusBarVisible = forceStatusBarVisible;
apply(mCurrentState);
}
- /**
- * Sets whether an ongoing process requires the status bar to be forced visible.
- *
- * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
- * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
- * false but this method is set to true, then the status bar **will** be visible.
- *
- * TODO(b/195839150): We should likely merge this method and
- * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead.
- */
+ @Override
public void setOngoingProcessRequiresStatusBarVisible(boolean visible) {
mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible;
apply(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
index 1a41987a8e5b..80ea925eabc7 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/domain/interactor/TouchpadGesturesInteractor.kt
@@ -30,12 +30,12 @@ class TouchpadGesturesInteractor(
private val logger: InputDeviceTutorialLogger,
) {
fun disableGestures() {
- logger.log("Disabling touchpad gestures across the system")
+ logger.d("Disabling touchpad gestures across the system")
setGesturesState(disabled = true)
}
fun enableGestures() {
- logger.log("Enabling touchpad gestures across the system")
+ logger.d("Enabling touchpad gestures across the system")
setGesturesState(disabled = false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index 5a77c04924dd..6acc891e93d5 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -78,21 +78,21 @@ private fun TutorialSelectionButtons(
modifier = modifier
) {
TutorialButton(
- text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
- onClick = onBackTutorialClicked,
+ text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
+ onClick = onHomeTutorialClicked,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.weight(1f)
)
TutorialButton(
- text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
- onClick = onHomeTutorialClicked,
- color = MaterialTheme.colorScheme.secondary,
+ text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
+ onClick = onBackTutorialClicked,
+ color = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.weight(1f)
)
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button),
onClick = onRecentAppsTutorialClicked,
- color = MaterialTheme.colorScheme.tertiary,
+ color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.weight(1f)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index 46ea352ff85a..d03b2e717398 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -29,12 +29,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
-import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen
-import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.ACTION_KEY
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.RECENT_APPS_GESTURE
@@ -59,7 +57,7 @@ constructor(
}
// required to handle 3+ fingers on touchpad
window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
- window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS)
+ logger.logOpenTutorial(TutorialContext.TOUCHPAD_TUTORIAL)
}
private fun finishTutorial() {
@@ -104,10 +102,5 @@ fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> U
onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
onBack = { vm.goTo(TUTORIAL_SELECTION) },
)
- ACTION_KEY -> // TODO(b/358105049) move action key tutorial to OOBE flow
- ActionKeyTutorialScreen(
- onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
- onBack = { vm.goTo(TUTORIAL_SELECTION) },
- )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
index 599e1b1eff75..c56dcf3bf062 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt
@@ -65,5 +65,4 @@ enum class Screen {
BACK_GESTURE,
HOME_GESTURE,
RECENT_APPS_GESTURE,
- ACTION_KEY,
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index db4f9ef13bd6..7166428d863f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -35,7 +35,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
-import static com.android.systemui.Flags.hapticVolumeSlider;
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
@@ -928,10 +927,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
}
private void addSliderHapticsToRow(VolumeRow row) {
- if (hapticVolumeSlider()) {
- row.createPlugin(mVibratorHelper, mSystemClock);
- HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
- }
+ row.createPlugin(mVibratorHelper, mSystemClock);
+ HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
}
@VisibleForTesting void addSliderHapticsToRows() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 9aaf2958031a..a940bc9b3e20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -8,7 +8,8 @@ import android.content.pm.ApplicationInfo
import android.graphics.Point
import android.graphics.Rect
import android.os.Looper
-import android.platform.test.flag.junit.SetFlagsRule
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import android.view.IRemoteAnimationFinishedCallback
import android.view.RemoteAnimationAdapter
@@ -63,7 +64,6 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
private lateinit var activityTransitionAnimator: ActivityTransitionAnimator
@get:Rule val rule = MockitoJUnit.rule()
- @get:Rule val setFlagsRule = SetFlagsRule()
@Before
fun setup() {
@@ -90,7 +90,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
animator: ActivityTransitionAnimator = this.activityTransitionAnimator,
controller: ActivityTransitionAnimator.Controller? = this.controller,
animate: Boolean = true,
- intentStarter: (RemoteAnimationAdapter?) -> Int
+ intentStarter: (RemoteAnimationAdapter?) -> Int,
) {
// We start in a new thread so that we can ensure that the callbacks are called in the main
// thread.
@@ -98,7 +98,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
animator.startIntentWithAnimation(
controller = controller,
animate = animate,
- intentStarter = intentStarter
+ intentStarter = intentStarter,
)
}
.join()
@@ -175,9 +175,9 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
assertFalse(willAnimateCaptor.value)
}
+ @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
@Test
fun registersReturnIffCookieIsPresent() {
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
`when`(callback.isOnKeyguard()).thenReturn(false)
startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
@@ -203,10 +203,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
}
+ @EnableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
@Test
fun registersLongLivedTransition() {
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
-
activityTransitionAnimator.register(
object : DelegateTransitionAnimatorController(controller) {
override val transitionCookie =
@@ -226,10 +228,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
assertEquals(4, testShellTransitions.remotes.size)
}
+ @EnableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
@Test
fun registersLongLivedTransitionOverridingPreviousRegistration() {
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
-
val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie")
activityTransitionAnimator.register(
object : DelegateTransitionAnimatorController(controller) {
@@ -251,9 +255,9 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
}
+ @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {
- setFlagsRule.disableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
val controller =
object : DelegateTransitionAnimatorController(controller) {
@@ -266,9 +270,9 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
}
+ @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() {
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
// No TransitionCookie
val controllerWithoutCookie =
@@ -310,9 +314,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
}
+ @EnableFlags(
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ )
@Test
fun unregistersLongLivedTransition() {
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
val cookies = arrayOfNulls<ActivityTransitionAnimator.TransitionCookie>(3)
@@ -411,7 +418,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
SurfaceControl(),
Rect(),
taskInfo,
- false
+ false,
)
}
}
@@ -430,7 +437,7 @@ private class FakeShellTransitions : ShellTransitions {
override fun registerRemoteForTakeover(
filter: TransitionFilter,
- remoteTransition: RemoteTransition
+ remoteTransition: RemoteTransition,
) {
remotesForTakeover[filter] = remoteTransition
}
@@ -460,7 +467,7 @@ private class TestTransitionAnimatorController(override var transitionContainer:
left = 300,
right = 400,
topCornerRadius = 10f,
- bottomCornerRadius = 20f
+ bottomCornerRadius = 20f,
)
private fun assertOnMainThread() {
@@ -480,7 +487,7 @@ private class TestTransitionAnimatorController(override var transitionContainer:
override fun onTransitionAnimationProgress(
state: TransitionAnimator.State,
progress: Float,
- linearProgress: Float
+ linearProgress: Float,
) {
assertOnMainThread()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
index 4b61a0d02f1e..088bb02512b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
@@ -25,6 +25,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.ui.viewmodel.patternBouncerViewModelFactory
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.motion.createSysUiComposeMotionTestRule
import com.android.systemui.testKosmos
@@ -55,6 +56,7 @@ class PatternBouncerTest : SysuiTestCase() {
kosmos.patternBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true).asStateFlow(),
onIntentionalUserInput = {},
+ bouncerHapticPlayer = kosmos.bouncerHapticPlayer,
)
@Before
@@ -75,11 +77,11 @@ class PatternBouncerTest : SysuiTestCase() {
content = { play -> if (play) PatternBouncerUnderTest() },
ComposeRecordingSpec.until(
recordBefore = false,
- checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) }
+ checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) },
) {
feature(MotionTestKeys.dotAppearFadeIn, floatArray)
feature(MotionTestKeys.dotAppearMoveUp, floatArray)
- }
+ },
)
assertThat(motion).timeSeriesMatchesGolden()
@@ -100,7 +102,7 @@ class PatternBouncerTest : SysuiTestCase() {
viewModel.onDragEnd()
// Failure animation starts when animateFailure flips to true...
viewModel.animateFailure.takeWhile { !it }.collect {}
- }
+ },
) {
// ... and ends when the composable flips it back to false.
viewModel.animateFailure.takeWhile { it }.collect {}
@@ -111,7 +113,7 @@ class PatternBouncerTest : SysuiTestCase() {
content = { PatternBouncerUnderTest() },
ComposeRecordingSpec(failureAnimationMotionControl) {
feature(MotionTestKeys.dotScaling, floatArray)
- }
+ },
)
assertThat(motion).timeSeriesMatchesGolden()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index c65a1176a55b..d72b72c3d21e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -32,6 +32,7 @@ import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.os.PersistableBundle;
+import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
@@ -101,8 +102,18 @@ public class ClipboardListenerTest extends SysuiTestCase {
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
- mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
- mClipboardToast, mClipboardManager, mKeyguardManager, mUiEventLogger);
+ mClipboardListener = new ClipboardListener(
+ getContext(),
+ mOverlayControllerProvider,
+ mClipboardToast,
+ user -> {
+ if (UserHandle.CURRENT.equals(user)) {
+ return mClipboardManager;
+ }
+ return null;
+ },
+ mKeyguardManager,
+ mUiEventLogger);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 411ff91ebc2f..8731853e4939 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -77,7 +77,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
private static final int TEST_CURRENT_VOLUME = 10;
// Mock
- private MediaOutputController mMediaOutputController = mock(MediaOutputController.class);
+ private MediaSwitchingController mMediaSwitchingController =
+ mock(MediaSwitchingController.class);
private MediaOutputDialog mMediaOutputDialog = mock(MediaOutputDialog.class);
private MediaDevice mMediaDevice1 = mock(MediaDevice.class);
private MediaDevice mMediaDevice2 = mock(MediaDevice.class);
@@ -95,13 +96,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Before
public void setUp() {
- when(mMediaOutputController.getMediaItemList()).thenReturn(mMediaItems);
- when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
- when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
- when(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat);
- when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
- when(mMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1);
- when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true);
+ when(mMediaSwitchingController.getMediaItemList()).thenReturn(mMediaItems);
+ when(mMediaSwitchingController.hasAdjustVolumeUserRestriction()).thenReturn(false);
+ when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false);
+ when(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat);
+ when(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
+ when(mMediaSwitchingController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1);
+ when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true);
when(mIconCompat.toIcon(mContext)).thenReturn(mIcon);
when(mMediaDevice1.getName()).thenReturn(TEST_DEVICE_NAME_1);
when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_ID_1);
@@ -116,7 +117,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1));
mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2));
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -142,7 +143,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindPairNew_verifyView() {
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -161,11 +162,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindGroup_withSessionName_verifyView() {
- when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
- mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
- Collectors.toList()));
- when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ when(mMediaSwitchingController.getSelectedMediaDevice())
+ .thenReturn(
+ mMediaItems.stream()
+ .map((item) -> item.getMediaDevice().get())
+ .collect(Collectors.toList()));
+ when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -181,11 +184,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindGroup_noSessionName_verifyView() {
- when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
- mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
- Collectors.toList()));
- when(mMediaOutputController.getSessionName()).thenReturn(null);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ when(mMediaSwitchingController.getSelectedMediaDevice())
+ .thenReturn(
+ mMediaItems.stream()
+ .map((item) -> item.getMediaDevice().get())
+ .collect(Collectors.toList()));
+ when(mMediaSwitchingController.getSessionName()).thenReturn(null);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -214,7 +219,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindNonRemoteConnectedDevice_verifyView() {
- when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
+ when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -230,9 +235,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindConnectedRemoteDevice_verifyView() {
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of(mMediaDevice2));
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice())
+ .thenReturn(ImmutableList.of(mMediaDevice2));
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -249,9 +254,9 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindConnectedRemoteDevice_verifyContentDescriptionNotNull() {
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of(mMediaDevice2));
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice())
+ .thenReturn(ImmutableList.of(mMediaDevice2));
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -263,9 +268,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindSingleConnectedRemoteDevice_verifyView() {
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of());
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of());
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -283,9 +287,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindConnectedRemoteDeviceWithOnGoingSession_verifyView() {
when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of());
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of());
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -305,9 +308,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
public void onBindViewHolder_bindConnectedRemoteDeviceWithHostOnGoingSession_verifyView() {
when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of());
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(ImmutableList.of());
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -326,8 +328,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindConnectedDeviceWithMutingExpectedDeviceExist_verifyView() {
- when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+ when(mMediaSwitchingController.hasMutingExpectedDevice()).thenReturn(true);
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
@@ -340,8 +342,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_isMutingExpectedDevice_verifyView() {
when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(true);
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
- when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+ when(mMediaSwitchingController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -378,14 +380,14 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
mOnSeekBarChangeListenerCaptor.getValue().onStopTrackingTouch(mViewHolder.mSeekBar);
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
- verify(mMediaOutputController).logInteractionAdjustVolume(mMediaDevice1);
+ verify(mMediaSwitchingController).logInteractionAdjustVolume(mMediaDevice1);
}
@Test
public void onBindViewHolder_bindSelectableDevice_verifyView() {
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice2);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
@@ -440,7 +442,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void subStatusSupported_onBindViewHolder_bindHostDeviceWithOngoingSession_verifyView() {
- when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
+ when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true);
when(mMediaDevice1.hasSubtext()).thenReturn(true);
when(mMediaDevice1.getSubtext()).thenReturn(SUBTEXT_CUSTOM);
@@ -540,7 +542,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_inTransferring_bindTransferringDevice_verifyView() {
- when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
+ when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(true);
when(mMediaDevice1.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -556,7 +558,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindGroupingDevice_verifyView() {
- when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
+ when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false);
when(mMediaDevice1.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_GROUPING);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -572,7 +574,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_inTransferring_bindNonTransferringDevice_verifyView() {
- when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
+ when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(true);
when(mMediaDevice2.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -586,7 +588,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onItemClick_clickPairNew_verifyLaunchBluetoothPairing() {
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -595,16 +597,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
mViewHolder.mContainerLayout.performClick();
- verify(mMediaOutputController).launchBluetoothPairing(mViewHolder.mContainerLayout);
+ verify(mMediaSwitchingController).launchBluetoothPairing(mViewHolder.mContainerLayout);
}
@Test
public void onItemClick_clickDevice_verifyConnectDevice() {
- when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
+ when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
assertThat(mMediaDevice2.getState()).isEqualTo(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -613,16 +615,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
mViewHolder.mContainerLayout.performClick();
- verify(mMediaOutputController).connectDevice(mMediaDevice2);
+ verify(mMediaSwitchingController).connectDevice(mMediaDevice2);
}
@Test
public void onItemClick_clickDeviceWithSessionOngoing_verifyShowsDialog() {
- when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(true);
+ when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(true);
assertThat(mMediaDevice2.getState()).isEqualTo(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER);
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -633,66 +635,68 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 1);
spyMediaDeviceViewHolder.mContainerLayout.performClick();
- verify(mMediaOutputController, never()).connectDevice(mMediaDevice2);
+ verify(mMediaSwitchingController, never()).connectDevice(mMediaDevice2);
verify(spyMediaDeviceViewHolder).showCustomEndSessionDialog(mMediaDevice2);
}
@Test
public void onItemClick_clicksWithMutingExpectedDeviceExist_cancelsMuteAwaitConnection() {
- when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
- when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
+ when(mMediaSwitchingController.isAnyDeviceTransferring()).thenReturn(false);
+ when(mMediaSwitchingController.hasMutingExpectedDevice()).thenReturn(true);
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(false);
when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(false);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mViewHolder.mContainerLayout.performClick();
- verify(mMediaOutputController).cancelMuteAwaitConnection();
+ verify(mMediaSwitchingController).cancelMuteAwaitConnection();
}
@Test
public void onGroupActionTriggered_clicksEndAreaOfSelectableDevice_triggerGrouping() {
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice2);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
mViewHolder.mEndTouchArea.performClick();
- verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2);
+ verify(mMediaSwitchingController).addDeviceToPlayMedia(mMediaDevice2);
}
@Test
public void onGroupActionTriggered_clickSelectedRemoteDevice_triggerUngrouping() {
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
- ImmutableList.of(mMediaDevice2));
- when(mMediaOutputController.getDeselectableMediaDevice()).thenReturn(
- ImmutableList.of(mMediaDevice1));
- when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice())
+ .thenReturn(ImmutableList.of(mMediaDevice2));
+ when(mMediaSwitchingController.getDeselectableMediaDevice())
+ .thenReturn(ImmutableList.of(mMediaDevice1));
+ when(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).thenReturn(true);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mViewHolder.mEndTouchArea.performClick();
- verify(mMediaOutputController).removeDeviceFromPlayMedia(mMediaDevice1);
+ verify(mMediaSwitchingController).removeDeviceFromPlayMedia(mMediaDevice1);
}
@Test
public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
- when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
- mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
- Collectors.toList()));
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ when(mMediaSwitchingController.getSelectedMediaDevice())
+ .thenReturn(
+ mMediaItems.stream()
+ .map((item) -> item.getMediaDevice().get())
+ .collect(Collectors.toList()));
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice1);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
- when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ when(mMediaSwitchingController.hasAdjustVolumeUserRestriction()).thenReturn(true);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mViewHolder.mContainerLayout.performClick();
@@ -702,11 +706,11 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_volumeControlChangeToEnabled_enableSeekbarAgain() {
- when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false);
+ when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
- when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
+ when(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mSeekBar.isEnabled()).isTrue();
@@ -719,7 +723,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
mMediaOutputAdapter.updateColorScheme(wallpaperColors, true);
- verify(mMediaOutputController).setCurrentColorScheme(wallpaperColors, true);
+ verify(mMediaSwitchingController).setCurrentColorScheme(wallpaperColors, true);
}
@Test
@@ -727,7 +731,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
mMediaOutputAdapter.updateItems();
List<MediaItem> updatedList = new ArrayList<>();
updatedList.add(MediaItem.createPairNewDeviceMediaItem());
- when(mMediaOutputController.getMediaItemList()).thenReturn(updatedList);
+ when(mMediaSwitchingController.getMediaItemList()).thenReturn(updatedList);
assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaItems.size());
mMediaOutputAdapter.updateItems();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index c8cc6b5fdf93..47371dfd8895 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -104,7 +104,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
- private MediaOutputController mMediaOutputController;
+ private MediaSwitchingController mMediaSwitchingController;
private int mHeaderIconRes;
private IconCompat mIconCompat;
private CharSequence mHeaderTitle;
@@ -132,8 +132,8 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor(
mKosmos);
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mContext,
TEST_PACKAGE,
mContext.getUser(),
@@ -153,12 +153,13 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
// Using a fake package will cause routing operations to fail, so we intercept
// scanning-related operations.
- mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
- doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
- doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
+ mMediaSwitchingController.mLocalMediaManager = mock(LocalMediaManager.class);
+ doNothing().when(mMediaSwitchingController.mLocalMediaManager).startScan();
+ doNothing().when(mMediaSwitchingController.mLocalMediaManager).stopScan();
- mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
- mMediaOutputController);
+ mMediaOutputBaseDialogImpl =
+ new MediaOutputBaseDialogImpl(
+ mContext, mBroadcastSender, mMediaSwitchingController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
}
@@ -176,7 +177,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
public void refresh_withIconCompat_iconIsVisible() {
mIconCompat = IconCompat.createWithBitmap(
Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888));
- when(mMediaOutputBaseAdapter.getController()).thenReturn(mMediaOutputController);
+ when(mMediaOutputBaseAdapter.getController()).thenReturn(mMediaSwitchingController);
mMediaOutputBaseDialogImpl.refresh();
final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
@@ -263,7 +264,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
when(mMediaOutputBaseAdapter.isDragging()).thenReturn(true);
mMediaOutputBaseDialogImpl.refresh();
- assertThat(mMediaOutputController.isRefreshing()).isFalse();
+ assertThat(mMediaSwitchingController.isRefreshing()).isFalse();
}
@Test
@@ -335,12 +336,14 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog {
- MediaOutputBaseDialogImpl(Context context, BroadcastSender broadcastSender,
- MediaOutputController mediaOutputController) {
+ MediaOutputBaseDialogImpl(
+ Context context,
+ BroadcastSender broadcastSender,
+ MediaSwitchingController mediaSwitchingController) {
super(
context,
broadcastSender,
- mediaOutputController, /* includePlaybackAndAppMetadata */
+ mediaSwitchingController, /* includePlaybackAndAppMetadata */
true);
mAdapter = mMediaOutputBaseAdapter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 189a56145d27..f0902e35b837 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -119,7 +119,7 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
private UserTracker mUserTracker = mock(UserTracker.class);
private MediaOutputBroadcastDialog mMediaOutputBroadcastDialog;
- private MediaOutputController mMediaOutputController;
+ private MediaSwitchingController mMediaSwitchingController;
@Before
public void setUp() {
@@ -133,8 +133,8 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor(
mKosmos);
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mContext,
TEST_PACKAGE,
mContext.getUser(),
@@ -151,9 +151,10 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
mFlags,
volumePanelGlobalStateInteractor,
mUserTracker);
- mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
- mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
- mBroadcastSender, mMediaOutputController);
+ mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager;
+ mMediaOutputBroadcastDialog =
+ new MediaOutputBroadcastDialog(
+ mContext, false, mBroadcastSender, mMediaSwitchingController);
mMediaOutputBroadcastDialog.show();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 90c2930f8e49..d3ecb3d8c944 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -119,7 +119,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputDialog mMediaOutputDialog;
- private MediaOutputController mMediaOutputController;
+ private MediaSwitchingController mMediaSwitchingController;
private final List<String> mFeatures = new ArrayList<>();
@Override
@@ -146,8 +146,8 @@ public class MediaOutputDialogTest extends SysuiTestCase {
VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor(
mKosmos);
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mContext,
TEST_PACKAGE,
mContext.getUser(),
@@ -164,8 +164,8 @@ public class MediaOutputDialogTest extends SysuiTestCase {
mFlags,
volumePanelGlobalStateInteractor,
mUserTracker);
- mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
- mMediaOutputDialog = makeTestDialog(mMediaOutputController);
+ mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager;
+ mMediaOutputDialog = makeTestDialog(mMediaSwitchingController);
mMediaOutputDialog.show();
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
@@ -388,12 +388,15 @@ public class MediaOutputDialogTest extends SysuiTestCase {
public void getStopButtonText_notSupportsBroadcast_returnsDefaultText() {
String stopText = mContext.getText(
R.string.media_output_dialog_button_stop_casting).toString();
- MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
- when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
-
- withTestDialog(mockMediaOutputController, testDialog -> {
- assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
- });
+ MediaSwitchingController mockMediaSwitchingController =
+ mock(MediaSwitchingController.class);
+ when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(false);
+
+ withTestDialog(
+ mockMediaSwitchingController,
+ testDialog -> {
+ assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+ });
}
@Test
@@ -401,28 +404,35 @@ public class MediaOutputDialogTest extends SysuiTestCase {
public void getStopButtonText_supportsBroadcast_returnsBroadcastText() {
String stopText = mContext.getText(R.string.media_output_broadcast).toString();
MediaDevice mMediaDevice = mock(MediaDevice.class);
- MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
- when(mockMediaOutputController.isBroadcastSupported()).thenReturn(true);
- when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice);
- when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true);
- when(mockMediaOutputController.isPlaying()).thenReturn(true);
- when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false);
- withTestDialog(mockMediaOutputController, testDialog -> {
- assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
- });
+ MediaSwitchingController mockMediaSwitchingController =
+ mock(MediaSwitchingController.class);
+ when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(true);
+ when(mockMediaSwitchingController.getCurrentConnectedMediaDevice())
+ .thenReturn(mMediaDevice);
+ when(mockMediaSwitchingController.isBluetoothLeDevice(any())).thenReturn(true);
+ when(mockMediaSwitchingController.isPlaying()).thenReturn(true);
+ when(mockMediaSwitchingController.isBluetoothLeBroadcastEnabled()).thenReturn(false);
+ withTestDialog(
+ mockMediaSwitchingController,
+ testDialog -> {
+ assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+ });
}
@Test
public void onStopButtonClick_notPlaying_releaseSession() {
- MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
- when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
- when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null);
- when(mockMediaOutputController.isPlaying()).thenReturn(false);
- withTestDialog(mockMediaOutputController, testDialog -> {
- testDialog.onStopButtonClick();
- });
-
- verify(mockMediaOutputController).releaseSession();
+ MediaSwitchingController mockMediaSwitchingController =
+ mock(MediaSwitchingController.class);
+ when(mockMediaSwitchingController.isBroadcastSupported()).thenReturn(false);
+ when(mockMediaSwitchingController.getCurrentConnectedMediaDevice()).thenReturn(null);
+ when(mockMediaSwitchingController.isPlaying()).thenReturn(false);
+ withTestDialog(
+ mockMediaSwitchingController,
+ testDialog -> {
+ testDialog.onStopButtonClick();
+ });
+
+ verify(mockMediaSwitchingController).releaseSession();
verify(mDialogTransitionAnimator).disableAllCurrentDialogsExitAnimations();
}
@@ -430,14 +440,14 @@ public class MediaOutputDialogTest extends SysuiTestCase {
// Check the visibility metric logging by creating a new MediaOutput dialog,
// and verify if the calling times increases.
public void onCreate_ShouldLogVisibility() {
- withTestDialog(mMediaOutputController, testDialog -> {});
+ withTestDialog(mMediaSwitchingController, testDialog -> {});
verify(mUiEventLogger, times(2))
.log(MediaOutputDialog.MediaOutputEvent.MEDIA_OUTPUT_DIALOG_SHOW);
}
@NonNull
- private MediaOutputDialog makeTestDialog(MediaOutputController controller) {
+ private MediaOutputDialog makeTestDialog(MediaSwitchingController controller) {
return new MediaOutputDialog(
mContext,
false,
@@ -448,7 +458,8 @@ public class MediaOutputDialogTest extends SysuiTestCase {
true);
}
- private void withTestDialog(MediaOutputController controller, Consumer<MediaOutputDialog> c) {
+ private void withTestDialog(
+ MediaSwitchingController controller, Consumer<MediaOutputDialog> c) {
MediaOutputDialog testDialog = makeTestDialog(controller);
testDialog.show();
c.accept(testDialog);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index 714fad9d7478..d3e20c6e39b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -43,6 +43,7 @@ import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaDescription;
import android.media.MediaMetadata;
@@ -58,6 +59,7 @@ import android.os.Bundle;
import android.os.PowerExemptionManager;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
import android.text.TextUtils;
@@ -67,8 +69,10 @@ import androidx.core.graphics.drawable.IconCompat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.media.flags.Flags;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.media.InputMediaDevice;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.SysuiTestCase;
@@ -101,7 +105,7 @@ import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class MediaOutputControllerTest extends SysuiTestCase {
+public class MediaSwitchingControllerTest extends SysuiTestCase {
private static final String TEST_DEVICE_1_ID = "test_device_1_id";
private static final String TEST_DEVICE_2_ID = "test_device_2_id";
private static final String TEST_DEVICE_3_ID = "test_device_3_id";
@@ -126,8 +130,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager;
@Mock
private LocalBluetoothManager mLocalBluetoothManager;
- @Mock
- private MediaOutputController.Callback mCb;
+ @Mock private MediaSwitchingController.Callback mCb;
@Mock
private MediaDevice mMediaDevice1;
@Mock
@@ -166,7 +169,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
private FeatureFlags mFlags = mock(FeatureFlags.class);
private View mDialogLaunchView = mock(View.class);
- private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
+ private MediaSwitchingController.Callback mCallback =
+ mock(MediaSwitchingController.Callback.class);
final Notification mNotification = mock(Notification.class);
private final VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor =
@@ -175,7 +179,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
private Context mSpyContext;
private String mPackageName = null;
- private MediaOutputController mMediaOutputController;
+ private MediaSwitchingController mMediaSwitchingController;
private LocalMediaManager mLocalMediaManager;
private List<MediaController> mMediaControllers = new ArrayList<>();
private List<MediaDevice> mMediaDevices = new ArrayList<>();
@@ -203,9 +207,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
mCachedBluetoothDeviceManager);
-
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
mPackageName,
mContext.getUser(),
@@ -222,9 +225,9 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mFlags,
mVolumePanelGlobalStateInteractor,
mUserTracker);
- mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
+ mLocalMediaManager = spy(mMediaSwitchingController.mLocalMediaManager);
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
- mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
+ mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager;
MediaDescription.Builder builder = new MediaDescription.Builder();
builder.setTitle(TEST_SONG);
builder.setSubtitle(TEST_ARTIST);
@@ -264,26 +267,26 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void start_verifyLocalMediaManagerInit() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- verify(mLocalMediaManager).registerCallback(mMediaOutputController);
+ verify(mLocalMediaManager).registerCallback(mMediaSwitchingController);
verify(mLocalMediaManager).startScan();
}
@Test
public void stop_verifyLocalMediaManagerDeinit() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mLocalMediaManager);
- mMediaOutputController.stop();
+ mMediaSwitchingController.stop();
- verify(mLocalMediaManager).unregisterCallback(mMediaOutputController);
+ verify(mLocalMediaManager).unregisterCallback(mMediaSwitchingController);
verify(mLocalMediaManager).stopScan();
}
@Test
public void start_notificationNotFound_mediaControllerInitFromSession() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
verify(mSessionMediaController).registerCallback(any());
}
@@ -291,7 +294,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void start_MediaNotificationFound_mediaControllerNotInitFromSession() {
when(mNotification.isMediaNotification()).thenReturn(true);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
verify(mSessionMediaController, never()).registerCallback(any());
verifyZeroInteractions(mMediaSessionManager);
@@ -299,8 +302,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void start_withoutPackageName_verifyMediaControllerInit() {
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mContext.getUser(),
@@ -318,32 +321,32 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mVolumePanelGlobalStateInteractor,
mUserTracker);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
verify(mSessionMediaController, never()).registerCallback(any());
}
@Test
public void start_nearbyMediaDevicesManagerNotNull_registersNearbyDevicesCallback() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
verify(mNearbyMediaDevicesManager).registerNearbyDevicesCallback(any());
}
@Test
public void stop_withPackageName_verifyMediaControllerDeinit() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mSessionMediaController);
- mMediaOutputController.stop();
+ mMediaSwitchingController.stop();
verify(mSessionMediaController).unregisterCallback(any());
}
@Test
public void stop_withoutPackageName_verifyMediaControllerDeinit() {
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mSpyContext.getUser(),
@@ -361,26 +364,26 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mVolumePanelGlobalStateInteractor,
mUserTracker);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- mMediaOutputController.stop();
+ mMediaSwitchingController.stop();
verify(mSessionMediaController, never()).unregisterCallback(any());
}
@Test
public void stop_nearbyMediaDevicesManagerNotNull_unregistersNearbyDevicesCallback() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mSessionMediaController);
- mMediaOutputController.stop();
+ mMediaSwitchingController.stop();
verify(mNearbyMediaDevicesManager).unregisterNearbyDevicesCallback(any());
}
@Test
public void tryToLaunchMediaApplication_nullIntent_skip() {
- mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView);
+ mMediaSwitchingController.tryToLaunchMediaApplication(mDialogLaunchView);
verify(mCb, never()).dismissDialog();
}
@@ -391,9 +394,9 @@ public class MediaOutputControllerTest extends SysuiTestCase {
.thenReturn(mController);
Intent intent = new Intent(mPackageName);
doReturn(intent).when(mPackageManager).getLaunchIntentForPackage(mPackageName);
- mMediaOutputController.start(mCallback);
+ mMediaSwitchingController.start(mCallback);
- mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView);
+ mMediaSwitchingController.tryToLaunchMediaApplication(mDialogLaunchView);
verify(mStarter).startActivity(any(Intent.class), anyBoolean(),
Mockito.eq(mController));
@@ -403,11 +406,12 @@ public class MediaOutputControllerTest extends SysuiTestCase {
public void tryToLaunchInAppRoutingIntent_componentNameNotNull_startActivity() {
when(mDialogTransitionAnimator.createActivityTransitionController(any(View.class)))
.thenReturn(mController);
- mMediaOutputController.start(mCallback);
+ mMediaSwitchingController.start(mCallback);
when(mLocalMediaManager.getLinkedItemComponentName()).thenReturn(
new ComponentName(mPackageName, ""));
- mMediaOutputController.tryToLaunchInAppRoutingIntent(TEST_DEVICE_1_ID, mDialogLaunchView);
+ mMediaSwitchingController.tryToLaunchInAppRoutingIntent(
+ TEST_DEVICE_1_ID, mDialogLaunchView);
verify(mStarter).startActivity(any(Intent.class), anyBoolean(),
Mockito.eq(mController));
@@ -415,9 +419,9 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void onDevicesUpdated_unregistersNearbyDevicesCallback() throws RemoteException {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- mMediaOutputController.onDevicesUpdated(ImmutableList.of());
+ mMediaSwitchingController.onDevicesUpdated(ImmutableList.of());
verify(mNearbyMediaDevicesManager).unregisterNearbyDevicesCallback(any());
}
@@ -425,11 +429,11 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void onDeviceListUpdate_withNearbyDevices_updatesRangeInformation()
throws RemoteException {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDevicesUpdated(mNearbyDevices);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDevicesUpdated(mNearbyDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
verify(mMediaDevice1).setRangeZone(NearbyDevice.RANGE_FAR);
verify(mMediaDevice2).setRangeZone(NearbyDevice.RANGE_CLOSE);
@@ -438,11 +442,11 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void onDeviceListUpdate_withNearbyDevices_rankByRangeInformation()
throws RemoteException {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDevicesUpdated(mNearbyDevices);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDevicesUpdated(mNearbyDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
assertThat(mMediaDevices.get(0).getId()).isEqualTo(TEST_DEVICE_1_ID);
}
@@ -451,11 +455,11 @@ public class MediaOutputControllerTest extends SysuiTestCase {
public void routeProcessSupport_onDeviceListUpdate_preferenceExist_NotUpdatesRangeInformation()
throws RemoteException {
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDevicesUpdated(mNearbyDevices);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDevicesUpdated(mNearbyDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
verify(mMediaDevice1, never()).setRangeZone(anyInt());
verify(mMediaDevice2, never()).setRangeZone(anyInt());
@@ -463,7 +467,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void onDeviceListUpdate_verifyDeviceListCallback() {
- // This test relies on mMediaOutputController.start being called while the selected device
+ // This test relies on mMediaSwitchingController.start being called while the selected
+ // device
// list has exactly one item, and that item's id is:
// - Different from both ids in mMediaDevices.
// - Different from the id of the route published by the device under test (usually the
@@ -475,12 +480,12 @@ public class MediaOutputControllerTest extends SysuiTestCase {
.when(mLocalMediaManager)
.getSelectedMediaDevice();
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
final List<MediaDevice> devices = new ArrayList<>();
- for (MediaItem item : mMediaOutputController.getMediaItemList()) {
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
if (item.getMediaDevice().isPresent()) {
devices.add(item.getMediaDevice().get());
}
@@ -488,14 +493,15 @@ public class MediaOutputControllerTest extends SysuiTestCase {
assertThat(devices.containsAll(mMediaDevices)).isTrue();
assertThat(devices.size()).isEqualTo(mMediaDevices.size());
- assertThat(mMediaOutputController.getMediaItemList().size()).isEqualTo(
- mMediaDevices.size() + 2);
+ assertThat(mMediaSwitchingController.getMediaItemList().size())
+ .isEqualTo(mMediaDevices.size() + 2);
verify(mCb).onDeviceListChanged();
}
@Test
public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() {
- // This test relies on mMediaOutputController.start being called while the selected device
+ // This test relies on mMediaSwitchingController.start being called while the selected
+ // device
// list has exactly one item, and that item's id is:
// - Different from both ids in mMediaDevices.
// - Different from the id of the route published by the device under test (usually the
@@ -510,12 +516,12 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(mMediaDevice1.getFeatures()).thenReturn(
ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
final List<MediaDevice> devices = new ArrayList<>();
- for (MediaItem item : mMediaOutputController.getMediaItemList()) {
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
if (item.getMediaDevice().isPresent()) {
devices.add(item.getMediaDevice().get());
}
@@ -523,23 +529,72 @@ public class MediaOutputControllerTest extends SysuiTestCase {
assertThat(devices.containsAll(mMediaDevices)).isTrue();
assertThat(devices.size()).isEqualTo(mMediaDevices.size());
- assertThat(mMediaOutputController.getMediaItemList().size()).isEqualTo(
- mMediaDevices.size() + 1);
+ assertThat(mMediaSwitchingController.getMediaItemList().size())
+ .isEqualTo(mMediaDevices.size() + 1);
verify(mCb).onDeviceListChanged();
}
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void onInputDeviceListUpdate_verifyDeviceListCallback() {
+ AudioDeviceInfo[] audioDeviceInfos = {};
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
+ .thenReturn(audioDeviceInfos);
+ mMediaSwitchingController.start(mCb);
+
+ // Output devices have changed.
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ final int MAX_VOLUME = 1;
+ final int CURRENT_VOLUME = 0;
+ final boolean IS_VOLUME_FIXED = true;
+ final MediaDevice mediaDevice3 =
+ InputMediaDevice.create(
+ mContext,
+ TEST_DEVICE_3_ID,
+ AudioDeviceInfo.TYPE_BUILTIN_MIC,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ IS_VOLUME_FIXED);
+ final MediaDevice mediaDevice4 =
+ InputMediaDevice.create(
+ mContext,
+ TEST_DEVICE_4_ID,
+ AudioDeviceInfo.TYPE_WIRED_HEADSET,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ IS_VOLUME_FIXED);
+ final List<MediaDevice> inputDevices = new ArrayList<>();
+ inputDevices.add(mediaDevice3);
+ inputDevices.add(mediaDevice4);
+
+ // Input devices have changed.
+ mMediaSwitchingController.mInputDeviceCallback.onInputDeviceListUpdated(inputDevices);
+
+ final List<MediaDevice> devices = new ArrayList<>();
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
+ if (item.getMediaDevice().isPresent()) {
+ devices.add(item.getMediaDevice().get());
+ }
+ }
+
+ assertThat(devices).containsAtLeastElementsIn(mMediaDevices);
+ assertThat(devices).hasSize(mMediaDevices.size() + inputDevices.size());
+ verify(mCb, atLeastOnce()).onDeviceListChanged();
+ }
+
@Test
public void advanced_categorizeMediaItems_withSuggestedDevice_verifyDeviceListSize() {
when(mMediaDevice1.isSuggestedDevice()).thenReturn(true);
when(mMediaDevice2.isSuggestedDevice()).thenReturn(false);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.getMediaItemList().clear();
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.getMediaItemList().clear();
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
final List<MediaDevice> devices = new ArrayList<>();
int dividerSize = 0;
- for (MediaItem item : mMediaOutputController.getMediaItemList()) {
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
if (item.getMediaDevice().isPresent()) {
devices.add(item.getMediaDevice().get());
}
@@ -556,33 +611,33 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.mIsRefreshing = true;
+ mMediaSwitchingController.mIsRefreshing = true;
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(mMediaOutputController.mNeedRefresh).isTrue();
+ assertThat(mMediaSwitchingController.mNeedRefresh).isTrue();
}
@Test
public void advanced_onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.mIsRefreshing = true;
+ mMediaSwitchingController.mIsRefreshing = true;
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(mMediaOutputController.mNeedRefresh).isTrue();
+ assertThat(mMediaSwitchingController.mNeedRefresh).isTrue();
}
@Test
public void cancelMuteAwaitConnection_cancelsWithMediaManager() {
when(mAudioManager.getMutingExpectedDevice()).thenReturn(mock(AudioDeviceAttributes.class));
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.cancelMuteAwaitConnection();
+ mMediaSwitchingController.cancelMuteAwaitConnection();
verify(mAudioManager).cancelMuteAwaitConnection(any());
}
@@ -590,17 +645,17 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void cancelMuteAwaitConnection_audioManagerIsNull_noAction() {
when(mAudioManager.getMutingExpectedDevice()).thenReturn(null);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.cancelMuteAwaitConnection();
+ mMediaSwitchingController.cancelMuteAwaitConnection();
verify(mAudioManager, never()).cancelMuteAwaitConnection(any());
}
@Test
public void getAppSourceName_packageNameIsNull_returnsNull() {
- MediaOutputController testMediaOutputController =
- new MediaOutputController(
+ MediaSwitchingController testMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
"",
mSpyContext.getUser(),
@@ -617,25 +672,25 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mFlags,
mVolumePanelGlobalStateInteractor,
mUserTracker);
- testMediaOutputController.start(mCb);
+ testMediaSwitchingController.start(mCb);
reset(mCb);
- testMediaOutputController.getAppSourceName();
+ testMediaSwitchingController.getAppSourceName();
- assertThat(testMediaOutputController.getAppSourceName()).isNull();
+ assertThat(testMediaSwitchingController.getAppSourceName()).isNull();
}
@Test
public void isActiveItem_deviceNotConnected_returnsFalse() {
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
- assertThat(mMediaOutputController.isActiveItem(mMediaDevice1)).isFalse();
+ assertThat(mMediaSwitchingController.isActiveItem(mMediaDevice1)).isFalse();
}
@Test
public void getNotificationSmallIcon_packageNameIsNull_returnsNull() {
- MediaOutputController testMediaOutputController =
- new MediaOutputController(
+ MediaSwitchingController testMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
"",
mSpyContext.getUser(),
@@ -652,23 +707,23 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mFlags,
mVolumePanelGlobalStateInteractor,
mUserTracker);
- testMediaOutputController.start(mCb);
+ testMediaSwitchingController.start(mCb);
reset(mCb);
- testMediaOutputController.getAppSourceName();
+ testMediaSwitchingController.getAppSourceName();
- assertThat(testMediaOutputController.getNotificationSmallIcon()).isNull();
+ assertThat(testMediaSwitchingController.getNotificationSmallIcon()).isNull();
}
@Test
public void refreshDataSetIfNeeded_needRefreshIsTrue_setsToFalse() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.mNeedRefresh = true;
+ mMediaSwitchingController.mNeedRefresh = true;
- mMediaOutputController.refreshDataSetIfNeeded();
+ mMediaSwitchingController.refreshDataSetIfNeeded();
- assertThat(mMediaOutputController.mNeedRefresh).isFalse();
+ assertThat(mMediaSwitchingController.mNeedRefresh).isFalse();
}
@Test
@@ -677,13 +732,13 @@ public class MediaOutputControllerTest extends SysuiTestCase {
ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
- assertThat(mMediaOutputController.isCurrentConnectedDeviceRemote()).isTrue();
+ assertThat(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).isTrue();
}
@Test
public void addDeviceToPlayMedia_callsLocalMediaManager() {
- MediaOutputController testMediaOutputController =
- new MediaOutputController(
+ MediaSwitchingController testMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mSpyContext.getUser(),
@@ -702,16 +757,16 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mUserTracker);
LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
- testMediaOutputController.mLocalMediaManager = mockLocalMediaManager;
+ testMediaSwitchingController.mLocalMediaManager = mockLocalMediaManager;
- testMediaOutputController.addDeviceToPlayMedia(mMediaDevice2);
+ testMediaSwitchingController.addDeviceToPlayMedia(mMediaDevice2);
verify(mockLocalMediaManager).addDeviceToPlayMedia(mMediaDevice2);
}
@Test
public void removeDeviceFromPlayMedia_callsLocalMediaManager() {
- MediaOutputController testMediaOutputController =
- new MediaOutputController(
+ MediaSwitchingController testMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mSpyContext.getUser(),
@@ -730,15 +785,15 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mUserTracker);
LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
- testMediaOutputController.mLocalMediaManager = mockLocalMediaManager;
+ testMediaSwitchingController.mLocalMediaManager = mockLocalMediaManager;
- testMediaOutputController.removeDeviceFromPlayMedia(mMediaDevice2);
+ testMediaSwitchingController.removeDeviceFromPlayMedia(mMediaDevice2);
verify(mockLocalMediaManager).removeDeviceFromPlayMedia(mMediaDevice2);
}
@Test
public void getDeselectableMediaDevice_triggersFromLocalMediaManager() {
- mMediaOutputController.getDeselectableMediaDevice();
+ mMediaSwitchingController.getDeselectableMediaDevice();
verify(mLocalMediaManager).getDeselectableMediaDevice();
}
@@ -746,108 +801,108 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void adjustSessionVolume_adjustWithoutId_triggersFromLocalMediaManager() {
int testVolume = 10;
- mMediaOutputController.adjustSessionVolume(testVolume);
+ mMediaSwitchingController.adjustSessionVolume(testVolume);
verify(mLocalMediaManager).adjustSessionVolume(testVolume);
}
@Test
public void logInteractionAdjustVolume_triggersFromMetricLogger() {
- MediaOutputMetricLogger spyMediaOutputMetricLogger = spy(
- mMediaOutputController.mMetricLogger);
- mMediaOutputController.mMetricLogger = spyMediaOutputMetricLogger;
+ MediaOutputMetricLogger spyMediaOutputMetricLogger =
+ spy(mMediaSwitchingController.mMetricLogger);
+ mMediaSwitchingController.mMetricLogger = spyMediaOutputMetricLogger;
- mMediaOutputController.logInteractionAdjustVolume(mMediaDevice1);
+ mMediaSwitchingController.logInteractionAdjustVolume(mMediaDevice1);
verify(spyMediaOutputMetricLogger).logInteractionAdjustVolume(mMediaDevice1);
}
@Test
public void getSessionVolumeMax_triggersFromLocalMediaManager() {
- mMediaOutputController.getSessionVolumeMax();
+ mMediaSwitchingController.getSessionVolumeMax();
verify(mLocalMediaManager).getSessionVolumeMax();
}
@Test
public void getSessionVolume_triggersFromLocalMediaManager() {
- mMediaOutputController.getSessionVolume();
+ mMediaSwitchingController.getSessionVolume();
verify(mLocalMediaManager).getSessionVolume();
}
@Test
public void getSessionName_triggersFromLocalMediaManager() {
- mMediaOutputController.getSessionName();
+ mMediaSwitchingController.getSessionName();
verify(mLocalMediaManager).getSessionName();
}
@Test
public void releaseSession_triggersFromLocalMediaManager() {
- mMediaOutputController.releaseSession();
+ mMediaSwitchingController.releaseSession();
verify(mLocalMediaManager).releaseSession();
}
@Test
public void isAnyDeviceTransferring_noDevicesStateIsConnecting_returnsFalse() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(mMediaOutputController.isAnyDeviceTransferring()).isFalse();
+ assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isFalse();
}
@Test
public void isAnyDeviceTransferring_deviceStateIsConnecting_returnsTrue() {
when(mMediaDevice1.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(mMediaOutputController.isAnyDeviceTransferring()).isTrue();
+ assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isTrue();
}
@Test
public void isAnyDeviceTransferring_advancedLayoutSupport() {
when(mMediaDevice1.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
- mMediaOutputController.start(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+ mMediaSwitchingController.start(mCb);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(mMediaOutputController.isAnyDeviceTransferring()).isTrue();
+ assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isTrue();
}
@Test
public void isPlaying_stateIsNull() {
when(mSessionMediaController.getPlaybackState()).thenReturn(null);
- assertThat(mMediaOutputController.isPlaying()).isFalse();
+ assertThat(mMediaSwitchingController.isPlaying()).isFalse();
}
@Test
public void onSelectedDeviceStateChanged_verifyCallback() {
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.connectDevice(mMediaDevice1);
+ mMediaSwitchingController.connectDevice(mMediaDevice1);
- mMediaOutputController.onSelectedDeviceStateChanged(mMediaDevice1,
- LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
+ mMediaSwitchingController.onSelectedDeviceStateChanged(
+ mMediaDevice1, LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
verify(mCb).onRouteChanged();
}
@Test
public void onDeviceAttributesChanged_verifyCallback() {
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.onDeviceAttributesChanged();
+ mMediaSwitchingController.onDeviceAttributesChanged();
verify(mCb).onRouteChanged();
}
@@ -855,11 +910,11 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void onRequestFailed_verifyCallback() {
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaOutputController.connectDevice(mMediaDevice2);
+ mMediaSwitchingController.connectDevice(mMediaDevice2);
- mMediaOutputController.onRequestFailed(0 /* reason */);
+ mMediaSwitchingController.onRequestFailed(0 /* reason */);
verify(mCb, atLeastOnce()).onRouteChanged();
}
@@ -868,37 +923,40 @@ public class MediaOutputControllerTest extends SysuiTestCase {
public void getHeaderTitle_withoutMetadata_returnDefaultString() {
when(mSessionMediaController.getMetadata()).thenReturn(null);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- assertThat(mMediaOutputController.getHeaderTitle()).isEqualTo(
- mContext.getText(R.string.controls_media_title));
+ assertThat(
+ mMediaSwitchingController
+ .getHeaderTitle()
+ .equals(mContext.getText(R.string.controls_media_title)))
+ .isTrue();
}
@Test
public void getHeaderTitle_withMetadata_returnSongName() {
when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- assertThat(mMediaOutputController.getHeaderTitle()).isEqualTo(TEST_SONG);
+ assertThat(mMediaSwitchingController.getHeaderTitle().equals(TEST_SONG)).isTrue();
}
@Test
public void getHeaderSubTitle_withoutMetadata_returnNull() {
when(mSessionMediaController.getMetadata()).thenReturn(null);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- assertThat(mMediaOutputController.getHeaderSubTitle()).isNull();
+ assertThat(mMediaSwitchingController.getHeaderSubTitle()).isNull();
}
@Test
public void getHeaderSubTitle_withMetadata_returnArtistName() {
when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata);
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.start(mCb);
- assertThat(mMediaOutputController.getHeaderSubTitle()).isEqualTo(TEST_ARTIST);
+ assertThat(mMediaSwitchingController.getHeaderSubTitle().equals(TEST_ARTIST)).isTrue();
}
@Test
@@ -911,8 +969,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mRoutingSessionInfos.add(mRemoteSessionInfo);
when(mLocalMediaManager.getRemoteRoutingSessions()).thenReturn(mRoutingSessionInfos);
- assertThat(mMediaOutputController.getActiveRemoteMediaDevices()).containsExactly(
- mRemoteSessionInfo);
+ assertThat(mMediaSwitchingController.getActiveRemoteMediaDevices())
+ .containsExactly(mRemoteSessionInfo);
}
@Test
@@ -933,7 +991,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
selectableMediaDevices.add(selectableMediaDevice2);
doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice();
doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice();
- final List<MediaDevice> groupMediaDevices = mMediaOutputController.getGroupMediaDevices();
+ final List<MediaDevice> groupMediaDevices =
+ mMediaSwitchingController.getGroupMediaDevices();
// Reset order
selectedMediaDevices.clear();
selectedMediaDevices.add(selectedMediaDevice2);
@@ -941,7 +1000,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
selectableMediaDevices.clear();
selectableMediaDevices.add(selectableMediaDevice2);
selectableMediaDevices.add(selectableMediaDevice1);
- final List<MediaDevice> newDevices = mMediaOutputController.getGroupMediaDevices();
+ final List<MediaDevice> newDevices = mMediaSwitchingController.getGroupMediaDevices();
assertThat(newDevices.size()).isEqualTo(groupMediaDevices.size());
for (int i = 0; i < groupMediaDevices.size(); i++) {
@@ -970,7 +1029,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
selectableMediaDevices.add(selectableMediaDevice2);
doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice();
doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice();
- final List<MediaDevice> groupMediaDevices = mMediaOutputController.getGroupMediaDevices();
+ final List<MediaDevice> groupMediaDevices =
+ mMediaSwitchingController.getGroupMediaDevices();
// Reset order
selectedMediaDevices.clear();
selectedMediaDevices.add(selectedMediaDevice2);
@@ -979,7 +1039,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
selectableMediaDevices.add(selectableMediaDevice3);
selectableMediaDevices.add(selectableMediaDevice2);
selectableMediaDevices.add(selectableMediaDevice1);
- final List<MediaDevice> newDevices = mMediaOutputController.getGroupMediaDevices();
+ final List<MediaDevice> newDevices = mMediaSwitchingController.getGroupMediaDevices();
assertThat(newDevices.size()).isEqualTo(5);
for (int i = 0; i < groupMediaDevices.size(); i++) {
@@ -991,8 +1051,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
- mMediaOutputController =
- new MediaOutputController(
+ mMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mSpyContext.getUser(),
@@ -1010,7 +1070,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mVolumePanelGlobalStateInteractor,
mUserTracker);
- assertThat(mMediaOutputController.getNotificationIcon()).isNull();
+ assertThat(mMediaSwitchingController.getNotificationIcon()).isNull();
}
@Test
@@ -1028,7 +1088,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getLargeIcon()).thenReturn(null);
- assertThat(mMediaOutputController.getNotificationIcon()).isNull();
+ assertThat(mMediaSwitchingController.getNotificationIcon()).isNull();
}
@Test
@@ -1047,7 +1107,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getLargeIcon()).thenReturn(icon);
- assertThat(mMediaOutputController.getNotificationIcon()).isInstanceOf(IconCompat.class);
+ assertThat(mMediaSwitchingController.getNotificationIcon()).isInstanceOf(IconCompat.class);
}
@Test
@@ -1066,7 +1126,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(notification.isMediaNotification()).thenReturn(false);
when(notification.getLargeIcon()).thenReturn(icon);
- assertThat(mMediaOutputController.getNotificationIcon()).isNull();
+ assertThat(mMediaSwitchingController.getNotificationIcon()).isNull();
}
@Test
@@ -1084,7 +1144,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getSmallIcon()).thenReturn(null);
- assertThat(mMediaOutputController.getNotificationSmallIcon()).isNull();
+ assertThat(mMediaSwitchingController.getNotificationSmallIcon()).isNull();
}
@Test
@@ -1103,8 +1163,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getSmallIcon()).thenReturn(icon);
- assertThat(mMediaOutputController.getNotificationSmallIcon()).isInstanceOf(
- IconCompat.class);
+ assertThat(mMediaSwitchingController.getNotificationSmallIcon())
+ .isInstanceOf(IconCompat.class);
}
@Test
@@ -1112,8 +1172,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
when(mMediaDevice1.getIcon()).thenReturn(mDrawable);
- assertThat(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).isInstanceOf(
- IconCompat.class);
+ assertThat(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1))
+ .isInstanceOf(IconCompat.class);
}
@Test
@@ -1121,13 +1181,13 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
when(mMediaDevice1.getIcon()).thenReturn(null);
- assertThat(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).isInstanceOf(
- IconCompat.class);
+ assertThat(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1))
+ .isInstanceOf(IconCompat.class);
}
@Test
public void setColorFilter_setColorFilterToDrawable() {
- mMediaOutputController.setColorFilter(mDrawable, true);
+ mMediaSwitchingController.setColorFilter(mDrawable, true);
verify(mDrawable).setColorFilter(any(PorterDuffColorFilter.class));
}
@@ -1150,11 +1210,11 @@ public class MediaOutputControllerTest extends SysuiTestCase {
selectableMediaDevices.add(selectableMediaDevice2);
doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice();
doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice();
- assertThat(mMediaOutputController.getGroupMediaDevices().isEmpty()).isFalse();
+ assertThat(mMediaSwitchingController.getGroupMediaDevices().isEmpty()).isFalse();
- mMediaOutputController.resetGroupMediaDevices();
+ mMediaSwitchingController.resetGroupMediaDevices();
- assertThat(mMediaOutputController.mGroupMediaDevices.isEmpty()).isTrue();
+ assertThat(mMediaSwitchingController.mGroupMediaDevices.isEmpty()).isTrue();
}
@Test
@@ -1164,7 +1224,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(mMediaDevice1.isVolumeFixed()).thenReturn(true);
- assertThat(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).isFalse();
+ assertThat(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).isFalse();
}
@Test
@@ -1174,7 +1234,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(mMediaDevice1.isVolumeFixed()).thenReturn(false);
- assertThat(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).isTrue();
+ assertThat(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).isTrue();
}
@Test
@@ -1187,7 +1247,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(mMediaDevice2.getDeviceType()).thenReturn(
MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
- mMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
+ mMediaSwitchingController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(),
anyLong());
@@ -1195,8 +1255,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void setTemporaryAllowListExceptionIfNeeded_packageNameIsNull_NoAction() {
- MediaOutputController testMediaOutputController =
- new MediaOutputController(
+ MediaSwitchingController testMediaSwitchingController =
+ new MediaSwitchingController(
mSpyContext,
null,
mSpyContext.getUser(),
@@ -1214,7 +1274,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mVolumePanelGlobalStateInteractor,
mUserTracker);
- testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
+ testMediaSwitchingController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
verify(mPowerExemptionManager, never()).addToTemporaryAllowList(anyString(), anyInt(),
anyString(),
@@ -1223,22 +1283,22 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void onMetadataChanged_triggersOnMetadataChanged() {
- mMediaOutputController.mCallback = this.mCallback;
+ mMediaSwitchingController.mCallback = this.mCallback;
- mMediaOutputController.mCb.onMetadataChanged(mMediaMetadata);
+ mMediaSwitchingController.mCb.onMetadataChanged(mMediaMetadata);
- verify(mMediaOutputController.mCallback).onMediaChanged();
+ verify(mMediaSwitchingController.mCallback).onMediaChanged();
}
@Test
public void onPlaybackStateChanged_updateWithNullState_onMediaStoppedOrPaused() {
when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_PLAYING);
- mMediaOutputController.mCallback = this.mCallback;
- mMediaOutputController.start(mCb);
+ mMediaSwitchingController.mCallback = this.mCallback;
+ mMediaSwitchingController.start(mCb);
- mMediaOutputController.mCb.onPlaybackStateChanged(null);
+ mMediaSwitchingController.mCb.onPlaybackStateChanged(null);
- verify(mMediaOutputController.mCallback).onMediaStoppedOrPaused();
+ verify(mMediaSwitchingController.mCallback).onMediaStoppedOrPaused();
}
@Test
@@ -1246,9 +1306,9 @@ public class MediaOutputControllerTest extends SysuiTestCase {
when(mDialogTransitionAnimator.createActivityTransitionController(mDialogLaunchView))
.thenReturn(mActivityTransitionAnimatorController);
when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
- mMediaOutputController.mCallback = this.mCallback;
+ mMediaSwitchingController.mCallback = this.mCallback;
- mMediaOutputController.launchBluetoothPairing(mDialogLaunchView);
+ mMediaSwitchingController.launchBluetoothPairing(mDialogLaunchView);
verify(mCallback).dismissDialog();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index 70af5e75105d..755adc68192a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -26,7 +26,6 @@ import androidx.compose.ui.test.assert
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onChildAt
import androidx.compose.ui.test.onChildren
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
@@ -40,6 +39,7 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.DefaultEditTileGrid
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.TileCategory
@@ -57,7 +57,7 @@ class DragAndDropTest : SysuiTestCase() {
@Composable
private fun EditTileGridUnderTest(
listState: EditTileListState,
- onSetTiles: (List<TileSpec>) -> Unit
+ onSetTiles: (List<TileSpec>) -> Unit,
) {
DefaultEditTileGrid(
currentListState = listState,
@@ -182,7 +182,7 @@ class DragAndDropTest : SysuiTestCase() {
private fun ComposeContentTestRule.assertTileGridContainsExactly(specs: List<String>) {
onNodeWithTag(CURRENT_TILES_GRID_TEST_TAG).onChildren().apply {
fetchSemanticsNodes().forEachIndexed { index, _ ->
- get(index).onChildAt(0).assert(hasContentDescription(specs[index]))
+ get(index).assert(hasContentDescription(specs[index]))
}
}
}
@@ -198,7 +198,7 @@ class DragAndDropTest : SysuiTestCase() {
icon =
Icon.Resource(
android.R.drawable.star_on,
- ContentDescription.Loaded(tileSpec)
+ ContentDescription.Loaded(tileSpec),
),
label = AnnotatedString(tileSpec),
appName = null,
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 af043093b6f7..c710c56fd516 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
@@ -168,7 +168,7 @@ import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.core.StatusBarInitializer;
+import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -504,7 +504,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mock(FragmentService.class),
mLightBarController,
mAutoHideController,
- new StatusBarInitializer(
+ new StatusBarInitializerImpl(
mStatusBarWindowController,
mCollapsedStatusBarFragmentProvider,
emptySet()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 1e2648b228f3..ecc7909a857a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -20,7 +20,6 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
-import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER;
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST;
@@ -51,7 +50,6 @@ import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.os.SystemClock;
-import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
@@ -285,23 +283,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
- public void addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics() {
- // GIVEN that the slider haptics flag is disabled and we try to add haptics to volume rows
- mDialog.addSliderHapticsToRows();
-
- // WHEN haptics try to be delivered to a volume stream
- boolean canDeliverHaptics =
- mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50);
-
- // THEN the result is that haptics are not successfully delivered
- assertFalse(canDeliverHaptics);
- }
-
- @Test
- @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
- public void addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics() {
- // GIVEN that the slider haptics flag is enabled and we try to add haptics to volume rows
+ public void addSliderHaptics_canDeliverOnProgressChangedHaptics() {
+ // GIVEN that the slider haptics are added to rows
mDialog.addSliderHapticsToRows();
// WHEN haptics try to be delivered to a volume stream
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index 649e4e8a6f7e..1b1d8c5d0f63 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -25,6 +25,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -34,9 +36,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.StateFlow
val Kosmos.bouncerUserActionsViewModel by Fixture {
- BouncerUserActionsViewModel(
- bouncerInteractor = bouncerInteractor,
- )
+ BouncerUserActionsViewModel(bouncerInteractor = bouncerInteractor)
}
val Kosmos.bouncerUserActionsViewModelFactory by Fixture {
@@ -59,6 +59,7 @@ val Kosmos.bouncerSceneContentViewModel by Fixture {
pinViewModelFactory = pinBouncerViewModelFactory,
patternViewModelFactory = patternBouncerViewModelFactory,
passwordViewModelFactory = passwordBouncerViewModelFactory,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
@@ -76,6 +77,7 @@ val Kosmos.pinBouncerViewModelFactory by Fixture {
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
authenticationMethod: AuthenticationMethodModel,
+ bouncerHapticPlayer: BouncerHapticPlayer,
): PinBouncerViewModel {
return PinBouncerViewModel(
applicationContext = applicationContext,
@@ -84,6 +86,7 @@ val Kosmos.pinBouncerViewModelFactory by Fixture {
isInputEnabled = isInputEnabled,
onIntentionalUserInput = onIntentionalUserInput,
authenticationMethod = authenticationMethod,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
}
@@ -92,6 +95,7 @@ val Kosmos.pinBouncerViewModelFactory by Fixture {
val Kosmos.patternBouncerViewModelFactory by Fixture {
object : PatternBouncerViewModel.Factory {
override fun create(
+ bouncerHapticPlayer: BouncerHapticPlayer,
isInputEnabled: StateFlow<Boolean>,
onIntentionalUserInput: () -> Unit,
): PatternBouncerViewModel {
@@ -100,6 +104,7 @@ val Kosmos.patternBouncerViewModelFactory by Fixture {
interactor = bouncerInteractor,
isInputEnabled = isInputEnabled,
onIntentionalUserInput = onIntentionalUserInput,
+ bouncerHapticPlayer = bouncerHapticPlayer,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index 1ed10fbe94c3..8922b2f5c5ef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -20,6 +20,7 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.flags.systemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -37,5 +38,6 @@ val Kosmos.deviceUnlockedInteractor by Fixture {
powerInteractor = powerInteractor,
biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
systemPropertiesHelper = fakeSystemPropertiesHelper,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index 1d2bce2f9b99..1df3ef48d5a7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -19,7 +19,7 @@ package com.android.systemui.education.data.repository
import com.android.systemui.kosmos.Kosmos
import java.time.Instant
-var Kosmos.contextualEducationRepository: ContextualEducationRepository by
+var Kosmos.contextualEducationRepository: FakeContextualEducationRepository by
Kosmos.Fixture { FakeContextualEducationRepository() }
var Kosmos.fakeEduClock: FakeEduClock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index fb4e2fb5fd14..4667bf5292fa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -17,39 +17,77 @@
package com.android.systemui.education.data.repository
import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
+import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
class FakeContextualEducationRepository : ContextualEducationRepository {
- private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
- private val _gestureEduModels = MutableStateFlow(GestureEduModel(userId = 0))
- private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+ private val userGestureMap = mutableMapOf<Int, MutableMap<GestureType, GestureEduModel>>()
+
+ private val _backGestureEduModels = MutableStateFlow(GestureEduModel(BACK, userId = 0))
+ private val backGestureEduModelsFlow = _backGestureEduModels.asStateFlow()
+
+ private val _homeGestureEduModels = MutableStateFlow(GestureEduModel(HOME, userId = 0))
+ private val homeEduModelsFlow = _homeGestureEduModels.asStateFlow()
+
+ private val _allAppsGestureEduModels = MutableStateFlow(GestureEduModel(ALL_APPS, userId = 0))
+ private val allAppsGestureEduModels = _allAppsGestureEduModels.asStateFlow()
+
+ private val _overviewsGestureEduModels = MutableStateFlow(GestureEduModel(OVERVIEW, userId = 0))
+ private val overviewsGestureEduModels = _overviewsGestureEduModels.asStateFlow()
private val userEduDeviceConnectionTimeMap = mutableMapOf<Int, EduDeviceConnectionTime>()
private val _eduDeviceConnectionTime = MutableStateFlow(EduDeviceConnectionTime())
private val eduDeviceConnectionTime = _eduDeviceConnectionTime.asStateFlow()
+ private val _keyboardShortcutTriggered = MutableStateFlow<GestureType?>(null)
+
private var currentUser: Int = 0
override fun setUser(userId: Int) {
if (!userGestureMap.contains(userId)) {
- userGestureMap[userId] = GestureEduModel(userId = userId)
+ userGestureMap[userId] = createGestureEduModelMap(userId = userId)
userEduDeviceConnectionTimeMap[userId] = EduDeviceConnectionTime()
}
// save data of current user to the map
- userGestureMap[currentUser] = _gestureEduModels.value
- userEduDeviceConnectionTimeMap[currentUser] = _eduDeviceConnectionTime.value
+ val currentUserMap = userGestureMap[currentUser]!!
+ currentUserMap[BACK] = _backGestureEduModels.value
+ currentUserMap[HOME] = _homeGestureEduModels.value
+ currentUserMap[ALL_APPS] = _allAppsGestureEduModels.value
+ currentUserMap[OVERVIEW] = _overviewsGestureEduModels.value
+
// switch to data of new user
- _gestureEduModels.value = userGestureMap[userId]!!
+ val newUserGestureMap = userGestureMap[userId]!!
+ newUserGestureMap[BACK]?.let { _backGestureEduModels.value = it }
+ newUserGestureMap[HOME]?.let { _homeGestureEduModels.value = it }
+ newUserGestureMap[ALL_APPS]?.let { _allAppsGestureEduModels.value = it }
+ newUserGestureMap[OVERVIEW]?.let { _overviewsGestureEduModels.value = it }
+
+ userEduDeviceConnectionTimeMap[currentUser] = _eduDeviceConnectionTime.value
_eduDeviceConnectionTime.value = userEduDeviceConnectionTimeMap[userId]!!
}
+ private fun createGestureEduModelMap(userId: Int): MutableMap<GestureType, GestureEduModel> {
+ val gestureModelMap = mutableMapOf<GestureType, GestureEduModel>()
+ GestureType.values().forEach { gestureModelMap[it] = GestureEduModel(it, userId = userId) }
+ return gestureModelMap
+ }
+
override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> {
- return gestureEduModelsFlow
+ return when (gestureType) {
+ BACK -> backGestureEduModelsFlow
+ HOME -> homeEduModelsFlow
+ ALL_APPS -> allAppsGestureEduModels
+ OVERVIEW -> overviewsGestureEduModels
+ }
}
override fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime> {
@@ -60,8 +98,16 @@ class FakeContextualEducationRepository : ContextualEducationRepository {
gestureType: GestureType,
transform: (GestureEduModel) -> GestureEduModel
) {
- val currentModel = _gestureEduModels.value
- _gestureEduModels.value = transform(currentModel)
+ val gestureModels =
+ when (gestureType) {
+ BACK -> _backGestureEduModels
+ HOME -> _homeGestureEduModels
+ ALL_APPS -> _allAppsGestureEduModels
+ OVERVIEW -> _overviewsGestureEduModels
+ }
+
+ val currentModel = gestureModels.value
+ gestureModels.value = transform(currentModel)
}
override suspend fun updateEduDeviceConnectionTime(
@@ -70,4 +116,11 @@ class FakeContextualEducationRepository : ContextualEducationRepository {
val currentModel = _eduDeviceConnectionTime.value
_eduDeviceConnectionTime.value = transform(currentModel)
}
+
+ override val keyboardShortcutTriggered: Flow<GestureType>
+ get() = _keyboardShortcutTriggered.filterNotNull()
+
+ fun setKeyboardShortcutTriggered(gestureType: GestureType) {
+ _keyboardShortcutTriggered.value = gestureType
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index 80f6fc24ef2c..2d275f9e9691 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -40,8 +40,7 @@ var Kosmos.keyboardTouchpadEduInteractor by
touchpadRepository,
userRepository
),
- clock = fakeEduClock,
- inputManager = mockEduInputManager
+ clock = fakeEduClock
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index c252924f4d2d..c0152b26d7a3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -17,7 +17,6 @@
package com.android.systemui.flags
import android.platform.test.annotations.EnableFlags
-import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN
import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
@@ -31,7 +30,6 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
* that feature. It is also picked up by [SceneContainerRule] to set non-aconfig prerequisites.
*/
@EnableFlags(
- FLAG_COMPOSE_LOCKSCREEN,
FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
FLAG_KEYGUARD_WM_STATE_REFACTOR,
FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
index 5ad973a54252..2b81da33b9bc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
@@ -20,8 +20,10 @@ import com.google.android.msdl.data.model.FeedbackLevel
import com.google.android.msdl.data.model.MSDLToken
import com.google.android.msdl.domain.InteractionProperties
import com.google.android.msdl.domain.MSDLPlayer
+import com.google.android.msdl.logging.MSDLEvent
class FakeMSDLPlayer : MSDLPlayer {
+ private val history = arrayListOf<MSDLEvent>()
var currentFeedbackLevel = FeedbackLevel.DEFAULT
var latestTokenPlayed: MSDLToken? = null
private set
@@ -34,5 +36,8 @@ class FakeMSDLPlayer : MSDLPlayer {
override fun playToken(token: MSDLToken, properties: InteractionProperties?) {
latestTokenPlayed = token
latestPropertiesPlayed = properties
+ history.add(MSDLEvent(token, properties))
}
+
+ override fun getHistory(): List<MSDLEvent> = history
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index ca748b661d04..80db1e9b5e29 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.haptics.qs
+import com.android.systemui.classifier.fakeFalsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.log.core.FakeLogBuffer
@@ -26,6 +27,7 @@ val Kosmos.qsLongPressEffect by
QSLongPressEffect(
vibratorHelper,
keyguardStateController,
+ fakeFalsingManager,
FakeLogBuffer.Factory.create(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
deleted file mode 100644
index edbc4c1cd65e..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 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.qs.panels.domain.interactor
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.log.core.FakeLogBuffer
-import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
-
-val Kosmos.gridConsistencyInteractor by
- Kosmos.Fixture {
- GridConsistencyInteractor(
- gridLayoutTypeInteractor,
- currentTilesInteractor,
- gridConsistencyInteractorsMap,
- noopGridConsistencyInteractor,
- FakeLogBuffer.Factory.create(),
- applicationCoroutineScope,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index 34e99d3a9a3c..c9516429553b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -27,6 +27,3 @@ val Kosmos.gridLayoutTypeInteractor by
val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
-
-var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by
- Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
deleted file mode 100644
index 320c2ec1bb99..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2024 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.qs.panels.domain.interactor
-
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.infiniteGridConsistencyInteractor by
- Kosmos.Fixture {
- InfiniteGridConsistencyInteractor(iconTilesInteractor, fixedColumnsSizeInteractor)
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index be00152cb511..3f62b4d9f9cb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -17,7 +17,7 @@
package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
index c416ea1c1b39..91602c23ac17 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
@@ -16,8 +16,13 @@
package com.android.systemui.statusbar.data.repository
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
class FakeRemoteInputRepository : RemoteInputRepository {
override val isRemoteInputActive = MutableStateFlow(false)
+ override val remoteInputRowBottomBound: Flow<Float?> = flowOf(null)
+
+ override fun setRemoteInputRowBottomBound(bottom: Float?) {}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
index 6370a5d9c80b..7244d465ed7e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.notificationScrollViewModel by Fixture {
@@ -29,6 +30,7 @@ val Kosmos.notificationScrollViewModel by Fixture {
dumpManager = dumpManager,
stackAppearanceInteractor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
+ remoteInputInteractor = remoteInputInteractor,
sceneInteractor = sceneInteractor,
keyguardInteractor = { keyguardInteractor },
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 8bfc390ecfa3..e5cf0a90ebbd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
@@ -31,6 +32,7 @@ val Kosmos.notificationsPlaceholderViewModel by Fixture {
sceneInteractor = sceneInteractor,
shadeInteractor = shadeInteractor,
headsUpNotificationInteractor = headsUpNotificationInteractor,
+ remoteInputInteractor = remoteInputInteractor,
featureFlags = featureFlagsClassic,
dumpManager = dumpManager,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index 61b53c9a2067..99cd8309631e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -22,6 +22,8 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
+import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
val Kosmos.zenModeInteractor by Fixture {
@@ -31,5 +33,7 @@ val Kosmos.zenModeInteractor by Fixture {
notificationSettingsRepository = notificationSettingsRepository,
bgDispatcher = testDispatcher,
iconLoader = zenIconLoader,
+ deviceProvisioningRepository = deviceProvisioningRepository,
+ userSetupRepository = userSetupRepository,
)
}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index d1a3bf9b529f..10e4f3820cd7 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -343,6 +343,8 @@ android_ravenwood_libgroup {
data: [
":framework-res",
":ravenwood-empty-res",
+ ":framework-platform-compat-config",
+ ":services-platform-compat-config",
],
libs: [
"100-framework-minus-apex.ravenwood",
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index c3b7087a44c3..1f98334bb8ce 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,7 +16,15 @@
package com.android.server.appfunctions;
+import android.annotation.NonNull;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -33,5 +41,50 @@ public final class AppFunctionExecutors {
/* unit= */ TimeUnit.SECONDS,
/* workQueue= */ new LinkedBlockingQueue<>());
+ /** A map of per-user executors for queued work. */
+ @GuardedBy("sLock")
+ private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>();
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Returns a per-user executor for queued metadata sync request.
+ *
+ * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence
+ * the use of a single thread.
+ *
+ * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code
+ * MetadataSyncAdapter}.
+ */
+ // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself.
+ public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) {
+ synchronized (sLock) {
+ ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null);
+ if (executor == null) {
+ executor = Executors.newSingleThreadExecutor();
+ mPerUserExecutorsLocked.put(user.getIdentifier(), executor);
+ }
+ return executor;
+ }
+ }
+
+ /**
+ * Shuts down and removes the per-user executor for queued work.
+ *
+ * <p>This should be called when the user is removed.
+ */
+ public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user)
+ throws InterruptedException {
+ ExecutorService executor;
+ synchronized (sLock) {
+ executor = mPerUserExecutorsLocked.get(user.getIdentifier());
+ mPerUserExecutorsLocked.remove(user.getIdentifier());
+ }
+ if (executor != null) {
+ executor.shutdown();
+ var unused = executor.awaitTermination(30, TimeUnit.SECONDS);
+ }
+ }
+
private AppFunctionExecutors() {}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
index 02800cbf4f3d..c293087defb6 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.appfunctions;
+import android.annotation.NonNull;
import android.app.appfunctions.AppFunctionManagerConfiguration;
import android.content.Context;
@@ -36,4 +37,14 @@ public class AppFunctionManagerService extends SystemService {
publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl);
}
}
+
+ @Override
+ public void onUserUnlocked(@NonNull TargetUser user) {
+ mServiceImpl.onUserUnlocked(user);
+ }
+
+ @Override
+ public void onUserStopping(@NonNull TargetUser user) {
+ mServiceImpl.onUserStopping(user);
+ }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 2362b91c826e..cf039df3bc96 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -19,29 +19,35 @@ package com.android.server.appfunctions;
import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
import android.app.appfunctions.ExecuteAppFunctionResponse;
import android.app.appfunctions.IAppFunctionManager;
import android.app.appfunctions.IAppFunctionService;
import android.app.appfunctions.IExecuteAppFunctionCallback;
import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.observer.DocumentChangeInfo;
+import android.app.appsearch.observer.ObserverCallback;
+import android.app.appsearch.observer.ObserverSpec;
+import android.app.appsearch.observer.SchemaChangeInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
-import android.app.appsearch.AppSearchResult;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService.TargetUser;
import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
+import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.CompletionException;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
/** Implementation of the AppFunctionManagerService. */
public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
@@ -51,9 +57,11 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
private final CallerValidator mCallerValidator;
private final ServiceHelper mInternalServiceHelper;
private final ServiceConfig mServiceConfig;
+ private final Context mContext;
public AppFunctionManagerServiceImpl(@NonNull Context context) {
this(
+ context,
new RemoteServiceCallerImpl<>(
context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR),
new CallerValidatorImpl(context),
@@ -63,10 +71,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
@VisibleForTesting
AppFunctionManagerServiceImpl(
+ Context context,
RemoteServiceCaller<IAppFunctionService> remoteServiceCaller,
CallerValidator callerValidator,
ServiceHelper appFunctionInternalServiceHelper,
ServiceConfig serviceConfig) {
+ mContext = Objects.requireNonNull(context);
mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller);
mCallerValidator = Objects.requireNonNull(callerValidator);
mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper);
@@ -90,6 +100,26 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
}
}
+ /** Called when the user is unlocked. */
+ public void onUserUnlocked(TargetUser user) {
+ Objects.requireNonNull(user);
+
+ registerAppSearchObserver(user);
+ trySyncRuntimeMetadata(user);
+ }
+
+ /** Called when the user is stopping. */
+ public void onUserStopping(@NonNull TargetUser user) {
+ Objects.requireNonNull(user);
+
+ try {
+ AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle());
+ MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
+ } catch (InterruptedException e) {
+ Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e);
+ }
+ }
+
private void executeAppFunctionInternal(
ExecuteAppFunctionAidlRequest requestInternal,
SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
@@ -132,53 +162,55 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
return;
}
- var unused = mCallerValidator
- .verifyCallerCanExecuteAppFunction(
- validatedCallingPackage,
- targetPackageName,
- requestInternal.getClientRequest().getFunctionIdentifier())
- .thenAccept(
- canExecute -> {
- if (!canExecute) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_DENIED,
- "Caller does not have permission to execute the"
- + " appfunction",
- /* extras= */ null));
- return;
- }
- Intent serviceIntent =
- mInternalServiceHelper.resolveAppFunctionService(
- targetPackageName, targetUser);
- if (serviceIntent == null) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
- "Cannot find the target service.",
- /* extras= */ null));
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- bindAppFunctionServiceUnchecked(
- requestInternal,
- serviceIntent,
- targetUser,
- safeExecuteAppFunctionCallback,
- /* bindFlags= */ Context.BIND_AUTO_CREATE,
- /* timeoutInMillis= */ mServiceConfig
- .getExecuteAppFunctionTimeoutMillis());
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- })
- .exceptionally(
- ex -> {
- safeExecuteAppFunctionCallback.onResult(
- mapExceptionToExecuteAppFunctionResponse(ex));
- return null;
- });
+ var unused =
+ mCallerValidator
+ .verifyCallerCanExecuteAppFunction(
+ validatedCallingPackage,
+ targetPackageName,
+ requestInternal.getClientRequest().getFunctionIdentifier())
+ .thenAccept(
+ canExecute -> {
+ if (!canExecute) {
+ safeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_DENIED,
+ "Caller does not have permission to execute"
+ + " the appfunction",
+ /* extras= */ null));
+ return;
+ }
+ Intent serviceIntent =
+ mInternalServiceHelper.resolveAppFunctionService(
+ targetPackageName, targetUser);
+ if (serviceIntent == null) {
+ safeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse
+ .RESULT_INTERNAL_ERROR,
+ "Cannot find the target service.",
+ /* extras= */ null));
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ bindAppFunctionServiceUnchecked(
+ requestInternal,
+ serviceIntent,
+ targetUser,
+ safeExecuteAppFunctionCallback,
+ /* bindFlags= */ Context.BIND_AUTO_CREATE,
+ /* timeoutInMillis= */ mServiceConfig
+ .getExecuteAppFunctionTimeoutMillis());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ })
+ .exceptionally(
+ ex -> {
+ safeExecuteAppFunctionCallback.onResult(
+ mapExceptionToExecuteAppFunctionResponse(ex));
+ return null;
+ });
}
private void bindAppFunctionServiceUnchecked(
@@ -256,7 +288,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
}
private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) {
- if(e instanceof CompletionException) {
+ if (e instanceof CompletionException) {
e = e.getCause();
}
@@ -291,4 +323,103 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
}
return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
}
+
+ private void registerAppSearchObserver(@NonNull TargetUser user) {
+ AppSearchManager perUserAppSearchManager =
+ mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)
+ .getSystemService(AppSearchManager.class);
+ if (perUserAppSearchManager == null) {
+ Slog.d(TAG, "AppSearch Manager not found for user: " + user.getUserIdentifier());
+ return;
+ }
+ try (FutureGlobalSearchSession futureGlobalSearchSession =
+ new FutureGlobalSearchSession(
+ perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR)) {
+ AppFunctionMetadataObserver appFunctionMetadataObserver =
+ new AppFunctionMetadataObserver(
+ user.getUserHandle(),
+ mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0));
+ var unused =
+ futureGlobalSearchSession
+ .registerObserverCallbackAsync(
+ "android",
+ new ObserverSpec.Builder().build(),
+ THREAD_POOL_EXECUTOR,
+ appFunctionMetadataObserver)
+ .whenComplete(
+ (voidResult, ex) -> {
+ if (ex != null) {
+ Slog.e(TAG, "Failed to register observer: ", ex);
+ }
+ });
+
+ } catch (IOException ex) {
+ Slog.e(TAG, "Failed to close observer session: ", ex);
+ }
+ }
+
+ private void trySyncRuntimeMetadata(@NonNull TargetUser user) {
+ MetadataSyncAdapter metadataSyncAdapter =
+ MetadataSyncPerUser.getPerUserMetadataSyncAdapter(
+ user.getUserHandle(),
+ mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0));
+ if (metadataSyncAdapter != null) {
+ var unused =
+ metadataSyncAdapter
+ .submitSyncRequest()
+ .whenComplete(
+ (isSuccess, ex) -> {
+ if (ex != null || !isSuccess) {
+ Slog.e(TAG, "Sync was not successful");
+ }
+ });
+ }
+ }
+
+ private static class AppFunctionMetadataObserver implements ObserverCallback {
+ @Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter;
+
+ AppFunctionMetadataObserver(@NonNull UserHandle userHandle, @NonNull Context userContext) {
+ mPerUserMetadataSyncAdapter =
+ MetadataSyncPerUser.getPerUserMetadataSyncAdapter(userHandle, userContext);
+ }
+
+ @Override
+ public void onDocumentChanged(@NonNull DocumentChangeInfo documentChangeInfo) {
+ if (mPerUserMetadataSyncAdapter == null) {
+ return;
+ }
+ if (documentChangeInfo
+ .getDatabaseName()
+ .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)
+ && documentChangeInfo
+ .getNamespace()
+ .equals(
+ AppFunctionStaticMetadataHelper
+ .APP_FUNCTION_STATIC_NAMESPACE)) {
+ var unused = mPerUserMetadataSyncAdapter.submitSyncRequest();
+ }
+ }
+
+ @Override
+ public void onSchemaChanged(@NonNull SchemaChangeInfo schemaChangeInfo) {
+ if (mPerUserMetadataSyncAdapter == null) {
+ return;
+ }
+ if (schemaChangeInfo
+ .getDatabaseName()
+ .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)) {
+ boolean shouldInitiateSync = false;
+ for (String schemaName : schemaChangeInfo.getChangedSchemaNames()) {
+ if (schemaName.startsWith(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE)) {
+ shouldInitiateSync = true;
+ break;
+ }
+ }
+ if (shouldInitiateSync) {
+ var unused = mPerUserMetadataSyncAdapter.submitSyncRequest();
+ }
+ }
+ }
+ }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index e2573590bf5d..8c6f50e5c1bd 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -24,6 +24,8 @@ import android.annotation.WorkerThread;
import android.app.appfunctions.AppFunctionRuntimeMetadata;
import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchManager.SearchContext;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.PackageIdentifier;
@@ -61,9 +63,8 @@ import java.util.concurrent.Executor;
*/
public class MetadataSyncAdapter {
private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
- private final FutureAppSearchSession mRuntimeMetadataSearchSession;
- private final FutureAppSearchSession mStaticMetadataSearchSession;
private final Executor mSyncExecutor;
+ private final AppSearchManager mAppSearchManager;
private final PackageManager mPackageManager;
// Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
@@ -73,13 +74,11 @@ public class MetadataSyncAdapter {
public MetadataSyncAdapter(
@NonNull Executor syncExecutor,
- @NonNull FutureAppSearchSession runtimeMetadataSearchSession,
- @NonNull FutureAppSearchSession staticMetadataSearchSession,
- @NonNull PackageManager packageManager) {
+ @NonNull PackageManager packageManager,
+ @NonNull AppSearchManager appSearchManager) {
mSyncExecutor = Objects.requireNonNull(syncExecutor);
- mRuntimeMetadataSearchSession = Objects.requireNonNull(runtimeMetadataSearchSession);
- mStaticMetadataSearchSession = Objects.requireNonNull(staticMetadataSearchSession);
mPackageManager = Objects.requireNonNull(packageManager);
+ mAppSearchManager = Objects.requireNonNull(appSearchManager);
}
/**
@@ -89,31 +88,54 @@ public class MetadataSyncAdapter {
* synchronization was successful.
*/
public AndroidFuture<Boolean> submitSyncRequest() {
+ SearchContext staticMetadataSearchContext =
+ new SearchContext.Builder(
+ AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)
+ .build();
+ SearchContext runtimeMetadataSearchContext =
+ new SearchContext.Builder(
+ AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB)
+ .build();
AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
mSyncExecutor.execute(
() -> {
- try {
- trySyncAppFunctionMetadataBlocking();
+ try (FutureAppSearchSession staticMetadataSearchSession =
+ new FutureAppSearchSessionImpl(
+ mAppSearchManager,
+ AppFunctionExecutors.THREAD_POOL_EXECUTOR,
+ staticMetadataSearchContext);
+ FutureAppSearchSession runtimeMetadataSearchSession =
+ new FutureAppSearchSessionImpl(
+ mAppSearchManager,
+ AppFunctionExecutors.THREAD_POOL_EXECUTOR,
+ runtimeMetadataSearchContext)) {
+
+ trySyncAppFunctionMetadataBlocking(
+ staticMetadataSearchSession, runtimeMetadataSearchSession);
settableSyncStatus.complete(true);
- } catch (Exception e) {
- settableSyncStatus.completeExceptionally(e);
+
+ } catch (Exception ex) {
+ settableSyncStatus.completeExceptionally(ex);
}
});
return settableSyncStatus;
}
@WorkerThread
- private void trySyncAppFunctionMetadataBlocking()
+ @VisibleForTesting
+ void trySyncAppFunctionMetadataBlocking(
+ @NonNull FutureAppSearchSession staticMetadataSearchSession,
+ @NonNull FutureAppSearchSession runtimeMetadataSearchSession)
throws ExecutionException, InterruptedException {
ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap =
getPackageToFunctionIdMap(
- mStaticMetadataSearchSession,
+ staticMetadataSearchSession,
AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE,
AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID,
AppFunctionStaticMetadataHelper.PROPERTY_PACKAGE_NAME);
ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap =
getPackageToFunctionIdMap(
- mRuntimeMetadataSearchSession,
+ runtimeMetadataSearchSession,
RUNTIME_SCHEMA_TYPE,
AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME);
@@ -134,7 +156,7 @@ public class MetadataSyncAdapter {
RemoveByDocumentIdRequest removeByDocumentIdRequest =
buildRemoveRuntimeMetadataRequest(removedFunctionsDiffMap);
AppSearchBatchResult<String, Void> removeDocumentBatchResult =
- mRuntimeMetadataSearchSession.remove(removeByDocumentIdRequest).get();
+ runtimeMetadataSearchSession.remove(removeByDocumentIdRequest).get();
if (!removeDocumentBatchResult.isSuccess()) {
throw convertFailedAppSearchResultToException(
removeDocumentBatchResult.getFailures().values());
@@ -144,13 +166,14 @@ public class MetadataSyncAdapter {
if (!addedFunctionsDiffMap.isEmpty()) {
// TODO(b/357551503): only set schema on package diff
SetSchemaRequest addSetSchemaRequest =
- buildSetSchemaRequestForRuntimeMetadataSchemas(appRuntimeMetadataSchemas);
+ buildSetSchemaRequestForRuntimeMetadataSchemas(
+ mPackageManager, appRuntimeMetadataSchemas);
Objects.requireNonNull(
- mRuntimeMetadataSearchSession.setSchema(addSetSchemaRequest).get());
+ runtimeMetadataSearchSession.setSchema(addSetSchemaRequest).get());
PutDocumentsRequest putDocumentsRequest =
buildPutRuntimeMetadataRequest(addedFunctionsDiffMap);
AppSearchBatchResult<String, Void> putDocumentBatchResult =
- mRuntimeMetadataSearchSession.put(putDocumentsRequest).get();
+ runtimeMetadataSearchSession.put(putDocumentsRequest).get();
if (!putDocumentBatchResult.isSuccess()) {
throw convertFailedAppSearchResultToException(
putDocumentBatchResult.getFailures().values());
@@ -211,6 +234,7 @@ public class MetadataSyncAdapter {
@NonNull
private SetSchemaRequest buildSetSchemaRequestForRuntimeMetadataSchemas(
+ @NonNull PackageManager packageManager,
@NonNull Set<AppSearchSchema> metadataSchemaSet) {
Objects.requireNonNull(metadataSchemaSet);
SetSchemaRequest.Builder setSchemaRequestBuilder =
@@ -220,7 +244,7 @@ public class MetadataSyncAdapter {
String packageName =
AppFunctionRuntimeMetadata.getPackageNameFromSchema(
runtimeMetadataSchema.getSchemaType());
- byte[] packageCert = getCertificate(packageName);
+ byte[] packageCert = getCertificate(packageManager, packageName);
if (packageCert == null) {
continue;
}
@@ -399,13 +423,15 @@ public class MetadataSyncAdapter {
/** Gets the SHA-256 certificate from a {@link PackageManager}, or null if it is not found. */
@Nullable
- private byte[] getCertificate(@NonNull String packageName) {
+ private byte[] getCertificate(
+ @NonNull PackageManager packageManager, @NonNull String packageName) {
+ Objects.requireNonNull(packageManager);
Objects.requireNonNull(packageName);
PackageInfo packageInfo;
try {
packageInfo =
Objects.requireNonNull(
- mPackageManager.getPackageInfo(
+ packageManager.getPackageInfo(
packageName,
PackageManager.GET_META_DATA
| PackageManager.GET_SIGNING_CERTIFICATES));
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
new file mode 100644
index 000000000000..f421527e72d0
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 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.appfunctions;
+
+import android.annotation.Nullable;
+import android.app.appsearch.AppSearchManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/** A Singleton class that manages per-user metadata sync adapters. */
+public final class MetadataSyncPerUser {
+ private static final String TAG = MetadataSyncPerUser.class.getSimpleName();
+
+ /** A map of per-user adapter for synchronizing appFunction metadata. */
+ @GuardedBy("sLock")
+ private static final SparseArray<MetadataSyncAdapter> sPerUserMetadataSyncAdapter =
+ new SparseArray<>();
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Returns the per-user metadata sync adapter for the given user.
+ *
+ * @param user The user for which to get the metadata sync adapter.
+ * @param userContext The user context for the given user.
+ * @return The metadata sync adapter for the given user.
+ */
+ @Nullable
+ public static MetadataSyncAdapter getPerUserMetadataSyncAdapter(
+ UserHandle user, Context userContext) {
+ synchronized (sLock) {
+ MetadataSyncAdapter metadataSyncAdapter =
+ sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null);
+ if (metadataSyncAdapter == null) {
+ AppSearchManager perUserAppSearchManager =
+ userContext.getSystemService(AppSearchManager.class);
+ PackageManager perUserPackageManager = userContext.getPackageManager();
+ if (perUserAppSearchManager != null) {
+ metadataSyncAdapter =
+ new MetadataSyncAdapter(
+ AppFunctionExecutors.getPerUserSyncExecutor(user),
+ perUserPackageManager,
+ perUserAppSearchManager);
+ sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter);
+ return metadataSyncAdapter;
+ }
+ }
+ return metadataSyncAdapter;
+ }
+ }
+
+ /**
+ * Removes the per-user metadata sync adapter for the given user.
+ *
+ * @param user The user for which to remove the metadata sync adapter.
+ */
+ public static void removeUserSyncAdapter(UserHandle user) {
+ synchronized (sLock) {
+ sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS
index 4fe0592f9075..4b732ac8e5a9 100644
--- a/services/companion/java/com/android/server/companion/virtual/OWNERS
+++ b/services/companion/java/com/android/server/companion/virtual/OWNERS
@@ -2,7 +2,9 @@
set noparent
-marvinramin@google.com
vladokom@google.com
+marvinramin@google.com
+caen@google.com
+biswarupp@google.com
ogunwale@google.com
michaelwr@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f0cc09f7c232..22ec7904f972 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -73,6 +73,7 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
import static android.media.audio.Flags.roForegroundAudioControl;
import static android.os.Process.THREAD_GROUP_BACKGROUND;
import static android.os.Process.THREAD_GROUP_DEFAULT;
+import static android.os.Process.THREAD_GROUP_FOREGROUND_WINDOW;
import static android.os.Process.THREAD_GROUP_RESTRICTED;
import static android.os.Process.THREAD_GROUP_TOP_APP;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
@@ -116,6 +117,7 @@ import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
+import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND;
@@ -1731,6 +1733,11 @@ public class OomAdjuster {
// The recently used non-top visible freeform app.
schedGroup = SCHED_GROUP_TOP_APP;
mAdjType = "perceptible-freeform-activity";
+ } else if ((flags
+ & WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) != 0) {
+ // Currently the only case is from freeform apps which are not close to top.
+ schedGroup = SCHED_GROUP_FOREGROUND_WINDOW;
+ mAdjType = "vis-multi-window-activity";
}
foregroundActivities = true;
mHasVisibleActivities = true;
@@ -3438,6 +3445,9 @@ public class OomAdjuster {
case SCHED_GROUP_RESTRICTED:
processGroup = THREAD_GROUP_RESTRICTED;
break;
+ case SCHED_GROUP_FOREGROUND_WINDOW:
+ processGroup = THREAD_GROUP_FOREGROUND_WINDOW;
+ break;
default:
processGroup = THREAD_GROUP_DEFAULT;
break;
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 21842db590b0..fb1c2e9a1f9d 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -331,7 +331,7 @@ public class OomAdjusterModernImpl extends OomAdjuster {
void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) {
ProcessRecordNode node = mLastNode[slot].mNext;
final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL;
- while (node != tail) {
+ while (node != null && node != tail) {
mTmpOomAdjusterArgs.mApp = node.mApp;
if (node.mApp == null) {
// TODO(b/336178916) - Temporary logging for root causing b/336178916.
@@ -365,7 +365,9 @@ public class OomAdjusterModernImpl extends OomAdjuster {
}
// Save the next before calling callback, since that may change the node.mNext.
final ProcessRecordNode next = node.mNext;
- callback.accept(mTmpOomAdjusterArgs);
+ if (mTmpOomAdjusterArgs.mApp != null) {
+ callback.accept(mTmpOomAdjusterArgs);
+ }
// There are couple of cases:
// a) The current node is moved to another slot
// - for this case, we'd need to keep using the "next" node.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cb918a045ec6..00250b4ef463 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -303,6 +303,9 @@ public final class ProcessList {
// Activity manager's version of Process.THREAD_GROUP_TOP_APP
// Disambiguate between actual top app and processes bound to the top app
static final int SCHED_GROUP_TOP_APP_BOUND = 4;
+ // Activity manager's version of Process.THREAD_GROUP_FOREGROUND_WINDOW
+ // The priority is like between default and top-app.
+ static final int SCHED_GROUP_FOREGROUND_WINDOW = 5;
// The minimum number of cached apps we want to be able to keep around,
// without empty apps being able to push them out of memory.
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index e145c90fb1c1..55d9c6eac87a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -533,7 +533,8 @@ public class AudioDeviceBroker {
AudioDeviceInfo.TYPE_BLE_SPEAKER,
AudioDeviceInfo.TYPE_LINE_ANALOG,
AudioDeviceInfo.TYPE_HDMI,
- AudioDeviceInfo.TYPE_AUX_LINE
+ AudioDeviceInfo.TYPE_AUX_LINE,
+ AudioDeviceInfo.TYPE_BUS
};
/*package */ static boolean isValidCommunicationDevice(@NonNull AudioDeviceInfo device) {
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index 38eb416ffdd8..ddea285d3564 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -109,7 +109,7 @@ public class DisplayControl {
/**
* Sets the HDR conversion mode for the device.
*
- * Returns the system preferred Hdr output type nn case when HDR conversion mode is
+ * Returns the system preferred HDR output type in case when HDR conversion mode is
* {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_SYSTEM}.
* Returns Hdr::INVALID in other cases.
* @hide
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 93bd92614403..acf4db30ba93 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -318,13 +318,16 @@ final class DisplayDeviceInfo {
*/
public Display.HdrCapabilities hdrCapabilities;
+ /** When true, all HDR capabilities are hidden from public APIs */
+ public boolean isForceSdr;
+
/**
* Indicates whether this display supports Auto Low Latency Mode.
*/
public boolean allmSupported;
/**
- * Indicates whether this display suppors Game content type.
+ * Indicates whether this display supports Game content type.
*/
public boolean gameContentTypeSupported;
@@ -516,6 +519,7 @@ final class DisplayDeviceInfo {
|| !Arrays.equals(supportedModes, other.supportedModes)
|| !Arrays.equals(supportedColorModes, other.supportedColorModes)
|| !Objects.equals(hdrCapabilities, other.hdrCapabilities)
+ || isForceSdr != other.isForceSdr
|| allmSupported != other.allmSupported
|| gameContentTypeSupported != other.gameContentTypeSupported
|| densityDpi != other.densityDpi
@@ -560,6 +564,7 @@ final class DisplayDeviceInfo {
colorMode = other.colorMode;
supportedColorModes = other.supportedColorModes;
hdrCapabilities = other.hdrCapabilities;
+ isForceSdr = other.isForceSdr;
allmSupported = other.allmSupported;
gameContentTypeSupported = other.gameContentTypeSupported;
densityDpi = other.densityDpi;
@@ -603,6 +608,7 @@ final class DisplayDeviceInfo {
sb.append(", colorMode ").append(colorMode);
sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes));
sb.append(", hdrCapabilities ").append(hdrCapabilities);
+ sb.append(", isForceSdr ").append(isForceSdr);
sb.append(", allmSupported ").append(allmSupported);
sb.append(", gameContentTypeSupported ").append(gameContentTypeSupported);
sb.append(", density ").append(densityDpi);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3c2167e7a4ef..e7fd8f7db182 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -48,6 +48,7 @@ import static android.os.Process.ROOT_UID;
import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL;
import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
@@ -284,7 +285,7 @@ public final class DisplayManagerService extends SystemService {
@GuardedBy("mSyncRoot")
private int[] mUserDisabledHdrTypes = {};
@Display.HdrCapabilities.HdrType
- private int[] mSupportedHdrOutputType;
+ private int[] mSupportedHdrOutputTypes;
@GuardedBy("mSyncRoot")
private boolean mAreUserDisabledHdrTypesAllowed = true;
@@ -299,10 +300,10 @@ public final class DisplayManagerService extends SystemService {
// HDR conversion mode chosen by user
@GuardedBy("mSyncRoot")
private HdrConversionMode mHdrConversionMode = null;
- // Actual HDR conversion mode, which takes app overrides into account.
- private HdrConversionMode mOverrideHdrConversionMode = null;
+ // Whether app has disabled HDR conversion
+ private boolean mShouldDisableHdrConversion = false;
@GuardedBy("mSyncRoot")
- private int mSystemPreferredHdrOutputType = Display.HdrCapabilities.HDR_TYPE_INVALID;
+ private int mSystemPreferredHdrOutputType = HDR_TYPE_INVALID;
// The synchronization root for the display manager.
@@ -1419,7 +1420,8 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) {
+ @VisibleForTesting
+ void setUserDisabledHdrTypesInternal(int[] userDisabledHdrTypes) {
synchronized (mSyncRoot) {
if (userDisabledHdrTypes == null) {
Slog.e(TAG, "Null is not an expected argument to "
@@ -1437,6 +1439,7 @@ public final class DisplayManagerService extends SystemService {
if (Arrays.equals(mUserDisabledHdrTypes, userDisabledHdrTypes)) {
return;
}
+
String userDisabledFormatsString = "";
if (userDisabledHdrTypes.length != 0) {
userDisabledFormatsString = TextUtils.join(",",
@@ -1452,6 +1455,15 @@ public final class DisplayManagerService extends SystemService {
handleLogicalDisplayChangedLocked(display);
});
}
+ /* Note: it may be expected to reset the Conversion Mode when an HDR type is enabled
+ and the Conversion Mode is set to System Preferred. This is handled in the Settings
+ code because in the special case where HDR is indirectly disabled by Force SDR
+ Conversion, manually enabling HDR is not recognized as an action that reduces the
+ disabled HDR count. Thus, this case needs to be checked in the Settings code when we
+ know we're enabling an HDR mode. If we split checking for SystemConversion and
+ isForceSdr in two places, we may have duplicate calls to resetting to System Conversion
+ and get two black screens.
+ */
}
}
@@ -1464,19 +1476,20 @@ public final class DisplayManagerService extends SystemService {
return true;
}
- private void setAreUserDisabledHdrTypesAllowedInternal(
+ @VisibleForTesting
+ void setAreUserDisabledHdrTypesAllowedInternal(
boolean areUserDisabledHdrTypesAllowed) {
synchronized (mSyncRoot) {
if (mAreUserDisabledHdrTypesAllowed == areUserDisabledHdrTypesAllowed) {
return;
}
mAreUserDisabledHdrTypesAllowed = areUserDisabledHdrTypesAllowed;
- if (mUserDisabledHdrTypes.length == 0) {
- return;
- }
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
areUserDisabledHdrTypesAllowed ? 1 : 0);
+ if (mUserDisabledHdrTypes.length == 0) {
+ return;
+ }
int userDisabledHdrTypes[] = {};
if (!mAreUserDisabledHdrTypesAllowed) {
userDisabledHdrTypes = mUserDisabledHdrTypes;
@@ -1487,6 +1500,14 @@ public final class DisplayManagerService extends SystemService {
display.setUserDisabledHdrTypes(finalUserDisabledHdrTypes);
handleLogicalDisplayChangedLocked(display);
});
+ // When HDR conversion mode is set to SYSTEM, modification to
+ // areUserDisabledHdrTypesAllowed requires refreshing the HDR conversion mode to tell
+ // the system which HDR types it is not allowed to use.
+ if (getHdrConversionModeInternal().getConversionMode()
+ == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
+ setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+ }
}
}
@@ -2357,7 +2378,7 @@ public final class DisplayManagerService extends SystemService {
final int preferredHdrOutputType =
hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_FORCE
? hdrConversionMode.getPreferredHdrOutputType()
- : Display.HdrCapabilities.HDR_TYPE_INVALID;
+ : HDR_TYPE_INVALID;
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.HDR_FORCE_CONVERSION_TYPE, preferredHdrOutputType);
}
@@ -2370,7 +2391,7 @@ public final class DisplayManagerService extends SystemService {
? Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.HDR_FORCE_CONVERSION_TYPE,
Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)
- : Display.HdrCapabilities.HDR_TYPE_INVALID;
+ : HDR_TYPE_INVALID;
mHdrConversionMode = new HdrConversionMode(conversionMode, preferredHdrOutputType);
setHdrConversionModeInternal(mHdrConversionMode);
}
@@ -2507,22 +2528,38 @@ public final class DisplayManagerService extends SystemService {
});
}
+ /**
+ * Returns the HDR output types that are supported by the device's HDR conversion capabilities,
+ * stripping out any user-disabled HDR types if mAreUserDisabledHdrTypesAllowed is false.
+ */
@GuardedBy("mSyncRoot")
- private int[] getEnabledAutoHdrTypesLocked() {
- IntArray autoHdrOutputTypesArray = new IntArray();
+ @VisibleForTesting
+ int[] getEnabledHdrOutputTypesLocked() {
+ if (mAreUserDisabledHdrTypesAllowed) {
+ return getSupportedHdrOutputTypesInternal();
+ }
+ // Strip out all HDR formats that are currently user-disabled
+ IntArray enabledHdrOutputTypesArray = new IntArray();
for (int type : getSupportedHdrOutputTypesInternal()) {
- boolean isDisabled = false;
+ boolean isEnabled = true;
for (int disabledType : mUserDisabledHdrTypes) {
if (type == disabledType) {
- isDisabled = true;
+ isEnabled = false;
break;
}
}
- if (!isDisabled) {
- autoHdrOutputTypesArray.add(type);
+ if (isEnabled) {
+ enabledHdrOutputTypesArray.add(type);
}
}
- return autoHdrOutputTypesArray.toArray();
+ return enabledHdrOutputTypesArray.toArray();
+ }
+
+ @VisibleForTesting
+ int[] getEnabledHdrOutputTypes() {
+ synchronized (mSyncRoot) {
+ return getEnabledHdrOutputTypesLocked();
+ }
}
@GuardedBy("mSyncRoot")
@@ -2531,7 +2568,7 @@ public final class DisplayManagerService extends SystemService {
final int preferredHdrOutputType =
mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType();
- if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) {
+ if (preferredHdrOutputType != HDR_TYPE_INVALID) {
int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency();
return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType);
}
@@ -2565,41 +2602,57 @@ public final class DisplayManagerService extends SystemService {
if (!mInjector.getHdrOutputConversionSupport()) {
return;
}
- int[] autoHdrOutputTypes = null;
+
synchronized (mSyncRoot) {
if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
&& hdrConversionMode.getPreferredHdrOutputType()
- != Display.HdrCapabilities.HDR_TYPE_INVALID) {
+ != HDR_TYPE_INVALID) {
throw new IllegalArgumentException("preferredHdrOutputType must not be set if"
+ " the conversion mode is HDR_CONVERSION_SYSTEM");
}
mHdrConversionMode = hdrConversionMode;
storeHdrConversionModeLocked(mHdrConversionMode);
- // For auto mode, all supported HDR types are allowed except the ones specifically
- // disabled by the user.
+ // If the HDR conversion is HDR_CONVERSION_SYSTEM, all supported HDR types are allowed
+ // except the ones specifically disabled by the user.
+ int[] enabledHdrOutputTypes = null;
if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
- autoHdrOutputTypes = getEnabledAutoHdrTypesLocked();
+ enabledHdrOutputTypes = getEnabledHdrOutputTypesLocked();
}
int conversionMode = hdrConversionMode.getConversionMode();
int preferredHdrType = hdrConversionMode.getPreferredHdrOutputType();
+
// If the HDR conversion is disabled by an app through WindowManager.LayoutParams, then
// set HDR conversion mode to HDR_CONVERSION_PASSTHROUGH.
- if (mOverrideHdrConversionMode == null) {
+ if (mShouldDisableHdrConversion) {
+ conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
+ preferredHdrType = -1;
+ enabledHdrOutputTypes = null;
+ } else {
// HDR_CONVERSION_FORCE with HDR_TYPE_INVALID is used to represent forcing SDR type.
- // But, internally SDR is selected by using passthrough mode.
+ // But, internally SDR is forced by using passthrough mode and not reporting any
+ // HDR capabilities to apps.
if (conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
- && preferredHdrType == Display.HdrCapabilities.HDR_TYPE_INVALID) {
+ && preferredHdrType == HDR_TYPE_INVALID) {
conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
+ mLogicalDisplayMapper.forEachLocked(
+ logicalDisplay -> {
+ if (logicalDisplay.setIsForceSdr(true)) {
+ handleLogicalDisplayChangedLocked(logicalDisplay);
+ }
+ });
+ } else {
+ mLogicalDisplayMapper.forEachLocked(
+ logicalDisplay -> {
+ if (logicalDisplay.setIsForceSdr(false)) {
+ handleLogicalDisplayChangedLocked(logicalDisplay);
+ }
+ });
}
- } else {
- conversionMode = mOverrideHdrConversionMode.getConversionMode();
- preferredHdrType = mOverrideHdrConversionMode.getPreferredHdrOutputType();
- autoHdrOutputTypes = null;
}
mSystemPreferredHdrOutputType = mInjector.setHdrConversionMode(
- conversionMode, preferredHdrType, autoHdrOutputTypes);
+ conversionMode, preferredHdrType, enabledHdrOutputTypes);
}
}
@@ -2621,8 +2674,8 @@ public final class DisplayManagerService extends SystemService {
}
HdrConversionMode mode;
synchronized (mSyncRoot) {
- mode = mOverrideHdrConversionMode != null
- ? mOverrideHdrConversionMode
+ mode = mShouldDisableHdrConversion
+ ? new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH)
: mHdrConversionMode;
// Handle default: PASSTHROUGH. Don't include the system-preferred type.
if (mode == null
@@ -2630,8 +2683,6 @@ public final class DisplayManagerService extends SystemService {
return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH);
}
// Handle default or current mode: SYSTEM. Include the system preferred type.
- // mOverrideHdrConversionMode and mHdrConversionMode do not include the system
- // preferred type, it is kept separately in mSystemPreferredHdrOutputType.
if (mode == null
|| mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
return new HdrConversionMode(
@@ -2642,10 +2693,10 @@ public final class DisplayManagerService extends SystemService {
}
private @Display.HdrCapabilities.HdrType int[] getSupportedHdrOutputTypesInternal() {
- if (mSupportedHdrOutputType == null) {
- mSupportedHdrOutputType = mInjector.getSupportedHdrOutputTypes();
+ if (mSupportedHdrOutputTypes == null) {
+ mSupportedHdrOutputTypes = mInjector.getSupportedHdrOutputTypes();
}
- return mSupportedHdrOutputType;
+ return mSupportedHdrOutputTypes;
}
void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) {
@@ -2831,15 +2882,9 @@ public final class DisplayManagerService extends SystemService {
// HDR conversion is disabled in two cases:
// - HDR conversion introduces latency and minimal post-processing is requested
// - app requests to disable HDR conversion
- if (mOverrideHdrConversionMode == null && (disableHdrConversion
- || disableHdrConversionForLatency)) {
- mOverrideHdrConversionMode =
- new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH);
- setHdrConversionModeInternal(mHdrConversionMode);
- handleLogicalDisplayChangedLocked(display);
- } else if (mOverrideHdrConversionMode != null && !disableHdrConversion
- && !disableHdrConversionForLatency) {
- mOverrideHdrConversionMode = null;
+ boolean previousShouldDisableHdrConversion = mShouldDisableHdrConversion;
+ mShouldDisableHdrConversion = disableHdrConversion || disableHdrConversionForLatency;
+ if (previousShouldDisableHdrConversion != mShouldDisableHdrConversion) {
setHdrConversionModeInternal(mHdrConversionMode);
handleLogicalDisplayChangedLocked(display);
}
@@ -3530,9 +3575,9 @@ public final class DisplayManagerService extends SystemService {
}
int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
- int[] autoHdrTypes) {
+ int[] allowedHdrOutputTypes) {
return DisplayControl.setHdrConversionMode(conversionMode, preferredHdrOutputType,
- autoHdrTypes);
+ allowedHdrOutputTypes);
}
@Display.HdrCapabilities.HdrType
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index e8be8a449652..007e3a8fde2f 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -518,6 +518,7 @@ final class LogicalDisplay {
deviceInfo.supportedColorModes,
deviceInfo.supportedColorModes.length);
mBaseDisplayInfo.hdrCapabilities = deviceInfo.hdrCapabilities;
+ mBaseDisplayInfo.isForceSdr = deviceInfo.isForceSdr;
mBaseDisplayInfo.userDisabledHdrTypes = mUserDisabledHdrTypes;
mBaseDisplayInfo.minimalPostProcessingSupported =
deviceInfo.allmSupported || deviceInfo.gameContentTypeSupported;
@@ -899,6 +900,29 @@ final class LogicalDisplay {
}
/**
+ * Checks whether display is of the type where HDR settings are relevant, and then sets
+ * whether Force SDR conversion mode is active. isForceSdr is checked by the Display when
+ * returning HDR capabilities.
+ *
+ * @param isForceSdr Whether Force SDR conversion mode is active
+ * @return Whether Display Manager should call handleLogicalDisplayChangedLocked()
+ */
+ public boolean setIsForceSdr(boolean isForceSdr) {
+ int displayType = getDisplayInfoLocked().type;
+ boolean isTargetDisplayType = displayType == Display.TYPE_INTERNAL
+ || displayType == Display.TYPE_EXTERNAL
+ || displayType == Display.TYPE_OVERLAY;
+
+ boolean handleLogicalDisplayChangedLocked = false;
+ if (isTargetDisplayType && mBaseDisplayInfo.isForceSdr != isForceSdr) {
+ mBaseDisplayInfo.isForceSdr = isForceSdr;
+ mInfo.set(null);
+ handleLogicalDisplayChangedLocked = true;
+ }
+ return handleLogicalDisplayChangedLocked;
+ }
+
+ /**
* Swap the underlying {@link DisplayDevice} with the specified LogicalDisplay.
*
* @param targetDisplay The display with which to swap display-devices.
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index ea240c75452d..9d04682f2374 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -196,12 +196,7 @@ final class UpdatableFontDir {
File signatureFile = new File(dir, FONT_SIGNATURE_FILE);
if (!signatureFile.exists()) {
Slog.i(TAG, "The signature file is missing.");
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- return;
- } else {
- FileUtils.deleteContentsAndDir(dir);
- continue;
- }
+ return;
}
byte[] signature;
try {
@@ -226,39 +221,33 @@ final class UpdatableFontDir {
FontFileInfo fontFileInfo = validateFontFile(fontFile, signature);
if (fontConfig == null) {
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- // Use preinstalled font config for checking revision number.
- fontConfig = mConfigSupplier.apply(Collections.emptyMap());
- } else {
- fontConfig = getSystemFontConfig();
- }
+ // Use preinstalled font config for checking revision number.
+ fontConfig = mConfigSupplier.apply(Collections.emptyMap());
}
addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */);
}
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- // Treat as error if post script name of font family was not installed.
- for (int i = 0; i < config.fontFamilies.size(); ++i) {
- FontUpdateRequest.Family family = config.fontFamilies.get(i);
- for (int j = 0; j < family.getFonts().size(); ++j) {
- FontUpdateRequest.Font font = family.getFonts().get(j);
- if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
- continue;
- }
-
- if (fontConfig == null) {
- fontConfig = mConfigSupplier.apply(Collections.emptyMap());
- }
-
- if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
- continue;
- }
-
- Slog.e(TAG, "Unknown font that has PostScript name "
- + font.getPostScriptName() + " is requested in FontFamily "
- + family.getName());
- return;
+ // Treat as error if post script name of font family was not installed.
+ for (int i = 0; i < config.fontFamilies.size(); ++i) {
+ FontUpdateRequest.Family family = config.fontFamilies.get(i);
+ for (int j = 0; j < family.getFonts().size(); ++j) {
+ FontUpdateRequest.Font font = family.getFonts().get(j);
+ if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
+ continue;
}
+
+ if (fontConfig == null) {
+ fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+ }
+
+ if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
+ continue;
+ }
+
+ Slog.e(TAG, "Unknown font that has PostScript name "
+ + font.getPostScriptName() + " is requested in FontFamily "
+ + family.getName());
+ return;
}
}
@@ -273,9 +262,7 @@ final class UpdatableFontDir {
mFontFileInfoMap.clear();
mLastModifiedMillis = 0;
FileUtils.deleteContents(mFilesDir);
- if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
- mConfigFile.delete();
- }
+ mConfigFile.delete();
}
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 164b230a0d3b..ac75ef7b4656 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1380,7 +1380,8 @@ public class HdmiControlService extends SystemService {
@ServiceThreadOnly
private List<Integer> getCecLocalDeviceTypes() {
ArrayList<Integer> allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices);
- if (isDsmEnabled() && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+ if (!isTvDevice() && isDsmEnabled()
+ && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
&& isArcSupported() && mSoundbarModeFeatureFlagEnabled) {
allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
}
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index 3f11e7836609..5ff8568f81b2 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -29,7 +29,9 @@ import android.util.Slog;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -49,6 +51,7 @@ public class TouchpadDebugView extends LinearLayout {
private static final float DEFAULT_RES_X = 47f;
private static final float DEFAULT_RES_Y = 45f;
private static final int TEXT_PADDING_DP = 12;
+ private static final int ROUNDED_CORNER_RADIUS_DP = 24;
/**
* Input device ID for the touchpad that this debug view is displaying.
@@ -152,6 +155,30 @@ public class TouchpadDebugView extends LinearLayout {
}
@Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ postDelayed(() -> {
+ final ViewRootImpl viewRootImpl = getRootView().getViewRootImpl();
+ if (viewRootImpl == null) {
+ Slog.d("TouchpadDebugView", "ViewRootImpl is null.");
+ return;
+ }
+
+ SurfaceControl surfaceControl = viewRootImpl.getSurfaceControl();
+ if (surfaceControl != null && surfaceControl.isValid()) {
+ try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
+ transaction.setCornerRadius(surfaceControl,
+ TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+ ROUNDED_CORNER_RADIUS_DP,
+ getResources().getDisplayMetrics())).apply();
+ }
+ } else {
+ Slog.d("TouchpadDebugView", "SurfaceControl is invalid or has been released.");
+ }
+ }, 100);
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent event) {
float deltaX;
float deltaY;
diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
index 67c3621b7c8c..2eed9ba95413 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
@@ -27,20 +27,28 @@ import com.android.server.input.TouchpadFingerState;
import com.android.server.input.TouchpadHardwareProperties;
import com.android.server.input.TouchpadHardwareState;
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.Map;
+
public class TouchpadVisualizationView extends View {
private static final String TAG = "TouchpadVizMain";
private static final boolean DEBUG = true;
private static final float DEFAULT_RES_X = 47f;
private static final float DEFAULT_RES_Y = 45f;
+ private static final float MAX_TRACE_HISTORY_DURATION_SECONDS = 1f;
private final TouchpadHardwareProperties mTouchpadHardwareProperties;
private float mScaleFactor;
- TouchpadHardwareState mLatestHardwareState = new TouchpadHardwareState(0, 0, 0, 0,
- new TouchpadFingerState[]{});
+ private final ArrayDeque<TouchpadHardwareState> mHardwareStateHistory =
+ new ArrayDeque<TouchpadHardwareState>();
+ private final Map<Integer, TouchpadFingerState> mTempFingerStatesByTrackingId = new HashMap<>();
private final Paint mOvalStrokePaint;
private final Paint mOvalFillPaint;
+ private final Paint mTracePaint;
+ private final Paint mCenterPointPaint;
private final RectF mTempOvalRect = new RectF();
public TouchpadVisualizationView(Context context,
@@ -55,6 +63,29 @@ public class TouchpadVisualizationView extends View {
mOvalFillPaint = new Paint();
mOvalFillPaint.setAntiAlias(true);
mOvalFillPaint.setARGB(255, 0, 0, 0);
+ mTracePaint = new Paint();
+ mTracePaint.setAntiAlias(false);
+ mTracePaint.setARGB(255, 0, 0, 255);
+ mTracePaint.setStyle(Paint.Style.STROKE);
+ mTracePaint.setStrokeWidth(2);
+ mCenterPointPaint = new Paint();
+ mCenterPointPaint.setAntiAlias(true);
+ mCenterPointPaint.setARGB(255, 255, 0, 0);
+ mCenterPointPaint.setStrokeWidth(2);
+ }
+
+ private void removeOldPoints() {
+ float latestTimestamp = mHardwareStateHistory.getLast().getTimestamp();
+
+ while (!mHardwareStateHistory.isEmpty()) {
+ TouchpadHardwareState oldestPoint = mHardwareStateHistory.getFirst();
+ float onScreenTime = latestTimestamp - oldestPoint.getTimestamp();
+ if (onScreenTime >= MAX_TRACE_HISTORY_DURATION_SECONDS) {
+ mHardwareStateHistory.removeFirst();
+ } else {
+ break;
+ }
+ }
}
private void drawOval(Canvas canvas, float x, float y, float major, float minor, float angle) {
@@ -71,19 +102,22 @@ public class TouchpadVisualizationView extends View {
@Override
protected void onDraw(Canvas canvas) {
+ if (mHardwareStateHistory.isEmpty()) {
+ return;
+ }
+
+ TouchpadHardwareState latestHardwareState = mHardwareStateHistory.getLast();
+
float maximumPressure = 0;
- for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) {
+ for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) {
maximumPressure = Math.max(maximumPressure, touchpadFingerState.getPressure());
}
- for (TouchpadFingerState touchpadFingerState : mLatestHardwareState.getFingerStates()) {
- float newX = translateRange(mTouchpadHardwareProperties.getLeft(),
- mTouchpadHardwareProperties.getRight(), 0, getWidth(),
- touchpadFingerState.getPositionX());
+ // Visualizing fingers as ovals
+ for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) {
+ float newX = translateX(touchpadFingerState.getPositionX());
- float newY = translateRange(mTouchpadHardwareProperties.getTop(),
- mTouchpadHardwareProperties.getBottom(), 0, getHeight(),
- touchpadFingerState.getPositionY());
+ float newY = translateY(touchpadFingerState.getPositionY());
float newAngle = translateRange(0, mTouchpadHardwareProperties.getOrientationMaximum(),
0, 90, touchpadFingerState.getOrientation());
@@ -102,6 +136,28 @@ public class TouchpadVisualizationView extends View {
drawOval(canvas, newX, newY, newTouchMajor, newTouchMinor, newAngle);
}
+
+ mTempFingerStatesByTrackingId.clear();
+
+ // Drawing the trace
+ for (TouchpadHardwareState currentHardwareState : mHardwareStateHistory) {
+ for (TouchpadFingerState currentFingerState : currentHardwareState.getFingerStates()) {
+ TouchpadFingerState prevFingerState = mTempFingerStatesByTrackingId.put(
+ currentFingerState.getTrackingId(), currentFingerState);
+
+ if (prevFingerState == null) {
+ continue;
+ }
+
+ float currentX = translateX(currentFingerState.getPositionX());
+ float currentY = translateY(currentFingerState.getPositionY());
+ float prevX = translateX(prevFingerState.getPositionX());
+ float prevY = translateY(prevFingerState.getPositionY());
+
+ canvas.drawLine(prevX, prevY, currentX, currentY, mTracePaint);
+ canvas.drawPoint(currentX, currentY, mCenterPointPaint);
+ }
+ }
}
/**
@@ -114,7 +170,18 @@ public class TouchpadVisualizationView extends View {
logHardwareState(schs);
}
- mLatestHardwareState = schs;
+ if (!mHardwareStateHistory.isEmpty()
+ && mHardwareStateHistory.getLast().getFingerCount() == 0
+ && schs.getFingerCount() > 0) {
+ mHardwareStateHistory.clear();
+ }
+
+ mHardwareStateHistory.addLast(schs);
+ removeOldPoints();
+
+ if (DEBUG) {
+ logFingerTrace();
+ }
invalidate();
}
@@ -128,6 +195,16 @@ public class TouchpadVisualizationView extends View {
mScaleFactor = scaleFactor;
}
+ private float translateX(float x) {
+ return translateRange(mTouchpadHardwareProperties.getLeft(),
+ mTouchpadHardwareProperties.getRight(), 0, getWidth(), x);
+ }
+
+ private float translateY(float y) {
+ return translateRange(mTouchpadHardwareProperties.getTop(),
+ mTouchpadHardwareProperties.getBottom(), 0, getHeight(), y);
+ }
+
private float translateRange(float rangeBeforeMin, float rangeBeforeMax,
float rangeAfterMin, float rangeAfterMax, float value) {
return rangeAfterMin + (value - rangeBeforeMin) / (rangeBeforeMax - rangeBeforeMin) * (
@@ -154,4 +231,10 @@ public class TouchpadVisualizationView extends View {
}
}
-}
+ private void logFingerTrace() {
+ Slog.d(TAG, "Trace size= " + mHardwareStateHistory.size());
+ for (TouchpadFingerState tfs : mHardwareStateHistory.getLast().getFingerStates()) {
+ Slog.d(TAG, "ID= " + tfs.getTrackingId());
+ }
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index fbe77720b9fc..f54c1f7654dc 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -218,10 +218,16 @@ public final class VibratorHelper {
* @param uri {@code Uri} an uri including query parameter "vibraiton_uri"
*/
public @Nullable VibrationEffect createVibrationEffectFromSoundUri(Uri uri) {
- if (uri == null) {
+ if (uri == null || uri.isOpaque()) {
return null;
}
- return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri));
+
+ try {
+ return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to get vibration effect: ", e);
+ }
+ return null;
}
/** Returns if a given vibration can be played by the vibrator that does notification buzz. */
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index d495ef5ce108..50bfbc3530a9 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -157,7 +157,7 @@ public class ZenModeConditions implements ConditionProviders.Callback {
}
// empty rule? disable and bail early
if (rule.component == null && rule.enabler == null) {
- if (!android.app.Flags.modesUi() || (android.app.Flags.modesUi() && !isManual)) {
+ if (!isManual) {
Log.w(TAG, "No component found for automatic rule: " + rule.conditionId);
rule.enabled = false;
}
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index 41351613331d..5aea356a4173 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.media.AudioAttributes.USAGE_ALARM;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.Notification;
@@ -42,6 +43,8 @@ import android.util.Log;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.List;
+
public class BackgroundUserSoundNotifier {
private static final boolean DEBUG = false;
@@ -49,11 +52,21 @@ public class BackgroundUserSoundNotifier {
private static final String BUSN_CHANNEL_ID = "bg_user_sound_channel";
private static final String BUSN_CHANNEL_NAME = "BackgroundUserSound";
public static final String ACTION_MUTE_SOUND = "com.android.server.ACTION_MUTE_BG_USER";
- private static final String EXTRA_NOTIFICATION_ID = "com.android.server.EXTRA_CLIENT_UID";
- private static final String EXTRA_CURRENT_USER_ID = "com.android.server.EXTRA_CURRENT_USER_ID";
private static final String ACTION_SWITCH_USER = "com.android.server.ACTION_SWITCH_TO_USER";
- /** ID of user with notification displayed, -1 if notification is not showing*/
- private int mUserWithNotification = -1;
+ private static final String ACTION_DISMISS_NOTIFICATION =
+ "com.android.server.ACTION_DISMISS_NOTIFICATION";
+ /**
+ * The clientUid from the AudioFocusInfo of the background user,
+ * for which an active notification is currently displayed.
+ * Set to -1 if no notification is being shown.
+ * TODO: b/367615180 - add support for multiple simultaneous alarms
+ */
+ @VisibleForTesting
+ int mNotificationClientUid = -1;
+ @VisibleForTesting
+ AudioPolicy mFocusControlAudioPolicy;
+ @VisibleForTesting
+ BackgroundUserListener mBgUserListener;
private final Context mSystemUserContext;
@VisibleForTesting
final NotificationManager mNotificationManager;
@@ -67,11 +80,18 @@ public class BackgroundUserSoundNotifier {
mSystemUserContext = context;
mNotificationManager = mSystemUserContext.getSystemService(NotificationManager.class);
mUserManager = mSystemUserContext.getSystemService(UserManager.class);
+ createNotificationChannel();
+ setupFocusControlAudioPolicy();
+ }
+
+ /**
+ * Creates a dedicated channel for background user related notifications.
+ */
+ private void createNotificationChannel() {
NotificationChannel channel = new NotificationChannel(BUSN_CHANNEL_ID, BUSN_CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH);
channel.setSound(null, null);
mNotificationManager.createNotificationChannel(channel);
- setupFocusControlAudioPolicy();
}
private void setupFocusControlAudioPolicy() {
@@ -81,15 +101,16 @@ public class BackgroundUserSoundNotifier {
ActivityManager am = mSystemUserContext.getSystemService(ActivityManager.class);
registerReceiver(am);
- BackgroundUserListener bgUserListener = new BackgroundUserListener(mSystemUserContext);
+ mBgUserListener = new BackgroundUserListener(mSystemUserContext);
AudioPolicy.Builder focusControlPolicyBuilder = new AudioPolicy.Builder(mSystemUserContext);
focusControlPolicyBuilder.setLooper(Looper.getMainLooper());
- focusControlPolicyBuilder.setAudioPolicyFocusListener(bgUserListener);
+ focusControlPolicyBuilder.setAudioPolicyFocusListener(mBgUserListener);
- AudioPolicy mFocusControlAudioPolicy = focusControlPolicyBuilder.build();
+ mFocusControlAudioPolicy = focusControlPolicyBuilder.build();
int status = mSystemUserContext.getSystemService(AudioManager.class)
.registerAudioPolicy(mFocusControlAudioPolicy);
+
if (status != AudioManager.SUCCESS) {
Log.w(LOG_TAG , "Could not register the service's focus"
+ " control audio policy, error: " + status);
@@ -117,123 +138,170 @@ public class BackgroundUserSoundNotifier {
@SuppressLint("MissingPermission")
public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
- BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(afi);
+ BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary();
}
}
+ @VisibleForTesting
+ BackgroundUserListener getAudioPolicyFocusListener() {
+ return mBgUserListener;
+ }
+
/**
* Registers a BroadcastReceiver for actions related to background user sound notifications.
* When ACTION_MUTE_SOUND is received, it mutes a background user's alarm sound.
* When ACTION_SWITCH_USER is received, a switch to the background user with alarm is started.
*/
- private void registerReceiver(ActivityManager service) {
+ private void registerReceiver(ActivityManager activityManager) {
BroadcastReceiver backgroundUserNotificationBroadcastReceiver = new BroadcastReceiver() {
@SuppressLint("MissingPermission")
@Override
public void onReceive(Context context, Intent intent) {
- if (!(intent.hasExtra(EXTRA_NOTIFICATION_ID)
- && intent.hasExtra(EXTRA_CURRENT_USER_ID)
- && intent.hasExtra(Intent.EXTRA_USER_ID))) {
+ if (mNotificationClientUid == -1) {
return;
}
- final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
+ dismissNotification();
if (DEBUG) {
- Log.d(LOG_TAG,
- "User with alarm id " + intent.getIntExtra(Intent.EXTRA_USER_ID,
- -1) + " current user id " + intent.getIntExtra(
- EXTRA_CURRENT_USER_ID, -1));
+ final int actionIndex = intent.getAction().lastIndexOf(".") + 1;
+ final String action = intent.getAction().substring(actionIndex);
+ Log.d(LOG_TAG, "Action requested: " + action + ", by userId "
+ + ActivityManager.getCurrentUser() + " for alarm on user "
+ + UserHandle.getUserHandleForUid(mNotificationClientUid));
}
- mUserWithNotification = -1;
- mNotificationManager.cancelAsUser(LOG_TAG, notificationId,
- UserHandle.of(intent.getIntExtra(EXTRA_CURRENT_USER_ID, -1)));
+
if (ACTION_MUTE_SOUND.equals(intent.getAction())) {
- final AudioManager audioManager =
- mSystemUserContext.getSystemService(AudioManager.class);
- if (audioManager != null) {
- for (AudioPlaybackConfiguration apc :
- audioManager.getActivePlaybackConfigurations()) {
- if (apc.getAudioAttributes().getUsage() == USAGE_ALARM) {
- if (apc.getPlayerProxy() != null) {
- apc.getPlayerProxy().stop();
- }
- }
- }
- }
+ muteAlarmSounds(mSystemUserContext);
} else if (ACTION_SWITCH_USER.equals(intent.getAction())) {
- service.switchUser(intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
+ activityManager.switchUser(UserHandle.getUserId(mNotificationClientUid));
}
+
+ mNotificationClientUid = -1;
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_MUTE_SOUND);
filter.addAction(ACTION_SWITCH_USER);
+ filter.addAction(ACTION_DISMISS_NOTIFICATION);
mSystemUserContext.registerReceiver(backgroundUserNotificationBroadcastReceiver, filter,
Context.RECEIVER_NOT_EXPORTED);
}
/**
+ * Stop player proxy for the ongoing alarm and drop focus for its AudioFocusInfo.
+ */
+ @VisibleForTesting
+ void muteAlarmSounds(Context context) {
+ AudioManager audioManager = context.getSystemService(AudioManager.class);
+ if (audioManager != null) {
+ for (AudioPlaybackConfiguration apc : audioManager.getActivePlaybackConfigurations()) {
+ if (apc.getClientUid() == mNotificationClientUid && apc.getPlayerProxy() != null) {
+ apc.getPlayerProxy().stop();
+ }
+ }
+ }
+ }
+
+ /**
* Check if sound is coming from background user and show notification is required.
*/
@VisibleForTesting
- void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context
- foregroundContext) throws RemoteException {
+ void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context foregroundContext)
+ throws RemoteException {
final int userId = UserHandle.getUserId(afi.getClientUid());
final int usage = afi.getAttributes().getUsage();
UserInfo userInfo = mUserManager.getUserInfo(userId);
- if (userInfo != null && userId != foregroundContext.getUserId()) {
+ // Only show notification if the sound is coming from background user and the notification
+ // is not already shown.
+ if (userInfo != null && userId != foregroundContext.getUserId()
+ && mNotificationClientUid == -1) {
//TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE
if (usage == USAGE_ALARM) {
- Intent muteIntent = createIntent(ACTION_MUTE_SOUND, afi, foregroundContext, userId);
- PendingIntent mutePI = PendingIntent.getBroadcast(mSystemUserContext, 0,
- muteIntent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE);
- Intent switchIntent = createIntent(ACTION_SWITCH_USER, afi, foregroundContext,
- userId);
- PendingIntent switchPI = PendingIntent.getBroadcast(mSystemUserContext, 0,
- switchIntent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE);
-
- mUserWithNotification = foregroundContext.getUserId();
- mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(),
- createNotification(userInfo.name, mutePI, switchPI, foregroundContext),
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Alarm ringing on background user " + userId
+ + ", displaying notification for current user "
+ + foregroundContext.getUserId());
+ }
+
+ mNotificationClientUid = afi.getClientUid();
+
+ mNotificationManager.notifyAsUser(LOG_TAG, mNotificationClientUid,
+ createNotification(userInfo.name, foregroundContext),
foregroundContext.getUser());
}
}
}
/**
- * If notification is present, dismisses it. To be called when the relevant sound loses focus.
+ * Dismisses notification if the associated focus has been removed from the focus stack.
+ * Notification remains if the focus is temporarily lost due to another client taking over the
+ * focus ownership.
*/
- private void dismissNotificationIfNecessary(AudioFocusInfo afi) {
- if (mUserWithNotification >= 0) {
- mNotificationManager.cancelAsUser(LOG_TAG, afi.getClientUid(),
- UserHandle.of(mUserWithNotification));
+ @VisibleForTesting
+ void dismissNotificationIfNecessary() {
+ if (getAudioFocusInfoForNotification() == null && mNotificationClientUid >= 0) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Alarm ringing on background user "
+ + UserHandle.getUserHandleForUid(mNotificationClientUid).getIdentifier()
+ + " left focus stack, dismissing notification");
+ }
+ dismissNotification();
+ mNotificationClientUid = -1;
}
- mUserWithNotification = -1;
}
- private Intent createIntent(String intentAction, AudioFocusInfo afi, Context fgUserContext,
- int userId) {
+ /**
+ * Dismisses notification for all users in case user switch occurred after notification was
+ * shown.
+ */
+ @SuppressLint("MissingPermission")
+ private void dismissNotification() {
+ mNotificationManager.cancelAsUser(LOG_TAG, mNotificationClientUid, UserHandle.ALL);
+ }
+
+ /**
+ * Returns AudioFocusInfo associated with the current notification.
+ */
+ @SuppressLint("MissingPermission")
+ @VisibleForTesting
+ @Nullable
+ AudioFocusInfo getAudioFocusInfoForNotification() {
+ if (mNotificationClientUid >= 0) {
+ List<AudioFocusInfo> stack = mFocusControlAudioPolicy.getFocusStack();
+ for (int i = stack.size() - 1; i >= 0; i--) {
+ if (stack.get(i).getClientUid() == mNotificationClientUid) {
+ return stack.get(i);
+ }
+ }
+ }
+ return null;
+ }
+
+ private PendingIntent createPendingIntent(String intentAction) {
final Intent intent = new Intent(intentAction);
- intent.putExtra(EXTRA_CURRENT_USER_ID, fgUserContext.getUserId());
- intent.putExtra(EXTRA_NOTIFICATION_ID, afi.getClientUid());
- intent.putExtra(Intent.EXTRA_USER_ID, userId);
- return intent;
+ PendingIntent resultPI = PendingIntent.getBroadcast(mSystemUserContext, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ return resultPI;
}
- private Notification createNotification(String userName, PendingIntent muteIntent,
- PendingIntent switchIntent, Context fgContext) {
+ @VisibleForTesting
+ Notification createNotification(String userName, Context fgContext) {
final String title = fgContext.getString(R.string.bg_user_sound_notification_title_alarm,
userName);
final int icon = R.drawable.ic_audio_alarm;
+
+ PendingIntent mutePI = createPendingIntent(ACTION_MUTE_SOUND);
+ PendingIntent switchPI = createPendingIntent(ACTION_SWITCH_USER);
+ PendingIntent dismissNotificationPI = createPendingIntent(ACTION_DISMISS_NOTIFICATION);
+
final Notification.Action mute = new Notification.Action.Builder(null,
fgContext.getString(R.string.bg_user_sound_notification_button_mute),
- muteIntent).build();
+ mutePI).build();
final Notification.Action switchUser = new Notification.Action.Builder(null,
fgContext.getString(R.string.bg_user_sound_notification_button_switch_user),
- switchIntent).build();
+ switchPI).build();
+
Notification.Builder notificationBuilder = new Notification.Builder(mSystemUserContext,
BUSN_CHANNEL_ID)
.setSmallIcon(icon)
@@ -243,16 +311,18 @@ public class BackgroundUserSoundNotifier {
.setOngoing(true)
.setColor(fgContext.getColor(R.color.system_notification_accent_color))
.setContentTitle(title)
- .setContentIntent(muteIntent)
+ .setContentIntent(mutePI)
.setAutoCancel(true)
+ .setDeleteIntent(dismissNotificationPI)
.setVisibility(Notification.VISIBILITY_PUBLIC);
+
if (mUserManager.isUserSwitcherEnabled() && (mUserManager.getUserSwitchability(
- UserHandle.of(fgContext.getUserId())) == UserManager.SWITCHABILITY_STATUS_OK)) {
+ fgContext.getUser()) == UserManager.SWITCHABILITY_STATUS_OK)) {
notificationBuilder.setActions(mute, switchUser);
} else {
notificationBuilder.setActions(mute);
}
+
return notificationBuilder.build();
}
}
-
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ee15bec0d62b..efd58ed6edcc 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -92,7 +92,6 @@ import android.graphics.Rect;
import android.multiuser.Flags;
import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IInterface;
@@ -215,7 +214,7 @@ public class LauncherAppsService extends SystemService {
@VisibleForTesting
static class LauncherAppsImpl extends ILauncherApps.Stub {
- private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+ private static final boolean DEBUG = false;
private static final String TAG = "LauncherAppsService";
private static final String NAMESPACE_MULTIUSER = "multiuser";
private static final String FLAG_NON_SYSTEM_ACCESS_TO_HIDDEN_PROFILES =
@@ -496,28 +495,8 @@ public class LauncherAppsService extends SystemService {
private boolean canAccessProfile(int callingUid, int callingUserId, int callingPid,
int targetUserId, String message) {
- if (DEBUG) {
- final AndroidPackage callingPackage =
- mPackageManagerInternal.getPackage(callingUid);
- final String callingPackageName = callingPackage == null
- ? null : callingPackage.getPackageName();
- Slog.v(TAG, "canAccessProfile called by " + callingPackageName
- + " for user " + callingUserId
- + " requesting to access user "
- + targetUserId + " when invoking " + message);
- }
- if (targetUserId == callingUserId) {
- if (DEBUG) {
- Slog.v(TAG, message + " passed canAccessProfile for targetuser"
- + targetUserId + " because it is the same as the calling user");
- }
- return true;
- }
+ if (targetUserId == callingUserId) return true;
if (injectHasInteractAcrossUsersFullPermission(callingPid, callingUid)) {
- if (DEBUG) {
- Slog.v(TAG, message + " passed because calling process"
- + "has permission to interact across users");
- }
return true;
}
@@ -535,25 +514,11 @@ public class LauncherAppsService extends SystemService {
if (isHiddenProfile(UserHandle.of(targetUserId))
&& !canAccessHiddenProfile(callingUid, callingPid)) {
- Slog.w(TAG, message + " for hidden profile user " + targetUserId
- + " from " + callingUserId + " not allowed");
-
return false;
}
- final boolean ret = mUserManagerInternal.isProfileAccessible(
- callingUserId, targetUserId, message, true);
- if (DEBUG) {
- final AndroidPackage callingPackage =
- mPackageManagerInternal.getPackage(callingUid);
- final String callingPackageName = callingPackage == null
- ? null : callingPackage.getPackageName();
- Slog.v(TAG, "canAccessProfile returned " + ret + " for " + callingPackageName
- + " for user " + callingUserId
- + " requesting to access user "
- + targetUserId + " when invoking " + message);
- }
- return ret;
+ return mUserManagerInternal.isProfileAccessible(callingUserId, targetUserId,
+ message, true);
}
private boolean isHiddenProfile(UserHandle targetUser) {
@@ -1376,10 +1341,6 @@ public class LauncherAppsService extends SystemService {
@Override
public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
UserHandle targetUser) {
- if (DEBUG) {
- Slog.v(TAG, "pinShortcuts: " + callingPackage + " is pinning shortcuts from "
- + packageName + " for user " + targetUser);
- }
if (!mShortcutServiceInternal
.areShortcutsSupportedOnHomeScreen(targetUser.getIdentifier())) {
// Requires strict ACCESS_SHORTCUTS permission for user-profiles with items
@@ -1390,11 +1351,6 @@ public class LauncherAppsService extends SystemService {
}
ensureShortcutPermission(callingPackage);
if (!canAccessProfile(targetUser.getIdentifier(), "Cannot pin shortcuts")) {
- if (DEBUG) {
- Slog.v(TAG, "pinShortcuts: " + callingPackage
- + " is pinning shortcuts from " + packageName
- + " for user " + targetUser + " but cannot access profile");
- }
return;
}
@@ -2451,7 +2407,7 @@ public class LauncherAppsService extends SystemService {
final int callbackUserId = callbackUser.getIdentifier();
final int shortcutUserId = shortcutUser.getIdentifier();
- if (shortcutUser == callbackUser) return true;
+ if ((shortcutUser.equals(callbackUser))) return true;
return mUserManagerInternal.isProfileAccessible(callbackUserId, shortcutUserId,
null, false);
}
@@ -2485,16 +2441,28 @@ public class LauncherAppsService extends SystemService {
final BroadcastCookie cookie =
(BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie, user, "onPackageRemoved")) {
+ // b/350144057
+ Slog.d(TAG, "onPackageRemoved: Skipping - profile not enabled"
+ + " or not accessible for user=" + user
+ + ", packageName=" + packageName);
continue;
}
if (!isCallingAppIdAllowed(appIdAllowList, UserHandle.getAppId(
cookie.callingUid))) {
+ // b/350144057
+ Slog.d(TAG, "onPackageRemoved: Skipping - appId not allowed"
+ + " for user=" + user
+ + ", packageName=" + packageName);
continue;
}
try {
+ // b/350144057
+ Slog.d(TAG, "onPackageRemoved: triggering onPackageRemoved"
+ + " for user=" + user
+ + ", packageName=" + packageName);
listener.onPackageRemoved(user, packageName);
} catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ Slog.d(TAG, "onPackageRemoved: Callback failed ", re);
}
}
} finally {
@@ -2524,15 +2492,27 @@ public class LauncherAppsService extends SystemService {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie, user, "onPackageAdded")) {
+ // b/350144057
+ Slog.d(TAG, "onPackageAdded: Skipping - profile not enabled"
+ + " or not accessible for user=" + user
+ + ", packageName=" + packageName);
continue;
}
if (!isPackageVisibleToListener(packageName, cookie, user)) {
+ // b/350144057
+ Slog.d(TAG, "onPackageAdded: Skipping - package filtered"
+ + " for user=" + user
+ + ", packageName=" + packageName);
continue;
}
try {
+ // b/350144057
+ Slog.d(TAG, "onPackageAdded: triggering onPackageAdded"
+ + " for user=" + user
+ + ", packageName=" + packageName);
listener.onPackageAdded(user, packageName);
} catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ Slog.d(TAG, "onPackageAdded: Callback failed ", re);
}
}
} finally {
@@ -2566,7 +2546,7 @@ public class LauncherAppsService extends SystemService {
try {
listener.onPackageChanged(user, packageName);
} catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ Slog.d(TAG, "onPackageChanged: Callback failed ", re);
}
}
} finally {
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index d65e30be9edb..045d4db0a1f1 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -42,7 +42,6 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Launcher information used by {@link ShortcutService}.
@@ -129,15 +128,9 @@ class ShortcutLauncher extends ShortcutPackageItem {
*/
public void pinShortcuts(@UserIdInt int packageUserId,
@NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) {
- if (ShortcutService.DEBUG) {
- Slog.v(TAG, "ShortcutLauncher#pinShortcuts: pin shortcuts from " + packageName
- + " with userId=" + packageUserId + " shortcutIds="
- + ids.stream().collect(Collectors.joining(", ", "[", "]")));
- }
final ShortcutPackage packageShortcuts =
mShortcutUser.getPackageShortcutsIfExists(packageName);
if (packageShortcuts == null) {
- Slog.w(TAG, "ShortcutLauncher#pinShortcuts packageShortcuts is null");
return; // No need to instantiate.
}
@@ -162,10 +155,6 @@ class ShortcutLauncher extends ShortcutPackageItem {
final String id = ids.get(i);
final ShortcutInfo si = packageShortcuts.findShortcutById(id);
if (si == null) {
- if (ShortcutService.DEBUG) {
- Slog.w(TAG, "ShortcutLauncher#pinShortcuts: cannot pin "
- + id + " because it does not exist");
- }
continue;
}
if (si.isDynamic() || si.isLongLived()
@@ -185,13 +174,6 @@ class ShortcutLauncher extends ShortcutPackageItem {
}
}
}
- if (ShortcutService.DEBUG) {
- Slog.v(TAG, "ShortcutLauncher#pinShortcuts: "
- + " newSet: " + newSet.stream().collect(
- Collectors.joining(", ", "[", "]"))
- + " floatingSet: " + floatingSet.stream().collect(
- Collectors.joining(", ", "[", "]")));
- }
mPinnedShortcuts.put(up, newSet);
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index c9ad4988f8ca..60056eb471d1 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -729,11 +729,6 @@ class ShortcutPackage extends ShortcutPackageItem {
}
pinnedShortcuts.addAll(pinned);
});
- if (ShortcutService.DEBUG) {
- Slog.v(TAG, "ShortcutPackage#refreshPinnedFlags: "
- + " pinnedShortcuts: " + pinnedShortcuts.stream().collect(
- Collectors.joining(", ", "[", "]")));
- }
// Secondly, update the pinned state if necessary.
final List<ShortcutInfo> pinned = findAll(pinnedShortcuts);
if (pinned != null) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index ea495c9bee9c..a3ff1952205f 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -169,7 +169,7 @@ import java.util.stream.Collectors;
public class ShortcutService extends IShortcutService.Stub {
static final String TAG = "ShortcutService";
- static final boolean DEBUG = Build.IS_DEBUGGABLE; // STOPSHIP if true
+ static final boolean DEBUG = false; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE;
@@ -3206,11 +3206,6 @@ public class ShortcutService extends IShortcutService.Stub {
public void pinShortcuts(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
@NonNull List<String> shortcutIds, int userId) {
- if (DEBUG) {
- Slog.v(TAG, "pinShortcuts: " + callingPackage + ", with userId=" + launcherUserId
- + ", is trying to pin shortcuts from " + packageName
- + " with userId=" + userId);
- }
// Calling permission must be checked by LauncherAppsImpl.
Preconditions.checkStringNotEmpty(packageName, "packageName");
Objects.requireNonNull(shortcutIds, "shortcutIds");
@@ -3235,11 +3230,6 @@ public class ShortcutService extends IShortcutService.Stub {
&& !si.isDeclaredInManifest(),
ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO,
callingPackage, launcherUserId, false);
- } else {
- if (DEBUG) {
- Slog.w(TAG, "specified package " + packageName + ", with userId=" + userId
- + ", doesn't exist.");
- }
}
// Get list of shortcuts that will get unpinned.
ArraySet<String> oldPinnedIds = launcher.getPinnedShortcutIds(packageName, userId);
@@ -5458,17 +5448,6 @@ public class ShortcutService extends IShortcutService.Stub {
*/
private List<ShortcutInfo> prepareChangedShortcuts(ArraySet<String> changedIds,
ArraySet<String> newIds, List<ShortcutInfo> deletedList, final ShortcutPackage ps) {
- if (DEBUG) {
- Slog.v(TAG, "prepareChangedShortcuts: "
- + " changedIds=" + (changedIds == null
- ? "n/a" : changedIds.stream().collect(Collectors.joining(", ", "[", "]")))
- + " newIds=" + (newIds == null
- ? "n/a" : newIds.stream().collect(Collectors.joining(", ", "[", "]")))
- + " deletedList=" + (deletedList == null
- ? "n/a" : deletedList.stream().map(ShortcutInfo::getId).collect(
- Collectors.joining(", ", "[", "]")))
- + " ps=" + (ps == null ? "n/a" : ps.getPackageName()));
- }
if (ps == null) {
// This can happen when package restore is not finished yet.
return null;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a683a8c54849..89417f3765ff 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1090,6 +1090,21 @@ public class UserManagerService extends IUserManager.Stub {
mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler);
emulateSystemUserModeIfNeeded();
+ initPropertyInvalidatedCaches();
+ }
+
+ /**
+ * This method is used to invalidate the caches at server statup,
+ * so that caches can start working.
+ */
+ private static final void initPropertyInvalidatedCaches() {
+ if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
+ UserManager.invalidateIsUserUnlockedCache();
+ UserManager.invalidateQuietModeEnabledCache();
+ UserManager.invalidateStaticUserProperties();
+ UserManager.invalidateUserPropertiesCache();
+ UserManager.invalidateUserSerialNumberCache();
+ }
}
private boolean doesDeviceHardwareSupportPrivateSpace() {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 953aae9588dd..457196b74d2e 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -89,6 +89,7 @@ import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockSettingsStateListener;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.ServiceWatcher;
import com.android.server.utils.Slogf;
@@ -170,6 +171,7 @@ public class TrustManagerService extends SystemService {
private final ActivityManager mActivityManager;
private FingerprintManager mFingerprintManager;
private FaceManager mFaceManager;
+ private UserManagerInternal mUserManagerInternal;
private enum TrustState {
// UNTRUSTED means that TrustManagerService is currently *not* giving permission for the
@@ -1064,6 +1066,8 @@ public class TrustManagerService extends SystemService {
Log.w(TAG, "Unable to check keyguard lock state", e);
}
currentUserIsUnlocked = unlockedUser == id;
+ } else if (isVisibleBackgroundUser(id)) {
+ showingKeyguard = !mUserManager.isUserUnlocked(id);
}
final boolean deviceLocked = secure && showingKeyguard && !trusted
&& !biometricAuthenticated;
@@ -1095,6 +1099,16 @@ public class TrustManagerService extends SystemService {
}
}
+ private boolean isVisibleBackgroundUser(int userId) {
+ if (!mUserManager.isVisibleBackgroundUsersSupported()) {
+ return false;
+ }
+ if (mUserManagerInternal == null) {
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ }
+ return mUserManagerInternal.isVisibleBackgroundFullUser(userId);
+ }
+
private void notifyTrustAgentsOfDeviceLockState(int userId, boolean isLocked) {
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo agent = mActiveAgents.valueAt(i);
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index 3be266e2951b..f069dcdbc86b 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -145,11 +145,13 @@ class AppCompatSizeCompatModePolicy {
}
}
- void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds) {
+ void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds,
+ @NonNull Configuration newParentConfig) {
mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy()
.findOpaqueNotFinishingActivityBelow()
.map(activityRecord -> mSizeCompatScale)
- .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds));
+ .orElseGet(() -> calculateSizeCompatScale(
+ resolvedAppBounds, containerAppBounds, newParentConfig));
}
void clearSizeCompatModeAttributes() {
@@ -290,7 +292,7 @@ class AppCompatSizeCompatModePolicy {
// Calculates the scale the size compatibility bounds into the region which is available
// to application.
final float lastSizeCompatScale = mSizeCompatScale;
- updateSizeCompatScale(resolvedAppBounds, containerAppBounds);
+ updateSizeCompatScale(resolvedAppBounds, containerAppBounds, newParentConfiguration);
final int containerTopInset = containerAppBounds.top - containerBounds.top;
final boolean topNotAligned =
@@ -423,7 +425,7 @@ class AppCompatSizeCompatModePolicy {
}
private float calculateSizeCompatScale(@NonNull Rect resolvedAppBounds,
- @NonNull Rect containerAppBounds) {
+ @NonNull Rect containerAppBounds, @NonNull Configuration newParentConfig) {
final int contentW = resolvedAppBounds.width();
final int contentH = resolvedAppBounds.height();
final int viewportW = containerAppBounds.width();
@@ -432,7 +434,8 @@ class AppCompatSizeCompatModePolicy {
// original container or if it's a freeform window in desktop mode.
boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
|| (canEnterDesktopMode(mActivityRecord.mAtmService.mContext)
- && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FREEFORM);
+ && newParentConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM);
return shouldAllowUpscaling ? Math.min(
(float) viewportW / contentW, (float) viewportH / contentH) : 1f;
}
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 19941741ed19..3a2cffbe1d85 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -422,6 +422,7 @@ class DeferredDisplayUpdater {
|| first.brightnessMaximum != second.brightnessMaximum
|| first.brightnessDefault != second.brightnessDefault
|| first.installOrientation != second.installOrientation
+ || first.isForceSdr != second.isForceSdr
|| !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
|| !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio)
|| !first.thermalRefreshRateThrottling.contentEquals(
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 5514294ed477..e007b1d07b34 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -181,22 +181,30 @@ class EmbeddedWindowController {
return true;
}
- boolean transferToHost(@NonNull InputTransferToken embeddedWindowToken,
+ boolean transferToHost(int callingUid, @NonNull InputTransferToken embeddedWindowToken,
@NonNull WindowState transferToHostWindowState) {
EmbeddedWindow ew = getByInputTransferToken(embeddedWindowToken);
if (!isValidTouchGestureParams(transferToHostWindowState, ew)) {
return false;
}
+ if (callingUid != ew.mOwnerUid) {
+ throw new SecurityException(
+ "Transfer request must originate from owner of transferFromToken");
+ }
return mInputManagerService.transferTouchGesture(ew.getInputChannelToken(),
transferToHostWindowState.mInputChannelToken);
}
- boolean transferToEmbedded(WindowState hostWindowState,
+ boolean transferToEmbedded(int callingUid, WindowState hostWindowState,
@NonNull InputTransferToken transferToToken) {
final EmbeddedWindowController.EmbeddedWindow ew = getByInputTransferToken(transferToToken);
if (!isValidTouchGestureParams(hostWindowState, ew)) {
return false;
}
+ if (callingUid != hostWindowState.mOwnerUid) {
+ throw new SecurityException(
+ "Transfer request must originate from owner of transferFromToken");
+ }
return mInputManagerService.transferTouchGesture(hostWindowState.mInputChannelToken,
ew.getInputChannelToken());
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 638e92f112c7..42ea5a88a09b 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -28,6 +28,7 @@ import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -57,6 +58,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.LaunchParamsController.LaunchParams;
import java.io.PrintWriter;
@@ -1761,10 +1763,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
* @return last reparented root task, or {@code null} if the root tasks had to be destroyed.
*/
Task remove() {
+ final TaskDisplayArea toDisplayArea = getReparentToTaskDisplayArea(getFocusedRootTask());
mPreferredTopFocusableRootTask = null;
// TODO(b/153090332): Allow setting content removal mode per task display area
final boolean destroyContentOnRemoval = mDisplayContent.shouldDestroyContentOnRemove();
- final TaskDisplayArea toDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
Task lastReparentedRootTask = null;
// Root tasks could be reparented from the removed display area to other display area. After
@@ -1830,6 +1832,41 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
return lastReparentedRootTask;
}
+ /**
+ * Returns the {@link TaskDisplayArea} to which root tasks should be reparented.
+ *
+ * <p>In the automotive multi-user multi-display environment where background users have
+ * UI access on their assigned displays (a.k.a. visible background users), it's not allowed to
+ * launch an activity on an unassigned display. If an activity is attempted to launch on an
+ * unassigned display, it throws an exception.
+ * <p>This method determines the appropriate {@link TaskDisplayArea} for reparenting root tasks
+ * when a display is removed, in order to avoid the exception. If the root task is null,
+ * the visible background user is not supported or the user associated with the root task is
+ * not a visible background user, it returns the default {@link TaskDisplayArea} of the default
+ * display. Otherwise, it returns the default {@link TaskDisplayArea} of the main display
+ * assigned to the user.
+ *
+ * @param rootTask The root task whose {@link TaskDisplayArea} needs to be determined.
+ * @return The {@link TaskDisplayArea} where the root tasks should be reparented to.
+ */
+ private TaskDisplayArea getReparentToTaskDisplayArea(Task rootTask) {
+ final TaskDisplayArea defaultTaskDisplayArea =
+ mRootWindowContainer.getDefaultTaskDisplayArea();
+ if (rootTask == null) {
+ return defaultTaskDisplayArea;
+ }
+ UserManagerInternal userManagerInternal = mAtmService.mWindowManager.mUmInternal;
+ if (!userManagerInternal.isVisibleBackgroundFullUser(rootTask.mUserId)) {
+ return defaultTaskDisplayArea;
+ }
+ int toDisplayId = userManagerInternal.getMainDisplayAssignedToUser(rootTask.mUserId);
+ if (toDisplayId == INVALID_DISPLAY) {
+ return defaultTaskDisplayArea;
+ }
+ DisplayContent dc = mRootWindowContainer.getDisplayContent(toDisplayId);
+ return dc != null ? dc.getDefaultTaskDisplayArea() : defaultTaskDisplayArea;
+ }
+
/** Whether this task display area can request orientation. */
boolean canSpecifyOrientation(@ScreenOrientation int orientation) {
// Only allow to specify orientation if this TDA is the last focused one on this logical
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 33f2dd103c2e..b8f47cce6005 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9212,6 +9212,8 @@ public class WindowManagerService extends IWindowManager.Stub
final InputApplicationHandle applicationHandle;
final String name;
Objects.requireNonNull(outInputChannel);
+ Objects.requireNonNull(inputTransferToken);
+
synchronized (mGlobalLock) {
WindowState hostWindowState = hostInputTransferToken != null
? mInputToWindowMap.get(hostInputTransferToken.getToken()) : null;
@@ -9236,6 +9238,7 @@ public class WindowManagerService extends IWindowManager.Stub
Objects.requireNonNull(transferFromToken);
Objects.requireNonNull(transferToToken);
+ final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
boolean didTransfer;
try {
@@ -9245,12 +9248,14 @@ public class WindowManagerService extends IWindowManager.Stub
// represents an embedded window so transfer from host to embedded.
WindowState windowStateTo = mInputToWindowMap.get(transferToToken.getToken());
if (windowStateTo != null) {
- didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken,
+ didTransfer = mEmbeddedWindowController.transferToHost(callingUid,
+ transferFromToken,
windowStateTo);
} else {
WindowState windowStateFrom = mInputToWindowMap.get(
transferFromToken.getToken());
- didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom,
+ didTransfer = mEmbeddedWindowController.transferToEmbedded(callingUid,
+ windowStateFrom,
transferToToken);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 976be4aa3bd4..30d6f0a46bae 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -326,6 +326,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
public static final int ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK = 1 << 22;
public static final int ACTIVITY_STATE_FLAG_RESUMED_SPLIT_SCREEN = 1 << 23;
public static final int ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM = 1 << 24;
+ public static final int ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE = 1 << 25;
public static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff;
/**
@@ -1293,8 +1294,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
if (hasResumedFreeform
&& com.android.window.flags.Flags.processPriorityPolicyForMultiWindowMode()
// Exclude task layer 1 because it is already the top most.
- && minTaskLayer > 1 && minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
- stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
+ && minTaskLayer > 1) {
+ if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) {
+ stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM;
+ } else {
+ stateFlags |= ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE;
+ }
}
stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
if (visible) {
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index 63cf7bf7cb5e..c05c3819ca28 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -139,16 +139,15 @@ class MetadataSyncAdapterTest {
runtimeSearchSession.put(putDocumentsRequest).get()
staticSearchSession.put(putDocumentsRequest).get()
val metadataSyncAdapter =
- MetadataSyncAdapter(
- testExecutor,
- runtimeSearchSession,
+ MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+
+ val submitSyncRequest =
+ metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
staticSearchSession,
- packageManager,
+ runtimeSearchSession,
)
- val submitSyncRequest = metadataSyncAdapter.submitSyncRequest()
-
- assertThat(submitSyncRequest.get()).isTrue()
+ assertThat(submitSyncRequest).isInstanceOf(Unit::class.java)
}
@Test
@@ -182,16 +181,15 @@ class MetadataSyncAdapterTest {
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
staticSearchSession.put(putDocumentsRequest).get()
val metadataSyncAdapter =
- MetadataSyncAdapter(
- testExecutor,
- runtimeSearchSession,
+ MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+
+ val submitSyncRequest =
+ metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
staticSearchSession,
- packageManager,
+ runtimeSearchSession,
)
- val submitSyncRequest = metadataSyncAdapter.submitSyncRequest()
-
- assertThat(submitSyncRequest.get()).isTrue()
+ assertThat(submitSyncRequest).isInstanceOf(Unit::class.java)
}
@Test
@@ -239,16 +237,15 @@ class MetadataSyncAdapterTest {
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
runtimeSearchSession.put(putDocumentsRequest).get()
val metadataSyncAdapter =
- MetadataSyncAdapter(
- testExecutor,
- runtimeSearchSession,
+ MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+
+ val submitSyncRequest =
+ metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
staticSearchSession,
- packageManager,
+ runtimeSearchSession,
)
- val submitSyncRequest = metadataSyncAdapter.submitSyncRequest()
-
- assertThat(submitSyncRequest.get()).isTrue()
+ assertThat(submitSyncRequest).isInstanceOf(Unit::class.java)
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 8b80f85aec00..255dcb083518 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -27,6 +27,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_D
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.server.display.ExternalDisplayPolicy.ENABLE_ON_CONNECT;
@@ -195,8 +196,8 @@ public class DisplayManagerServiceTest {
private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS =
STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
@@ -238,6 +239,8 @@ public class DisplayManagerServiceTest {
private UserManager mUserManager;
+ private int[] mAllowedHdrOutputTypes;
+
private final DisplayManagerService.Injector mShortMockedInjector =
new DisplayManagerService.Injector() {
@Override
@@ -256,11 +259,12 @@ public class DisplayManagerServiceTest {
displayAdapterListener, flags,
mMockedDisplayNotificationManager,
new LocalDisplayAdapter.Injector() {
- @Override
- public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
- return mSurfaceControlProxy;
- }
- });
+ @Override
+ public LocalDisplayAdapter.SurfaceControlProxy
+ getSurfaceControlProxy() {
+ return mSurfaceControlProxy;
+ }
+ });
}
@Override
@@ -320,7 +324,7 @@ public class DisplayManagerServiceTest {
@Override
int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
- int[] autoHdrTypes) {
+ int[] allowedHdrOutputTypes) {
mHdrConversionMode = conversionMode;
mPreferredHdrOutputType = preferredHdrOutputType;
return Display.HdrCapabilities.HDR_TYPE_INVALID;
@@ -1295,11 +1299,11 @@ public class DisplayManagerServiceTest {
.setUniqueId("uniqueId --- mirror display");
assertThrows(SecurityException.class, () -> {
localService.createVirtualDisplay(
- builder.build(),
- mMockAppToken /* callback */,
- null /* virtualDeviceToken */,
- mock(DisplayWindowPolicyController.class),
- PACKAGE_NAME);
+ builder.build(),
+ mMockAppToken /* callback */,
+ null /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
});
}
@@ -1433,7 +1437,7 @@ public class DisplayManagerServiceTest {
// The virtual display should not have FLAG_ALWAYS_UNLOCKED set.
assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags
- & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED));
+ & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED));
}
/**
@@ -1466,7 +1470,7 @@ public class DisplayManagerServiceTest {
// The virtual display should not have FLAG_PRESENTATION set.
assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags
- & DisplayDeviceInfo.FLAG_PRESENTATION));
+ & DisplayDeviceInfo.FLAG_PRESENTATION));
}
@Test
@@ -2358,6 +2362,7 @@ public class DisplayManagerServiceTest {
HdrConversionMode.HDR_CONVERSION_FORCE,
Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
displayManager.setHdrConversionModeInternal(mode);
+
assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
assertEquals(mode.getConversionMode(), mHdrConversionMode);
assertEquals(mode.getPreferredHdrOutputType(), mPreferredHdrOutputType);
@@ -2402,6 +2407,86 @@ public class DisplayManagerServiceTest {
}
@Test
+ public void testSetAreUserDisabledHdrTypesAllowed_withFalse_whenHdrDisabled_stripsHdrType() {
+ DisplayManagerService displayManager = new DisplayManagerService(
+ mContext, new BasicInjector() {
+ @Override
+ int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
+ int[] allowedTypes) {
+ mAllowedHdrOutputTypes = allowedTypes;
+ return Display.HdrCapabilities.HDR_TYPE_INVALID;
+ }
+
+ // Overriding this method to capture the allowed HDR type
+ @Override
+ int[] getSupportedHdrOutputTypes() {
+ return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
+ }
+ });
+
+ // Setup: no HDR types disabled, userDisabledTypes allowed, system conversion
+ displayManager.setUserDisabledHdrTypesInternal(new int [0]);
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(true);
+ displayManager.setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+
+ assertEquals(1, mAllowedHdrOutputTypes.length);
+ assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == mAllowedHdrOutputTypes[0]);
+
+ // Action: disable Dolby Vision, set userDisabledTypes not allowed
+ displayManager.setUserDisabledHdrTypesInternal(
+ new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION});
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(false);
+
+ assertEquals(0, mAllowedHdrOutputTypes.length);
+ }
+
+ @Test
+ public void testGetEnabledHdrTypesLocked_whenTypesDisabled_stripsDisabledTypes() {
+ DisplayManagerService displayManager = new DisplayManagerService(
+ mContext, new BasicInjector() {
+ @Override
+ int[] getSupportedHdrOutputTypes() {
+ return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
+ }
+ });
+
+ displayManager.setUserDisabledHdrTypesInternal(new int [0]);
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(true);
+ int [] enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+ assertEquals(1, enabledHdrOutputTypes.length);
+ assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]);
+
+ displayManager.setAreUserDisabledHdrTypesAllowedInternal(false);
+ enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+ assertEquals(1, enabledHdrOutputTypes.length);
+ assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == enabledHdrOutputTypes[0]);
+
+ displayManager.setUserDisabledHdrTypesInternal(
+ new int [] {Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION});
+ enabledHdrOutputTypes = displayManager.getEnabledHdrOutputTypes();
+ assertEquals(0, enabledHdrOutputTypes.length);
+ }
+
+ @Test
+ public void testSetHdrConversionModeInternal_isForceSdrIsUpdated() {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+ FakeDisplayDevice displayDevice =
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+ LogicalDisplay logicalDisplay =
+ logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+
+ displayManager.setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, HDR_TYPE_INVALID));
+ assertTrue(logicalDisplay.getDisplayInfoLocked().isForceSdr);
+
+ displayManager.setHdrConversionModeInternal(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+ assertFalse(logicalDisplay.getDisplayInfoLocked().isForceSdr);
+ }
+
+ @Test
public void testReturnsRefreshRateForDisplayAndSensor_proximitySensorSet() {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
@@ -3505,7 +3590,7 @@ public class DisplayManagerServiceTest {
}
private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
- Display.Mode[] modes) {
+ Display.Mode[] modes) {
FakeDisplayDevice displayDevice = new FakeDisplayDevice();
DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
displayDeviceInfo.supportedModes = modes;
@@ -3761,9 +3846,9 @@ public class DisplayManagerServiceTest {
public void setUserPreferredDisplayModeLocked(Display.Mode preferredMode) {
for (Display.Mode mode : mDisplayDeviceInfo.supportedModes) {
if (mode.matchesIfValid(
- preferredMode.getPhysicalWidth(),
- preferredMode.getPhysicalHeight(),
- preferredMode.getRefreshRate())) {
+ preferredMode.getPhysicalWidth(),
+ preferredMode.getPhysicalHeight(),
+ preferredMode.getRefreshRate())) {
mPreferredMode = mode;
break;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 5ec53023dc67..f6ad07d03673 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -62,6 +62,7 @@ import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
+import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP;
import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND;
@@ -534,6 +535,14 @@ public class MockingOomAdjusterTests {
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
assertEquals("perceptible-freeform-activity", app.mState.getAdjType());
+
+ doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE
+ | WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE)
+ .when(wpc).getActivityStateFlags();
+ updateOomAdj(app);
+ assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ,
+ SCHED_GROUP_FOREGROUND_WINDOW);
+ assertEquals("vis-multi-window-activity", app.mState.getAdjType());
}
@SuppressWarnings("GuardedBy")
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
index a82658b52a77..3062d5120e6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
@@ -16,13 +16,19 @@
package com.android.server.pm;
+import static android.media.AudioAttributes.USAGE_ALARM;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.testng.AssertJUnit.assertEquals;
import android.app.Notification;
import android.app.NotificationManager;
@@ -31,6 +37,9 @@ import android.content.pm.UserInfo;
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.PlayerProxy;
+import android.media.audiopolicy.AudioPolicy;
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -45,6 +54,10 @@ import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
@RunWith(JUnit4.class)
public class BackgroundUserSoundNotifierTest {
@@ -63,7 +76,10 @@ public class BackgroundUserSoundNotifierTest {
MockitoAnnotations.initMocks(this);
mSpiedContext = spy(mRealContext);
mUsersToRemove = new ArraySet<>();
- mUserManager = UserManager.get(mRealContext);
+
+ mUserManager = spy(mSpiedContext.getSystemService(UserManager.class));
+ doReturn(mUserManager)
+ .when(mSpiedContext).getSystemService(UserManager.class);
doReturn(mNotificationManager)
.when(mSpiedContext).getSystemService(NotificationManager.class);
mBackgroundUserSoundNotifier = new BackgroundUserSoundNotifier(mSpiedContext);
@@ -74,12 +90,9 @@ public class BackgroundUserSoundNotifierTest {
mUsersToRemove.stream().toList().forEach(this::removeUser);
}
@Test
- public void testAlarmOnBackgroundUser_ForegroundUserNotified() throws RemoteException {
- AudioAttributes aa = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ALARM).build();
- UserInfo user = createUser("User",
- UserManager.USER_TYPE_FULL_SECONDARY,
- 0);
+ public void testAlarmOnBackgroundUser_foregroundUserNotified() throws RemoteException {
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
final int fgUserId = mSpiedContext.getUserId();
final int bgUserUid = user.id * 100000;
doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
@@ -95,10 +108,9 @@ public class BackgroundUserSoundNotifierTest {
}
@Test
- public void testMediaOnBackgroundUser_ForegroundUserNotNotified() throws RemoteException {
+ public void testMediaOnBackgroundUser_foregroundUserNotNotified() throws RemoteException {
AudioAttributes aa = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA).build();
- UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
final int bgUserUid = mSpiedContext.getUserId() * 100000;
AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "",
/* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
@@ -109,9 +121,9 @@ public class BackgroundUserSoundNotifierTest {
}
@Test
- public void testAlarmOnForegroundUser_ForegroundUserNotNotified() throws RemoteException {
+ public void testAlarmOnForegroundUser_foregroundUserNotNotified() throws RemoteException {
AudioAttributes aa = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ALARM).build();
+ .setUsage(USAGE_ALARM).build();
final int fgUserId = mSpiedContext.getUserId();
final int fgUserUid = fgUserId * 100000;
doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
@@ -123,6 +135,109 @@ public class BackgroundUserSoundNotifierTest {
verifyZeroInteractions(mNotificationManager);
}
+ @Test
+ public void testMuteAlarmSounds() {
+ final int fgUserId = mSpiedContext.getUserId();
+ int bgUserId = fgUserId + 1;
+ int bgUserUid = bgUserId * 100000;
+ mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid;
+
+ AudioManager mockAudioManager = mock(AudioManager.class);
+ when(mSpiedContext.getSystemService(AudioManager.class)).thenReturn(mockAudioManager);
+
+ AudioPlaybackConfiguration apc1 = mock(AudioPlaybackConfiguration.class);
+ when(apc1.getClientUid()).thenReturn(bgUserUid);
+ when(apc1.getPlayerProxy()).thenReturn(mock(PlayerProxy.class));
+
+ AudioPlaybackConfiguration apc2 = mock(AudioPlaybackConfiguration.class);
+ when(apc2.getClientUid()).thenReturn(bgUserUid + 1);
+ when(apc2.getPlayerProxy()).thenReturn(mock(PlayerProxy.class));
+
+ List<AudioPlaybackConfiguration> configs = new ArrayList<>();
+ configs.add(apc1);
+ configs.add(apc2);
+ when(mockAudioManager.getActivePlaybackConfigurations()).thenReturn(configs);
+
+ AudioPolicy mockAudioPolicy = mock(AudioPolicy.class);
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", /* packageName= */ "",
+ AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0,
+ Build.VERSION.SDK_INT);
+ Stack<AudioFocusInfo> focusStack = new Stack<>();
+ focusStack.add(afi);
+ doReturn(focusStack).when(mockAudioPolicy).getFocusStack();
+ mBackgroundUserSoundNotifier.mFocusControlAudioPolicy = mockAudioPolicy;
+
+ mBackgroundUserSoundNotifier.muteAlarmSounds(mSpiedContext);
+
+ verify(apc1.getPlayerProxy()).stop();
+ verify(apc2.getPlayerProxy(), never()).stop();
+ }
+
+ @Test
+ public void testOnAudioFocusGrant_alarmOnBackgroundUser_notifiesForegroundUser() {
+ final int fgUserId = mSpiedContext.getUserId();
+ UserInfo bgUser = createUser("Background User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ int bgUserUid = bgUser.id * 100000;
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi = new AudioFocusInfo(aa, bgUserUid, "", "",
+ AudioManager.AUDIOFOCUS_GAIN, 0, 0, Build.VERSION.SDK_INT);
+
+ mBackgroundUserSoundNotifier.getAudioPolicyFocusListener()
+ .onAudioFocusGrant(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+
+ verify(mNotificationManager)
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+ }
+
+
+ @Test
+ public void testCreateNotification_UserSwitcherEnabled_bothActionsAvailable() {
+ String userName = "BgUser";
+
+ doReturn(true).when(mUserManager).isUserSwitcherEnabled();
+ doReturn(UserManager.SWITCHABILITY_STATUS_OK)
+ .when(mUserManager).getUserSwitchability(any());
+
+ Notification notification = mBackgroundUserSoundNotifier.createNotification(userName,
+ mSpiedContext);
+
+ assertEquals("Alarm for BgUser", notification.extras.getString(
+ Notification.EXTRA_TITLE));
+ assertEquals(Notification.CATEGORY_REMINDER, notification.category);
+ assertEquals(Notification.VISIBILITY_PUBLIC, notification.visibility);
+ assertEquals(com.android.internal.R.drawable.ic_audio_alarm,
+ notification.getSmallIcon().getResId());
+
+ assertEquals(2, notification.actions.length);
+ assertEquals(mSpiedContext.getString(
+ com.android.internal.R.string.bg_user_sound_notification_button_mute),
+ notification.actions[0].title);
+ assertEquals(mSpiedContext.getString(
+ com.android.internal.R.string.bg_user_sound_notification_button_switch_user),
+ notification.actions[1].title);
+ }
+
+ @Test
+ public void testCreateNotification_UserSwitcherDisabled_onlyMuteActionAvailable() {
+ String userName = "BgUser";
+
+ doReturn(false).when(mUserManager).isUserSwitcherEnabled();
+ doReturn(UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED)
+ .when(mUserManager).getUserSwitchability(any());
+
+ Notification notification = mBackgroundUserSoundNotifier.createNotification(userName,
+ mSpiedContext);
+
+ assertEquals(1, notification.actions.length);
+ assertEquals(mSpiedContext.getString(
+ com.android.internal.R.string.bg_user_sound_notification_button_mute),
+ notification.actions[0].title);
+ }
private UserInfo createUser(String name, String userType, int flags) {
UserInfo user = mUserManager.createUser(name, userType, flags);
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 1a398c5f1ec3..e0c393cada49 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -100,6 +100,7 @@ import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
+import com.android.server.pm.UserManagerInternal;
import org.junit.After;
import org.junit.Before;
@@ -145,6 +146,7 @@ public class TrustManagerServiceTest {
private static final String URI_SCHEME_PACKAGE = "package";
private static final int TEST_USER_ID = 50;
+ private static final int TEST_VISIBLE_BACKGROUND_USER_ID = 51;
private static final UserInfo TEST_USER =
new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL);
private static final int PARENT_USER_ID = 60;
@@ -170,6 +172,7 @@ public class TrustManagerServiceTest {
private @Mock KeyStoreAuthorization mKeyStoreAuthorization;
private @Mock LockPatternUtils mLockPatternUtils;
private @Mock LockSettingsInternal mLockSettingsInternal;
+ private @Mock UserManagerInternal mUserManagerInternal;
private @Mock PackageManager mPackageManager;
private @Mock UserManager mUserManager;
private @Mock IWindowManager mWindowManager;
@@ -224,6 +227,7 @@ public class TrustManagerServiceTest {
when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER));
when(mUserManager.getEnabledProfileIds(TEST_USER_ID)).thenReturn(new int[0]);
when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(TEST_USER);
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(false);
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
@@ -593,6 +597,54 @@ public class TrustManagerServiceTest {
verify(mTrustListener, never()).onTrustManagedChanged(anyBoolean(), anyInt());
}
+ @Test
+ public void testDeviceLocked_visibleBackgroundUser_userLocked() throws RemoteException {
+ setupVisibleBackgroundUser(/* visible= */ true, /* unlocked= */ false);
+ mService.waitForIdle();
+ mTrustManager.reportEnabledTrustAgentsChanged(TEST_VISIBLE_BACKGROUND_USER_ID);
+ mService.waitForIdle();
+ assertThat(mService.isDeviceLockedInner(TEST_VISIBLE_BACKGROUND_USER_ID)).isTrue();
+ }
+
+ @Test
+ public void testDeviceLocked_visibleBackgroundUser_userUnlocked() throws RemoteException {
+ setupVisibleBackgroundUser(/* visible= */ true, /* unlocked= */ true);
+ mService.waitForIdle();
+ mTrustManager.reportEnabledTrustAgentsChanged(TEST_VISIBLE_BACKGROUND_USER_ID);
+ mService.waitForIdle();
+ assertThat(mService.isDeviceLockedInner(TEST_VISIBLE_BACKGROUND_USER_ID)).isFalse();
+ }
+
+ @Test
+ public void testDeviceLocked_invisibleBackgroundUser_userUnlocked() throws RemoteException {
+ setupVisibleBackgroundUser(/* visible= */ false, /* unlocked= */ true);
+ mService.waitForIdle();
+ mTrustManager.reportEnabledTrustAgentsChanged(TEST_VISIBLE_BACKGROUND_USER_ID);
+ mService.waitForIdle();
+ assertThat(mService.isDeviceLockedInner(TEST_VISIBLE_BACKGROUND_USER_ID)).isTrue();
+ }
+
+ private void setupVisibleBackgroundUser(boolean visible, boolean unlocked) {
+ UserInfo info = new UserInfo(TEST_VISIBLE_BACKGROUND_USER_ID, "visible bg user",
+ UserInfo.FLAG_FULL);
+
+ when(mActivityManager.isUserRunning(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(true);
+
+ when(mLockPatternUtils.isSecure(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(true);
+
+ when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER, info));
+ when(mUserManager.getEnabledProfileIds(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(
+ new int[0]);
+ when(mUserManager.getUserInfo(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(info);
+ when(mUserManager.isUserUnlocked(TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(unlocked);
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(true);
+
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ LocalServices.addService(UserManagerInternal.class, mUserManagerInternal);
+ when(mUserManagerInternal.isVisibleBackgroundFullUser(
+ TEST_VISIBLE_BACKGROUND_USER_ID)).thenReturn(visible);
+ }
+
private void setUpRenewableTrust(ITrustAgentService trustAgent) throws RemoteException {
ITrustAgentServiceCallback callback = getCallback(trustAgent);
callback.setManagingTrust(true);
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 44aa868716eb..a55aa2364aa5 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -30,7 +30,6 @@ import android.graphics.fonts.SystemFonts;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.system.Os;
@@ -41,8 +40,6 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.text.flags.Flags;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -1106,7 +1103,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1126,7 +1122,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1);
@@ -1146,7 +1141,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1166,7 +1160,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1186,7 +1179,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1206,7 +1198,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1226,7 +1217,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1246,7 +1236,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1266,7 +1255,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1286,7 +1274,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1);
@@ -1306,7 +1293,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1326,7 +1312,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1346,7 +1331,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1366,7 +1350,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1386,7 +1369,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1406,7 +1388,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1426,7 +1407,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1446,7 +1426,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1466,7 +1445,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1486,7 +1464,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1506,7 +1483,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1527,7 +1503,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1548,7 +1523,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1569,7 +1543,6 @@ public final class UpdatableFontDirTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
index 4d2396c78d16..65b4ac116b34 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
@@ -104,6 +104,12 @@ public class VibratorHelperTest extends UiServiceTestCase {
}
@Test
+ public void createVibrationEffectFromSoundUri_opaqueUri() {
+ Uri uri = Uri.parse("a:b#c");
+ assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri));
+ }
+
+ @Test
public void createVibrationEffectFromSoundUri_uriWithoutRequiredQueryParameter() {
Uri uri = Settings.System.DEFAULT_NOTIFICATION_URI;
assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 8ee7e0304d2b..84c4f620f394 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -32,6 +32,7 @@ import static android.service.notification.Condition.STATE_FALSE;
import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.service.notification.ZenModeConfig.XML_VERSION_MODES_API;
+import static android.service.notification.ZenModeConfig.XML_VERSION_MODES_UI;
import static android.service.notification.ZenModeConfig.ZEN_TAG;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_NONE;
@@ -1169,6 +1170,23 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertThat(suppressedEffectsOf(result)).isEqualTo(suppressedEffectsOf(policy));
}
+ @Test
+ public void readXml_fixesWronglyDisabledManualRule() throws Exception {
+ ZenModeConfig config = getCustomConfig();
+ if (!Flags.modesUi()) {
+ config.manualRule = new ZenModeConfig.ZenRule();
+ config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ }
+ config.manualRule.enabled = false;
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeConfigXml(config, XML_VERSION_MODES_UI, /* forBackup= */ false, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig fromXml = readConfigXml(bais);
+
+ assertThat(fromXml.manualRule.enabled).isTrue();
+ }
+
private static String suppressedEffectsOf(Policy policy) {
return suppressedEffectsToString(policy.suppressedVisualEffects) + "("
+ policy.suppressedVisualEffects + ")";
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 f74340113a04..7bce8285972c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1640,7 +1640,7 @@ public class SizeCompatTests extends WindowTestsBase {
.build();
setUpApp(display);
prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
- mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
assertFalse(mActivity.inSizeCompatMode());
// Resize app to make original app bounds larger than parent bounds.
@@ -1667,7 +1667,7 @@ public class SizeCompatTests extends WindowTestsBase {
.build();
setUpApp(display);
prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
- mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
assertFalse(mActivity.inSizeCompatMode());
// Resize app to make original app bounds smaller than parent bounds.
@@ -1692,7 +1692,7 @@ public class SizeCompatTests extends WindowTestsBase {
.build();
setUpApp(display);
prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
- mActivity.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
assertFalse(mActivity.inSizeCompatMode());
final Rect originalAppBounds = mActivity.getBounds();
@@ -1705,6 +1705,38 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(originalAppBounds, mActivity.getBounds());
}
+ /**
+ * Test that when desktop mode is enabled, a freeform unresizeable activity is not up-scaled
+ * when exiting freeform despite its larger parent bounds.
+ */
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void testCompatScaling_freeformUnresizeableApp_exitFreeform_notScaled() {
+ doReturn(true).when(() ->
+ DesktopModeHelper.canEnterDesktopMode(any()));
+ final int dw = 600;
+ final int dh = 800;
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+ setUpApp(display);
+ prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
+ final Rect originalAppBounds = mActivity.getBounds();
+
+ assertFalse(mActivity.inSizeCompatMode());
+
+ // Resize app to make original app bounds smaller than parent bounds.
+ mTask.getWindowConfiguration().setAppBounds(
+ new Rect(0, 0, dw + 300, dh + 400));
+ // Change windowing mode from freeform to fullscreen
+ mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ mActivity.onConfigurationChanged(mTask.getConfiguration());
+ // App should enter size compat mode but remain its original size.
+ assertTrue(mActivity.inSizeCompatMode());
+ assertEquals(originalAppBounds, mActivity.getBounds());
+ }
+
@Test
public void testGetLetterboxInnerBounds_noScalingApplied() {
// Set up a display in portrait and ignoring orientation request.
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
index 92b6b934874f..82e53c81daaa 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
@@ -54,7 +54,7 @@ class ShowImeOnUnlockScreenTest(flicker: LegacyFlickerTest) : BaseTest(flicker)
}
transitions {
device.sleep()
- wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+ wmHelper.StateSyncBuilder().withKeyguardShowing().waitForAndVerify()
UnlockScreenRule.unlockScreen(device)
wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt
new file mode 100644
index 000000000000..69fde0168b14
--- /dev/null
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 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.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.helpers.SYSTEMUI_PACKAGE
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.traces.parsers.WindowManagerStateHelper
+import android.tools.traces.parsers.toFlickerComponent
+import android.util.Log
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.UiObjectNotFoundException
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import java.util.regex.Pattern
+
+class StartMediaProjectionAppHelper
+@JvmOverloads
+constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.StartMediaProjectionActivity.LABEL,
+ component: ComponentNameMatcher =
+ ActivityOptions.StartMediaProjectionActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
+ private val packageManager = instr.context.packageManager
+
+ fun startEntireScreenMediaProjection(wmHelper: WindowManagerStateHelper) {
+ clickStartMediaProjectionButton()
+ chooseEntireScreenOption()
+ startScreenSharing()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ }
+
+ fun startSingleAppMediaProjection(
+ wmHelper: WindowManagerStateHelper,
+ targetApp: StandardAppHelper
+ ) {
+ clickStartMediaProjectionButton()
+ chooseSingleAppOption()
+ startScreenSharing()
+ selectTargetApp(targetApp.appName)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(targetApp)
+ .waitForAndVerify()
+ }
+
+ private fun clickStartMediaProjectionButton() {
+ findObject(By.res(packageName, START_MEDIA_PROJECTION_BUTTON_ID)).also { it.click() }
+ }
+
+ private fun chooseEntireScreenOption() {
+ findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() }
+
+ val entireScreenString = getSysUiResourceString(ENTIRE_SCREEN_STRING_RES_NAME)
+ findObject(By.text(entireScreenString)).also { it.click() }
+ }
+
+ private fun selectTargetApp(targetAppName: String) {
+ // Scroll to to find target app to launch then click app icon it to start capture
+ val scrollable = UiScrollable(UiSelector().scrollable(true))
+ try {
+ scrollable.scrollForward()
+ if (!scrollable.scrollIntoView(UiSelector().text(targetAppName))) {
+ Log.e(TAG, "Didn't find target app when scrolling")
+ return
+ }
+ } catch (e: UiObjectNotFoundException) {
+ Log.d(TAG, "There was no scrolling (UI may not be scrollable")
+ }
+
+ findObject(By.text(targetAppName)).also { it.click() }
+ }
+
+ private fun chooseSingleAppOption() {
+ findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() }
+
+ val singleAppString = getSysUiResourceString(SINGLE_APP_STRING_RES_NAME)
+ findObject(By.text(singleAppString)).also { it.click() }
+ }
+
+ private fun startScreenSharing() {
+ findObject(By.res(ACCEPT_RESOURCE_ID)).also { it.click() }
+ }
+
+ private fun findObject(selector: BySelector): UiObject2 =
+ uiDevice.wait(Until.findObject(selector), TIMEOUT) ?: error("Can't find object $selector")
+
+ private fun getSysUiResourceString(resName: String): String =
+ with(packageManager.getResourcesForApplication(SYSTEMUI_PACKAGE)) {
+ getString(getIdentifier(resName, "string", SYSTEMUI_PACKAGE))
+ }
+
+ companion object {
+ const val TAG: String = "StartMediaProjectionAppHelper"
+ const val TIMEOUT: Long = 5000L
+ const val ACCEPT_RESOURCE_ID: String = "android:id/button1"
+ const val START_MEDIA_PROJECTION_BUTTON_ID: String = "button_start_mp"
+ val SCREEN_SHARE_OPTIONS_PATTERN: Pattern =
+ Pattern.compile("$SYSTEMUI_PACKAGE:id/screen_share_mode_(options|spinner)")
+ const val ENTIRE_SCREEN_STRING_RES_NAME: String =
+ "screen_share_permission_dialog_option_entire_screen"
+ const val SINGLE_APP_STRING_RES_NAME: String =
+ "screen_share_permission_dialog_option_single_app"
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp
index a186679790b6..c55df8604362 100644
--- a/tests/FlickerTests/test-apps/flickerapp/Android.bp
+++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp
@@ -47,6 +47,7 @@ android_test {
"wm-flicker-common-app-helpers",
"wm-flicker-common-assertions",
"wm-flicker-window-extensions",
+ "wm-shell-flicker-utils",
],
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 45260bddd355..f891606f0066 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -21,6 +21,15 @@
android:targetSdkVersion="35"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+ <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"/>
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<application android:allowBackup="false"
android:supportsRtl="true">
@@ -106,6 +115,17 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".StartMediaProjectionActivity"
+ android:theme="@style/CutoutNever"
+ android:resizeableActivity="false"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.StartMediaProjectionActivity"
+ android:label="StartMediaProjectionActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
<activity android:name=".PortraitImmersiveActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitImmersiveActivity"
android:immersive="true"
@@ -404,6 +424,11 @@
android:name="android.voice_interaction"
android:resource="@xml/interaction_service"/>
</service>
+ <service android:name="com.android.wm.shell.flicker.utils.MediaProjectionService"
+ android:foregroundServiceType="mediaProjection"
+ android:label="WMShellTestsMediaProjectionService"
+ android:enabled="true">
+ </service>
</application>
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
</manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
new file mode 100644
index 000000000000..46f01e6c9752
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:background="@android:color/holo_orange_light">
+
+ <Button
+ android:id="@+id/button_start_mp"
+ android:layout_width="500dp"
+ android:layout_height="500dp"
+ android:gravity="center_vertical|center_horizontal"
+ android:text="Start Media Projection"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 80c1dd072df7..e4de2c574553 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -85,6 +85,12 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
}
+ public static class StartMediaProjectionActivity {
+ public static final String LABEL = "StartMediaProjectionActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".StartMediaProjectionActivity");
+ }
+
public static class PortraitImmersiveActivity {
public static final String LABEL = "PortraitImmersiveActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java
new file mode 100644
index 000000000000..a24a48269d7c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.EXTRA_MESSENGER;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_SERVICE_DESTROYED;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_START_FOREGROUND_DONE;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Messenger;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.widget.Button;
+
+import com.android.wm.shell.flicker.utils.MediaProjectionService;
+
+public class StartMediaProjectionActivity extends Activity {
+
+ private static final String TAG = "StartMediaProjectionActivity";
+ private MediaProjectionManager mService;
+ private ImageReader mImageReader;
+ private VirtualDisplay mVirtualDisplay;
+ private MediaProjection mMediaProjection;
+ private MediaProjection.Callback mMediaProjectionCallback = new MediaProjection.Callback() {
+ @Override
+ public void onStop() {
+ super.onStop();
+ }
+
+ @Override
+ public void onCapturedContentResize(int width, int height) {
+ super.onCapturedContentResize(width, height);
+ }
+
+ @Override
+ public void onCapturedContentVisibilityChanged(boolean isVisible) {
+ super.onCapturedContentVisibilityChanged(isVisible);
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mService = getSystemService(MediaProjectionManager.class);
+ setContentView(R.layout.activity_start_media_projection);
+
+ Button startMediaProjectionButton = findViewById(R.id.button_start_mp);
+ startMediaProjectionButton.setOnClickListener(v ->
+ startActivityForResult(mService.createScreenCaptureIntent(), REQUEST_CODE));
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode != REQUEST_CODE) {
+ throw new IllegalStateException("Unknown request code: " + requestCode);
+ }
+ if (resultCode != RESULT_OK) {
+ throw new IllegalStateException("User denied screen sharing permission");
+ }
+ Log.d(TAG, "onActivityResult");
+ startMediaProjectionService(resultCode, data);
+ }
+
+ private void startMediaProjectionService(int resultCode, Intent resultData) {
+ final Messenger messenger = new Messenger(new Handler(Looper.getMainLooper(),
+ msg -> {
+ switch (msg.what) {
+ case MSG_START_FOREGROUND_DONE:
+ setupMediaProjection(resultCode, resultData);
+ return true;
+ case MSG_SERVICE_DESTROYED:
+ return true;
+ }
+ Log.e(TAG, "Unknown message from the FlickerMPService: " + msg.what);
+ return false;
+ }
+ ));
+
+ final Intent intent = new Intent()
+ .setComponent(new ComponentName(this, MediaProjectionService.class))
+ .putExtra(EXTRA_MESSENGER, messenger);
+ startForegroundService(intent);
+ }
+
+ private void setupMediaProjection(int resultCode, Intent resultData) {
+ mMediaProjection = mService.getMediaProjection(resultCode, resultData);
+ if (mMediaProjection == null) {
+ throw new IllegalStateException("cannot create new MediaProjection");
+ }
+
+ mMediaProjection.registerCallback(
+ mMediaProjectionCallback, new Handler(Looper.getMainLooper()));
+
+ Rect displayBounds = getWindowManager().getMaximumWindowMetrics().getBounds();
+ mImageReader = ImageReader.newInstance(
+ displayBounds.width(), displayBounds.height(), PixelFormat.RGBA_8888, 1);
+
+ mVirtualDisplay = mMediaProjection.createVirtualDisplay(
+ "DanielDisplay",
+ displayBounds.width(),
+ displayBounds.height(),
+ DisplayMetrics.DENSITY_HIGH,
+ /* flags= */ 0,
+ mImageReader.getSurface(),
+ new VirtualDisplay.Callback() {
+ @Override
+ public void onStopped() {
+ if (mMediaProjection != null) {
+ if (mMediaProjectionCallback != null) {
+ mMediaProjection.unregisterCallback(mMediaProjectionCallback);
+ mMediaProjectionCallback = null;
+ }
+ mMediaProjection.stop();
+ mMediaProjection = null;
+ }
+ if (mImageReader != null) {
+ mImageReader = null;
+ }
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.getSurface().release();
+ mVirtualDisplay.release();
+ mVirtualDisplay = null;
+ }
+ }
+ },
+ new Handler(Looper.getMainLooper())
+ );
+ }
+
+}
diff --git a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
index aa73c397a663..c61a25021949 100644
--- a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
+++ b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
@@ -36,7 +36,6 @@ import com.android.cts.input.inputeventmatchers.withMotionAction
import com.android.cts.input.inputeventmatchers.withPressure
import com.android.cts.input.inputeventmatchers.withRawCoords
import com.android.cts.input.inputeventmatchers.withSource
-import java.io.InputStream
import junit.framework.Assert.fail
import org.hamcrest.Matchers.allOf
import org.junit.Before
@@ -130,17 +129,18 @@ class UinputRecordingIntegrationTests {
scenario.virtualDisplay.display.uniqueId!!,
)
- injectUinputEvents()
-
- if (DEBUG_RECEIVED_EVENTS) {
- printReceivedEventsToLogcat(scenario.activity)
- fail("Test cannot pass in debug mode!")
+ injectUinputEvents().use {
+ if (DEBUG_RECEIVED_EVENTS) {
+ printReceivedEventsToLogcat(scenario.activity)
+ fail("Test cannot pass in debug mode!")
+ }
+
+ val verifier = EventVerifier(
+ BatchedEventSplitter { scenario.activity.getInputEvent() }
+ )
+ verifyEvents(verifier)
+ scenario.activity.assertNoEvents()
}
-
- val verifier =
- EventVerifier(BatchedEventSplitter { scenario.activity.getInputEvent() })
- verifyEvents(verifier)
- scenario.activity.assertNoEvents()
} finally {
inputManager.removeUniqueIdAssociationByPort(inputPort)
}
@@ -162,14 +162,32 @@ class UinputRecordingIntegrationTests {
}
}
- private fun injectUinputEvents() {
+ /**
+ * Plays back the evemu recording associated with the current test case by injecting it via
+ * the `uinput` shell command in interactive mode. The recording playback will begin
+ * immediately, and the shell command (and the associated input device) will remain alive
+ * until the returned [AutoCloseable] is closed.
+ */
+ private fun injectUinputEvents(): AutoCloseable {
val fds = instrumentation.uiAutomation!!.executeShellCommandRw("uinput -")
+ // We do not need to use stdout in this test.
+ fds[0].close()
- ParcelFileDescriptor.AutoCloseOutputStream(fds[1]).use { stdIn ->
- val inputStream: InputStream = instrumentation.context.resources.openRawResource(
+ return ParcelFileDescriptor.AutoCloseOutputStream(fds[1]).also { stdin ->
+ instrumentation.context.resources.openRawResource(
testData.uinputRecordingResource,
- )
- stdIn.write(inputStream.readBytes())
+ ).use { inputStream ->
+ stdin.write(inputStream.readBytes())
+
+ // TODO(b/367419268): Remove extra event injection when uinput parsing is fixed.
+ // Inject an extra sync event with an arbitrarily large timestamp, because the
+ // uinput command will not process the last event until either the next event is
+ // parsed, or fd is closed. Injecting this sync allows us complete injection of
+ // the evemu recording and extend the lifetime of the input device by keeping this
+ // fd open.
+ stdin.write("\nE: 9999.99 0 0 0\n".toByteArray())
+ stdin.flush()
+ }
}
}
diff --git a/tests/Tracing/Android.bp b/tests/Tracing/Android.bp
new file mode 100644
index 000000000000..5a7f12f56655
--- /dev/null
+++ b/tests/Tracing/Android.bp
@@ -0,0 +1,33 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_team: "trendy_team_windowing_tools",
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "TracingTests",
+ proto: {
+ type: "nano",
+ },
+ // Include some source files directly to be able to access package members
+ srcs: ["src/**/*.java"],
+ libs: ["android.test.runner"],
+ static_libs: [
+ "junit",
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ "truth",
+ "platform-test-annotations",
+ "flickerlib-parsers",
+ "perfetto_trace_java_protos",
+ "flickerlib-trace_processor_shell",
+ ],
+ java_resource_dirs: ["res"],
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/tests/Tracing/AndroidManifest.xml b/tests/Tracing/AndroidManifest.xml
new file mode 100644
index 000000000000..7254f81307ad
--- /dev/null
+++ b/tests/Tracing/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tracing.tests">
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.BIND_WALLPAPER"/>
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <application
+ android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.tracing.tests"
+ android:label="Tracing Tests"/>
+</manifest>
diff --git a/tests/Tracing/AndroidTest.xml b/tests/Tracing/AndroidTest.xml
new file mode 100644
index 000000000000..9a404203ee18
--- /dev/null
+++ b/tests/Tracing/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<configuration description="Runs tests for tracing classes/utilities.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="TracingTests.apk" />
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="TracingTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.tracing.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.tracing.tests/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration> \ No newline at end of file
diff --git a/tests/Internal/src/com/android/internal/protolog/OWNERS b/tests/Tracing/OWNERS
index 18cf2be9f7df..4a5033800b8e 100644
--- a/tests/Internal/src/com/android/internal/protolog/OWNERS
+++ b/tests/Tracing/OWNERS
@@ -1,3 +1,3 @@
-# ProtoLog owners
+# Tracing owners
# Bug component: 1157642
include platform/development:/tools/winscope/OWNERS
diff --git a/tests/Tracing/TEST_MAPPING b/tests/Tracing/TEST_MAPPING
new file mode 100644
index 000000000000..7f58fceee24d
--- /dev/null
+++ b/tests/Tracing/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "TracingTests"
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/Tracing/res/xml/network_security_config.xml b/tests/Tracing/res/xml/network_security_config.xml
new file mode 100644
index 000000000000..fdf1dbbe7672
--- /dev/null
+++ b/tests/Tracing/res/xml/network_security_config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index 9657225588b7..8913e8c1996e 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -180,7 +180,6 @@ public class LegacyProtoLogImplTest {
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
LogLevel.INFO), eq("test 5"));
- verify(mReader, never()).getViewerString(anyLong());
}
@Test
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
index 253965337824..253965337824 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index e841d9ea0880..e841d9ea0880 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
index aba6722c0813..be0c7daebb57 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.endsWith;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.times;
+import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import org.junit.Test;
@@ -44,6 +45,8 @@ public class ProtoLogCommandHandlerTest {
ProtoLogConfigurationService mProtoLogConfigurationService;
@Mock
PrintWriter mPrintWriter;
+ @Mock
+ Binder mMockBinder;
@Test
public void printsHelpForAllAvailableCommands() {
@@ -70,7 +73,7 @@ public class ProtoLogCommandHandlerTest {
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "groups", "list" });
Mockito.verify(mPrintWriter, times(1))
@@ -84,7 +87,7 @@ public class ProtoLogCommandHandlerTest {
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "groups" });
Mockito.verify(mPrintWriter, times(1))
@@ -99,7 +102,7 @@ public class ProtoLogCommandHandlerTest {
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
Mockito.verify(mPrintWriter, times(1))
@@ -114,7 +117,7 @@ public class ProtoLogCommandHandlerTest {
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
Mockito.verify(mPrintWriter, times(1))
@@ -128,7 +131,7 @@ public class ProtoLogCommandHandlerTest {
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "groups", "status" });
Mockito.verify(mPrintWriter, times(1))
@@ -140,7 +143,7 @@ public class ProtoLogCommandHandlerTest {
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat" });
Mockito.verify(mPrintWriter, times(1))
@@ -152,11 +155,11 @@ public class ProtoLogCommandHandlerTest {
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP" });
Mockito.verify(mProtoLogConfigurationService).enableProtoLogToLogcat("MY_GROUP");
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err,
new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" });
Mockito.verify(mProtoLogConfigurationService)
@@ -168,11 +171,11 @@ public class ProtoLogCommandHandlerTest {
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP" });
Mockito.verify(mProtoLogConfigurationService).disableProtoLogToLogcat("MY_GROUP");
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err,
new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" });
Mockito.verify(mProtoLogConfigurationService)
@@ -184,7 +187,7 @@ public class ProtoLogCommandHandlerTest {
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat", "enable" });
Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
}
@@ -194,7 +197,7 @@ public class ProtoLogCommandHandlerTest {
final ProtoLogCommandHandler cmdHandler =
new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
- cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
FileDescriptor.err, new String[] { "logcat", "disable" });
Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
index e1bdd777dc5f..e1bdd777dc5f 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java
index 0496240f01e4..0496240f01e4 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
index 9d56a92fad52..9d56a92fad52 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index be0e8bc0fc07..28d7b42764c4 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -27,7 +27,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
@Presubmit
@@ -48,7 +47,7 @@ public class ProtoLogViewerConfigReaderTest {
.setTag(TEST_GROUP_TAG)
).addGroups(
perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
- .setId(1)
+ .setId(2)
.setName(OTHER_TEST_GROUP_NAME)
.setTag(OTHER_TEST_GROUP_TAG)
).addMessages(
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
index 9a062e3b2f80..ce519b7a1576 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -67,7 +67,8 @@ public class ProtologDataSourceTest {
@Test
public void allEnabledTraceMode() {
- final ProtoLogDataSource ds = new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {});
+ final ProtoLogDataSource ds =
+ new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {});
final ProtoLogDataSource.TlsState tlsState = createTlsState(
DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig(
diff --git a/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java b/tests/Tracing/src/com/android/internal/protolog/common/LogDataTypeTest.java
index 9c2f74eabe02..9c2f74eabe02 100644
--- a/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/common/LogDataTypeTest.java