summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PREUPLOAD.cfg3
-rw-r--r--core/api/current.txt5
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/api/test-current.txt9
-rw-r--r--core/java/android/app/appfunctions/AppFunctionManager.java36
-rw-r--r--core/java/android/app/appfunctions/AppFunctionManagerHelper.java5
-rw-r--r--core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java8
-rw-r--r--core/java/android/app/appfunctions/IAppFunctionManager.aidl2
-rw-r--r--core/java/android/companion/AssociationRequest.java54
-rw-r--r--core/java/android/content/Intent.java2
-rw-r--r--core/java/android/hardware/display/DisplayManager.java54
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java18
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig10
-rw-r--r--core/java/android/os/BaseBundle.java17
-rw-r--r--core/java/android/os/Binder.java16
-rw-r--r--core/java/android/os/Bundle.java6
-rw-r--r--core/java/android/os/PowerManager.java8
-rw-r--r--core/java/android/view/MotionEvent.java38
-rw-r--r--core/java/android/view/NotificationHeaderView.java19
-rw-r--r--core/java/android/view/View.java10
-rw-r--r--core/java/android/view/WindowManagerPolicyConstants.java2
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig154
-rw-r--r--core/java/android/window/DesktopModeFlags.java40
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig10
-rw-r--r--core/java/com/android/internal/os/BaseCommand.java10
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl4
-rw-r--r--core/jni/Android.bp1
-rw-r--r--core/jni/AndroidRuntime.cpp11
-rw-r--r--core/jni/android_util_Binder.cpp34
-rw-r--r--core/res/AndroidManifest.xml17
-rw-r--r--core/res/res/layout/notification_2025_conversation_header.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_base.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_media.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml1
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/dimens.xml3
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java6
-rw-r--r--core/tests/coretests/src/android/os/BundleTest.java46
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt18
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java69
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt52
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt167
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt23
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt28
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt124
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt235
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java78
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt1
-rw-r--r--libs/appfunctions/api/current.txt4
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java14
-rw-r--r--media/java/android/media/MediaCodec.java15
-rw-r--r--media/java/android/media/OWNERS3
-rw-r--r--media/java/android/media/RoutingSessionInfo.java57
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig7
-rw-r--r--media/java/android/media/quality/MediaQualityContract.java28
-rw-r--r--media/jni/android_media_MediaCodec.cpp10
-rw-r--r--packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java21
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java23
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java55
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java82
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java1
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig33
-rw-r--r--packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalLockSection.kt150
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt20
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt3
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt3
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt3
-rwxr-xr-xpackages/SystemUI/flag_check.py138
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt222
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt500
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt40
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalLockIconViewModelTest.kt138
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java120
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt202
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt76
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java112
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt59
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt139
-rw-r--r--packages/SystemUI/res/drawable/media_output_item_expand_group.xml26
-rw-r--r--packages/SystemUI/res/layout/notification_2025_hybrid.xml1
-rw-r--r--packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml6
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalLockIconViewBinder.kt162
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt148
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt1102
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java202
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt110
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt120
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/ToEditMode.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt130
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java54
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt2
-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/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java36
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java51
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java29
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java4
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java28
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java3
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java20
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java29
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java5
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java20
-rw-r--r--services/core/java/com/android/server/DockObserver.java68
-rw-r--r--services/core/java/com/android/server/TradeInModeService.java17
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java4
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java22
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java29
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/input/KeyboardLayoutManager.java17
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java699
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityUtils.java1543
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java23
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java4
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java92
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java2
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java65
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeHelper.java26
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java6
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java8
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java2
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java26
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java66
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java219
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java50
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java63
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java237
-rw-r--r--tests/CompanionDeviceMultiDeviceTests/host/Android.bp9
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt4
-rw-r--r--tests/utils/testutils/java/android/os/test/TestLooper.java170
305 files changed, 9173 insertions, 2857 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 5e0428bab467..05d6e886b01a 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -29,8 +29,5 @@ hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclu
ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
-# This flag check hook runs only for "packages/SystemUI" subdirectory. If you want to include this check for other subdirectories, please modify flag_check.py.
-flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PROJECT}
-
[Tool Paths]
ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh
diff --git a/core/api/current.txt b/core/api/current.txt
index 17e7d7a258d8..21929658cbb9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8893,8 +8893,8 @@ package android.app.appfunctions {
}
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
- method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
- method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+ method @RequiresPermission(value=android.Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
+ method @RequiresPermission(value=android.Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
@@ -20785,6 +20785,7 @@ package android.hardware.display {
method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
method @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public void registerDisplayListener(@NonNull java.util.concurrent.Executor, long, @NonNull android.hardware.display.DisplayManager.DisplayListener);
method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
+ field @FlaggedApi("com.android.server.display.feature.flags.display_category_built_in") public static final String DISPLAY_CATEGORY_BUILT_IN_DISPLAYS = "android.hardware.display.category.BUILT_IN_DISPLAYS";
field public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_TYPE_DISPLAY_ADDED = 1L; // 0x1L
field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_TYPE_DISPLAY_CHANGED = 4L; // 0x4L
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 41f286245d8d..ab824119d643 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -150,7 +150,6 @@ package android {
field @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission") public static final String EMBED_ANY_APP_IN_UNTRUSTED_MODE = "android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE";
field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
- field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXECUTE_APP_FUNCTIONS_TRUSTED = "android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED";
field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7c1c86823110..0b0738ee14dc 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -909,6 +909,14 @@ package android.companion {
method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
}
+ public final class AssociationRequest implements android.os.Parcelable {
+ method public boolean isSkipRoleGrant();
+ }
+
+ public static final class AssociationRequest.Builder {
+ method @NonNull @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) public android.companion.AssociationRequest.Builder setSkipRoleGrant(boolean);
+ }
+
public final class CompanionDeviceManager {
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void enableSecureTransport(boolean);
}
@@ -1727,6 +1735,7 @@ package android.hardware.display {
method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]);
method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
+ field public static final String DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED = "android.hardware.display.category.ALL_INCLUDING_DISABLED";
field public static final String DISPLAY_CATEGORY_REAR = "android.hardware.display.category.REAR";
field public static final String HDR_OUTPUT_CONTROL_FLAG = "enable_hdr_output_control";
field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 6fd8db995368..0a3891fe47a1 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -72,10 +72,10 @@ import java.util.concurrent.Executor;
* <p>To execute an app function, the caller app can retrieve the {@code functionIdentifier} from
* the {@code AppFunctionStaticMetadata} document and use it to build an {@link
* ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request to execute
- * the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} or {@code
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app functions from other
- * apps. An app can always execute its own app functions and doesn't need these permissions.
- * AppFunction SDK provides a convenient way to achieve this and is the preferred method.
+ * the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} permission to
+ * execute app functions from other apps. An app can always execute its own app functions and
+ * doesn't need these permissions. AppFunction SDK provides a convenient way to achieve this and
+ * is the preferred method.
*
* <h3>Example</h3>
*
@@ -141,32 +141,24 @@ public final class AppFunctionManager {
* Executes the app function.
*
* <p>Note: Applications can execute functions they define. To execute functions defined in
- * another component, apps would need to have {@code
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
- * android.permission.EXECUTE_APP_FUNCTIONS}.
+ * another component, apps would need to have the permission
+ * {@code android.permission.EXECUTE_APP_FUNCTIONS}.
*
* @param request the request to execute the app function
* @param executor the executor to run the callback
* @param cancellationSignal the cancellation signal to cancel the execution.
* @param callback the callback to receive the function execution result or error.
* <p>If the calling app does not own the app function or does not have {@code
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
* android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
* AppFunctionException.ERROR_DENIED}.
- * <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS} but the
- * function requires {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}, the execution
+ * <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS}, the execution
* result will contain {@code AppFunctionException.ERROR_DENIED}
* <p>If the function requested for execution is disabled, then the execution result will
* contain {@code AppFunctionException.ERROR_DISABLED}
* <p>If the cancellation signal is issued, the operation is cancelled and no response is
* returned to the caller.
*/
- @RequiresPermission(
- anyOf = {
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- Manifest.permission.EXECUTE_APP_FUNCTIONS
- },
- conditional = true)
+ @RequiresPermission(value = Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)
@UserHandleAware
public void executeAppFunction(
@NonNull ExecuteAppFunctionRequest request,
@@ -222,9 +214,8 @@ public final class AppFunctionManager {
* Returns a boolean through a callback, indicating whether the app function is enabled.
*
* <p>This method can only check app functions owned by the caller, or those where the caller
- * has visibility to the owner package and holds either the {@link
- * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
- * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
+ * has visibility to the owner package and holds the
+ * {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} permission.
*
* <p>If the operation fails, the callback's {@link OutcomeReceiver#onError} is called with
* errors:
@@ -241,12 +232,7 @@ public final class AppFunctionManager {
* @param executor the executor to run the request
* @param callback the callback to receive the function enabled check result
*/
- @RequiresPermission(
- anyOf = {
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- Manifest.permission.EXECUTE_APP_FUNCTIONS
- },
- conditional = true)
+ @RequiresPermission(value = Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)
public void isAppFunctionEnabled(
@NonNull String functionIdentifier,
@NonNull String targetPackage,
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
index 64dece99c5d1..cc3ca03f423d 100644
--- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -54,9 +54,8 @@ public class AppFunctionManagerHelper {
* Returns (through a callback) a boolean indicating whether the app function is enabled.
*
* This method can only check app functions owned by the caller, or those where the caller
- * has visibility to the owner package and holds either the {@link
- * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
- * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
+ * has visibility to the owner package and holds the {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS} permission.
*
* <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
*
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 3ddda228d145..7743d4862b51 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -90,8 +90,7 @@ public class AppFunctionRuntimeMetadata extends GenericDocument {
* we need to have per-package app function schemas.
*
* <p>This schema should be set visible to callers from the package owner itself and for callers
- * with {@link android.Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
- * android.Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permissions.
+ * with the permission {@link android.Manifest.permission#EXECUTE_APP_FUNCTIONS}.
*
* @param packageName The package name to create a schema for.
*/
@@ -105,9 +104,8 @@ public class AppFunctionRuntimeMetadata extends GenericDocument {
/**
* Creates a parent schema for all app function runtime schemas.
*
- * <p>This schema should be set visible to the owner itself and for callers with {@link
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@link
- * android.permission.EXECUTE_APP_FUNCTIONS} permissions.
+ * <p>This schema should be set visible to the owner itself and for callers with
+ * the permission {@link android.permission.EXECUTE_APP_FUNCTIONS}.
*/
public static AppSearchSchema createParentAppFunctionRuntimeSchema() {
return getAppFunctionRuntimeSchemaBuilder(RUNTIME_SCHEMA_TYPE).build();
diff --git a/core/java/android/app/appfunctions/IAppFunctionManager.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
index 72335e40c207..098e1fe8b516 100644
--- a/core/java/android/app/appfunctions/IAppFunctionManager.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
@@ -34,7 +34,7 @@ interface IAppFunctionManager {
* @param request the request to execute an app function.
* @param callback the callback to report the result.
*/
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional = true)")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = android.Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)")
ICancellationSignal executeAppFunction(
in ExecuteAppFunctionAidlRequest request,
in IExecuteAppFunctionCallback callback
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index a098a6067491..67dea321a446 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -16,6 +16,7 @@
package android.companion;
+import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
import static com.android.internal.util.CollectionUtils.emptyIfNull;
@@ -28,7 +29,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
+import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Icon;
import android.os.Build;
@@ -214,6 +218,11 @@ public final class AssociationRequest implements Parcelable {
private final boolean mForceConfirmation;
/**
+ * Whether to skip the role grant, permission checks and consent dialog.
+ */
+ private final boolean mSkipRoleGrant;
+
+ /**
* The app package name of the application the association will belong to.
* Populated by the system.
* @hide
@@ -283,6 +292,7 @@ public final class AssociationRequest implements Parcelable {
@Nullable CharSequence displayName,
boolean selfManaged,
boolean forceConfirmation,
+ boolean skipRoleGrant,
@Nullable Icon deviceIcon) {
mSingleDevice = singleDevice;
mDeviceFilters = requireNonNull(deviceFilters);
@@ -290,6 +300,7 @@ public final class AssociationRequest implements Parcelable {
mDisplayName = displayName;
mSelfManaged = selfManaged;
mForceConfirmation = forceConfirmation;
+ mSkipRoleGrant = skipRoleGrant;
mCreationTime = System.currentTimeMillis();
mDeviceIcon = deviceIcon;
}
@@ -333,6 +344,18 @@ public final class AssociationRequest implements Parcelable {
}
/**
+ * Whether to skip the role grant, permission checks and consent dialog.
+ *
+ * @see Builder#setSkipRoleGrant(boolean)
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ public boolean isSkipRoleGrant() {
+ return mSkipRoleGrant;
+ }
+
+ /**
* Whether only a single device should match the provided filter.
*
* When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
@@ -407,6 +430,7 @@ public final class AssociationRequest implements Parcelable {
private CharSequence mDisplayName;
private boolean mSelfManaged = false;
private boolean mForceConfirmation = false;
+ private boolean mSkipRoleGrant = false;
private Icon mDeviceIcon = null;
public Builder() {}
@@ -494,6 +518,27 @@ public final class AssociationRequest implements Parcelable {
}
/**
+ * Do not attempt to grant the role corresponding to the device profile.
+ *
+ * <p>This will skip the permission checks and consent dialog but will not fail if the
+ * role cannot be granted.</p>
+ *
+ * <p>Requires that the device not to have secure lock screen and that there no locked SIM
+ * card. See {@link KeyguardManager#isKeyguardSecure()}</p>
+ *
+ * @hide
+ */
+ @RequiresPermission(ASSOCIATE_COMPANION_DEVICES)
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ @NonNull
+ public Builder setSkipRoleGrant(boolean skipRoleGrant) {
+ checkNotUsed();
+ mSkipRoleGrant = skipRoleGrant;
+ return this;
+ }
+
+ /**
* Set the device icon for the self-managed device and to display the icon in the
* self-managed association dialog.
* <p>The given device icon will be resized to 24dp x 24dp.
@@ -521,7 +566,8 @@ public final class AssociationRequest implements Parcelable {
+ "provide the display name of the device");
}
return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters),
- mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mDeviceIcon);
+ mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mSkipRoleGrant,
+ mDeviceIcon);
}
}
@@ -597,6 +643,7 @@ public final class AssociationRequest implements Parcelable {
+ ", associatedDevice = " + mAssociatedDevice
+ ", selfManaged = " + mSelfManaged
+ ", forceConfirmation = " + mForceConfirmation
+ + ", skipRoleGrant = " + mSkipRoleGrant
+ ", packageName = " + mPackageName
+ ", userId = " + mUserId
+ ", deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription
@@ -617,6 +664,7 @@ public final class AssociationRequest implements Parcelable {
&& Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
&& mSelfManaged == that.mSelfManaged
&& mForceConfirmation == that.mForceConfirmation
+ && mSkipRoleGrant == that.mSkipRoleGrant
&& Objects.equals(mPackageName, that.mPackageName)
&& mUserId == that.mUserId
&& Objects.equals(mDeviceProfilePrivilegesDescription,
@@ -637,6 +685,7 @@ public final class AssociationRequest implements Parcelable {
_hash = 31 * _hash + Objects.hashCode(mAssociatedDevice);
_hash = 31 * _hash + Boolean.hashCode(mSelfManaged);
_hash = 31 * _hash + Boolean.hashCode(mForceConfirmation);
+ _hash = 31 * _hash + Boolean.hashCode(mSkipRoleGrant);
_hash = 31 * _hash + Objects.hashCode(mPackageName);
_hash = 31 * _hash + mUserId;
_hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription);
@@ -659,6 +708,7 @@ public final class AssociationRequest implements Parcelable {
if (mAssociatedDevice != null) flg |= 0x40;
if (mPackageName != null) flg |= 0x80;
if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100;
+ if (mSkipRoleGrant) flg |= 0x200;
dest.writeInt(flg);
dest.writeParcelableList(mDeviceFilters, flags);
@@ -692,6 +742,7 @@ public final class AssociationRequest implements Parcelable {
boolean selfManaged = (flg & 0x2) != 0;
boolean forceConfirmation = (flg & 0x4) != 0;
boolean skipPrompt = (flg & 0x8) != 0;
+ boolean skipRoleGrant = (flg & 0x200) != 0;
List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(),
(Class<android.companion.DeviceFilter<?>>) (Class<?>)
@@ -714,6 +765,7 @@ public final class AssociationRequest implements Parcelable {
this.mAssociatedDevice = associatedDevice;
this.mSelfManaged = selfManaged;
this.mForceConfirmation = forceConfirmation;
+ this.mSkipRoleGrant = skipRoleGrant;
this.mPackageName = packageName;
this.mUserId = userId;
com.android.internal.util.AnnotationValidations.validate(
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 469688265ae8..0312ad7a739a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12426,8 +12426,8 @@ public class Intent implements Parcelable, Cloneable {
}
private void collectNestedIntentKeysRecur(Set<Intent> visited, boolean forceUnparcel) {
- addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
if (mExtras != null && (forceUnparcel || !mExtras.isParcelled()) && !mExtras.isEmpty()) {
+ addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
for (String key : mExtras.keySet()) {
Object value;
try {
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 0590a06f3f82..b7460e976313 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -146,6 +146,22 @@ public final class DisplayManager {
"android.hardware.display.category.PRESENTATION";
/**
+ * Display category: Built in displays.
+ *
+ * <p>
+ * This category can be used to identify displays that are built into the device. The
+ * displays that are returned may be inactive or disabled at the current moment. The
+ * returned displays are useful in identifying the various sizes of built-in displays. The
+ * id from {@link Display#getDisplayId()} is not guaranteed to be stable and may change
+ * when the display becomes active.
+ * </p>
+ * @see #getDisplays(String)
+ */
+ @FlaggedApi(com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_CATEGORY_BUILT_IN)
+ public static final String DISPLAY_CATEGORY_BUILT_IN_DISPLAYS =
+ "android.hardware.display.category.BUILT_IN_DISPLAYS";
+
+ /**
* Display category: Rear displays.
* <p>
* This category can be used to identify complementary internal displays that are facing away
@@ -171,6 +187,8 @@ public final class DisplayManager {
* @see #getDisplays(String)
* @hide
*/
+ @TestApi
+ @SuppressLint("UnflaggedApi")
public static final String DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED =
"android.hardware.display.category.ALL_INCLUDING_DISABLED";
@@ -623,6 +641,9 @@ public final class DisplayManager {
* is triggered whenever the properties of a {@link android.view.Display}, such as size,
* state, density are modified.
*
+ * This event is not triggered for refresh rate changes as they can change very often.
+ * To monitor refresh rate changes, subscribe to {@link EVENT_TYPE_DISPLAY_REFRESH_RATE}.
+ *
* @see #registerDisplayListener(DisplayListener, Handler, long)
*
*/
@@ -729,10 +750,13 @@ public final class DisplayManager {
* @see #DISPLAY_CATEGORY_PRESENTATION
*/
public Display[] getDisplays(String category) {
- boolean includeDisabled = (category != null
- && category.equals(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED));
+ boolean includeDisabled = shouldIncludeDisabledDisplays(category);
final int[] displayIds = mGlobal.getDisplayIds(includeDisabled);
- if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
+ if (Flags.displayCategoryBuiltIn()
+ && DISPLAY_CATEGORY_BUILT_IN_DISPLAYS.equals(category)) {
+ Display[] value = getDisplays(displayIds, DisplayManager::isBuiltInDisplay);
+ return value;
+ } else if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
return getDisplays(displayIds, DisplayManager::isPresentationDisplay);
} else if (DISPLAY_CATEGORY_REAR.equals(category)) {
return getDisplays(displayIds, DisplayManager::isRearDisplay);
@@ -742,6 +766,16 @@ public final class DisplayManager {
return new Display[0];
}
+ private boolean shouldIncludeDisabledDisplays(@Nullable String category) {
+ if (DISPLAY_CATEGORY_BUILT_IN_DISPLAYS.equals(category)) {
+ return true;
+ }
+ if (DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
+ return true;
+ }
+ return false;
+ }
+
private Display[] getDisplays(int[] displayIds, Predicate<Display> predicate) {
ArrayList<Display> tmpDisplays = new ArrayList<>();
for (int displayId : displayIds) {
@@ -753,6 +787,13 @@ public final class DisplayManager {
return tmpDisplays.toArray(new Display[tmpDisplays.size()]);
}
+ private static boolean isBuiltInDisplay(@Nullable Display display) {
+ if (display == null) {
+ return false;
+ }
+ return display.getType() == Display.TYPE_INTERNAL;
+ }
+
private static boolean isPresentationDisplay(@Nullable Display display) {
if (display == null || (display.getDisplayId() == DEFAULT_DISPLAY)
|| (display.getFlags() & Display.FLAG_PRESENTATION) == 0) {
@@ -801,6 +842,9 @@ public final class DisplayManager {
* Registers a display listener to receive notifications about when
* displays are added, removed or changed.
*
+ * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
+ * instead to subscribe for explicit events of interest
+ *
* @param listener The listener to register.
* @param handler The handler on which the listener should be invoked, or null
* if the listener should be invoked on the calling thread's looper.
@@ -809,7 +853,9 @@ public final class DisplayManager {
*/
public void registerDisplayListener(DisplayListener listener, Handler handler) {
registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
- | EVENT_TYPE_DISPLAY_CHANGED | EVENT_TYPE_DISPLAY_REMOVED);
+ | EVENT_TYPE_DISPLAY_CHANGED
+ | EVENT_TYPE_DISPLAY_REFRESH_RATE
+ | EVENT_TYPE_DISPLAY_REMOVED);
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index b5715ed25bd9..339dbf2c2029 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1766,29 +1766,23 @@ public final class DisplayManagerGlobal {
}
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_CHANGED) != 0) {
- // For backward compatibility, a client subscribing to
- // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and
- // RR changes
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
- | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED;
}
- if ((eventFlags
- & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
+ if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
}
- if (Flags.displayListenerPerformanceImprovements()) {
- if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
- }
+ if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+ }
+ if (Flags.displayListenerPerformanceImprovements()) {
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_STATE) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE;
}
}
-
return baseEventMask;
}
}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 79323bf2f2f7..ae017e80966f 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -57,16 +57,6 @@ flag {
}
flag {
- namespace: "input_native"
- name: "keyboard_layout_manager_multi_user_ime_setup"
- description: "Update KeyboardLayoutManager to work correctly with multi-user IME setup"
- bug: "354333072"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "modifier_shortcut_dump"
namespace: "input"
description: "Dump keyboard shortcuts in dumpsys window"
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 1cf293d46350..e79b2e7becce 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -142,6 +142,7 @@ public class BaseBundle {
/** {@hide} */
@VisibleForTesting
public int mFlags;
+ private boolean mHasIntent = false;
/**
* Constructs a new, empty Bundle that uses a specific ClassLoader for
@@ -258,9 +259,20 @@ public class BaseBundle {
// Keep as last statement to ensure visibility of other fields
mParcelledData = parcelledData;
+ mHasIntent = from.mHasIntent;
}
}
+ /** @hide */
+ public boolean hasIntent() {
+ return mHasIntent;
+ }
+
+ /** @hide */
+ public void setHasIntent(boolean hasIntent) {
+ mHasIntent = hasIntent;
+ }
+
/**
* TODO: optimize this later (getting just the value part of a Bundle
* with a single pair) once Bundle.forPair() above is implemented
@@ -1837,6 +1849,7 @@ public class BaseBundle {
parcel.writeInt(length);
parcel.writeInt(mParcelledByNative ? BUNDLE_MAGIC_NATIVE : BUNDLE_MAGIC);
parcel.appendFrom(mParcelledData, 0, length);
+ parcel.writeBoolean(mHasIntent);
}
return;
}
@@ -1851,7 +1864,6 @@ public class BaseBundle {
int lengthPos = parcel.dataPosition();
parcel.writeInt(-1); // placeholder, will hold length
parcel.writeInt(BUNDLE_MAGIC);
-
int startPos = parcel.dataPosition();
parcel.writeArrayMapInternal(map);
int endPos = parcel.dataPosition();
@@ -1861,6 +1873,7 @@ public class BaseBundle {
int length = endPos - startPos;
parcel.writeInt(length);
parcel.setDataPosition(endPos);
+ parcel.writeBoolean(mHasIntent);
}
/**
@@ -1904,6 +1917,7 @@ public class BaseBundle {
mOwnsLazyValues = false;
initializeFromParcelLocked(parcel, /*ownsParcel*/ false, isNativeBundle);
}
+ mHasIntent = parcel.readBoolean();
return;
}
@@ -1922,6 +1936,7 @@ public class BaseBundle {
mOwnsLazyValues = true;
mParcelledByNative = isNativeBundle;
mParcelledData = p;
+ mHasIntent = parcel.readBoolean();
}
/** {@hide} */
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 6b1e918a3c47..ee62dea7f9e5 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -149,11 +149,6 @@ public class Binder implements IBinder {
private static volatile boolean sStackTrackingEnabled = false;
/**
- * The extension binder object
- */
- private IBinder mExtension = null;
-
- /**
* Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to
* {@link TransactionTracker}.
*
@@ -1242,9 +1237,7 @@ public class Binder implements IBinder {
/** @hide */
@Override
- public final @Nullable IBinder getExtension() {
- return mExtension;
- }
+ public final native @Nullable IBinder getExtension();
/**
* Set the binder extension.
@@ -1252,12 +1245,7 @@ public class Binder implements IBinder {
*
* @hide
*/
- public final void setExtension(@Nullable IBinder extension) {
- mExtension = extension;
- setExtensionNative(extension);
- }
-
- private final native void setExtensionNative(@Nullable IBinder extension);
+ public final native void setExtension(@Nullable IBinder extension);
/**
* Default implementation rewinds the parcels and calls onTransact. On
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 819d58d9f059..a24dc5739b7e 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -398,7 +398,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
if ((bundle.mFlags & FLAG_HAS_BINDERS_KNOWN) == 0) {
mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
}
- mFlags |= bundle.mFlags & FLAG_HAS_INTENT;
+ setHasIntent(hasIntent() || bundle.hasIntent());
}
/**
@@ -465,7 +465,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @hide
*/
public boolean hasIntent() {
- return (mFlags & FLAG_HAS_INTENT) != 0;
+ return super.hasIntent();
}
/** {@hide} */
@@ -591,7 +591,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
mFlags &= ~FLAG_HAS_FDS_KNOWN;
mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
if (intentClass != null && intentClass.isInstance(value)) {
- mFlags |= FLAG_HAS_INTENT;
+ setHasIntent(true);
}
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 2a5666cbe83c..e769abec7dd9 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -624,6 +624,7 @@ public final class PowerManager {
WAKE_REASON_TAP,
WAKE_REASON_LIFT,
WAKE_REASON_BIOMETRIC,
+ WAKE_REASON_DOCK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface WakeReason{}
@@ -765,6 +766,12 @@ public final class PowerManager {
public static final int WAKE_REASON_BIOMETRIC = 17;
/**
+ * Wake up reason code: Waking up due to a user docking the device.
+ * @hide
+ */
+ public static final int WAKE_REASON_DOCK = 18;
+
+ /**
* Convert the wake reason to a string for debugging purposes.
* @hide
*/
@@ -788,6 +795,7 @@ public final class PowerManager {
case WAKE_REASON_TAP: return "WAKE_REASON_TAP";
case WAKE_REASON_LIFT: return "WAKE_REASON_LIFT";
case WAKE_REASON_BIOMETRIC: return "WAKE_REASON_BIOMETRIC";
+ case WAKE_REASON_DOCK: return "WAKE_REASON_DOCK";
default: return Integer.toString(wakeReason);
}
}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index bd6ff4c2af02..ae0e9c623571 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -532,14 +532,30 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int FLAG_NO_FOCUS_CHANGE = MotionEventFlag.NO_FOCUS_CHANGE;
/**
- * This flag indicates that this event was modified by or generated from an accessibility
- * service. Value = 0x800
+ * This flag indicates that this event was injected from some
+ * {@link android.accessibilityservice.AccessibilityService}, which may be either an
+ * Accessibility Tool OR a service using that API for purposes other than assisting users with
+ * disabilities. Value = 0x800
+ * @see #FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL
* @hide
*/
@TestApi
public static final int FLAG_IS_ACCESSIBILITY_EVENT = MotionEventFlag.IS_ACCESSIBILITY_EVENT;
/**
+ * This flag indicates that this event was injected from an
+ * {@link android.accessibilityservice.AccessibilityService} with the
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool()} property
+ * set to true. These services (known as "Accessibility Tools") are used to assist users with
+ * disabilities, so events from these services should be able to reach all Views including
+ * Views which set {@link View#isAccessibilityDataSensitive()} to true.
+ * Value = 0x1000
+ * @hide
+ */
+ public static final int FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL =
+ MotionEventFlag.INJECTED_FROM_ACCESSIBILITY_TOOL;
+
+ /**
* Private flag that indicates when the system has detected that this motion event
* may be inconsistent with respect to the sequence of previously delivered motion events,
* such as when a pointer move event is sent but the pointer is not down.
@@ -2534,6 +2550,24 @@ public final class MotionEvent extends InputEvent implements Parcelable {
: flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS);
}
+ /**
+ * @see #FLAG_IS_ACCESSIBILITY_EVENT
+ * @hide
+ */
+ public boolean isInjectedFromAccessibilityService() {
+ final int flags = getFlags();
+ return (flags & FLAG_IS_ACCESSIBILITY_EVENT) != 0;
+ }
+
+ /**
+ * @see #FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL
+ * @hide
+ */
+ public boolean isInjectedFromAccessibilityTool() {
+ final int flags = getFlags();
+ return (flags & FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL) != 0;
+ }
+
/** @hide */
public final boolean isHoverExitPending() {
final int flags = getFlags();
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index df680c054f56..73cd5ecd39ef 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -32,6 +32,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.RemoteViews;
@@ -265,14 +266,20 @@ public class NotificationHeaderView extends RelativeLayout {
? R.style.TextAppearance_DeviceDefault_Notification_Title
: R.style.TextAppearance_DeviceDefault_Notification_Info;
// Most of the time, we're showing text in the minimized state
- View headerText = findViewById(R.id.header_text);
- if (headerText instanceof TextView) {
- ((TextView) headerText).setTextAppearance(styleResId);
+ if (findViewById(R.id.header_text) instanceof TextView headerText) {
+ headerText.setTextAppearance(styleResId);
+ if (notificationsRedesignTemplates()) {
+ // TODO: b/378660052 - When inlining the redesign flag, this should be updated
+ // directly in TextAppearance_DeviceDefault_Notification_Title so we won't need to
+ // override it here.
+ float textSize = getContext().getResources().getDimension(
+ R.dimen.notification_2025_title_text_size);
+ headerText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ }
}
// If there's no summary or text, we show the app name instead of nothing
- View appNameText = findViewById(R.id.app_name_text);
- if (appNameText instanceof TextView) {
- ((TextView) appNameText).setTextAppearance(styleResId);
+ if (findViewById(R.id.app_name_text) instanceof TextView appNameText) {
+ appNameText.setTextAppearance(styleResId);
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7206906658ff..0866e0d832b1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16654,6 +16654,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// Window is obscured, drop this touch.
return false;
}
+ if (android.view.accessibility.Flags.preventA11yNontoolFromInjectingIntoSensitiveViews()) {
+ if (event.isInjectedFromAccessibilityService()
+ // If the event came from an Accessibility Service that does *not* declare
+ // itself as AccessibilityServiceInfo#isAccessibilityTool and this View is
+ // declared sensitive then drop the event.
+ // Only Accessibility Tools are allowed to interact with sensitive Views.
+ && !event.isInjectedFromAccessibilityTool() && isAccessibilityDataSensitive()) {
+ return false;
+ }
+ }
return true;
}
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 6d2c0d0061dd..bb8958bc9c70 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -17,6 +17,7 @@
package android.view;
import static android.os.IInputConstants.POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY;
+import static android.os.IInputConstants.POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL;
import static android.os.IInputConstants.POLICY_FLAG_KEY_GESTURE_TRIGGERED;
import android.annotation.IntDef;
@@ -37,6 +38,7 @@ public interface WindowManagerPolicyConstants {
int FLAG_VIRTUAL = 0x00000002;
int FLAG_INJECTED_FROM_ACCESSIBILITY = POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY;
+ int FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL = POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL;
int FLAG_KEY_GESTURE_TRIGGERED = POLICY_FLAG_KEY_GESTURE_TRIGGERED;
int FLAG_INJECTED = 0x01000000;
int FLAG_TRUSTED = 0x02000000;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 294e5da1edd1..37f393ec6511 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -4,6 +4,14 @@ container: "system"
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
flag {
+ name: "a11y_character_in_window_api"
+ namespace: "accessibility"
+ description: "Enables new extra data key for an AccessibilityService to request character bounds in unmagnified window coordinates."
+ bug: "375429616"
+ is_exported: true
+}
+
+flag {
name: "a11y_expansion_state_api"
namespace: "accessibility"
description: "Enables new APIs for an app to convey if a node is expanded or collapsed."
@@ -42,23 +50,15 @@ flag {
}
flag {
- name: "a11y_character_in_window_api"
- namespace: "accessibility"
- description: "Enables new extra data key for an AccessibilityService to request character bounds in unmagnified window coordinates."
- bug: "375429616"
- is_exported: true
-}
-
-flag {
- namespace: "accessibility"
name: "allow_shortcut_chooser_on_lockscreen"
+ namespace: "accessibility"
description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen"
bug: "303871725"
}
flag {
- namespace: "accessibility"
name: "braille_display_hid"
+ namespace: "accessibility"
is_exported: true
description: "Enables new APIs for an AccessibilityService to communicate with a HID Braille display"
bug: "303522222"
@@ -72,47 +72,62 @@ flag {
}
flag {
- namespace: "accessibility"
name: "collection_info_item_counts"
+ namespace: "accessibility"
is_exported: true
description: "Fields for total items and the number of important for accessibility items in a collection"
bug: "302376158"
}
flag {
- namespace: "accessibility"
name: "copy_events_for_gesture_detection"
+ namespace: "accessibility"
description: "Creates copies of MotionEvents and GestureEvents in GestureMatcher"
bug: "280130713"
}
flag {
- namespace: "accessibility"
name: "deprecate_accessibility_announcement_apis"
+ namespace: "accessibility"
description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements"
bug: "376727542"
is_exported: true
}
flag {
- namespace: "accessibility"
name: "deprecate_ani_label_for_apis"
+ namespace: "accessibility"
description: "Controls the deprecation of AccessibilityNodeInfo labelFor apis"
bug: "333783827"
is_exported: true
}
flag {
+ name: "enable_system_pinch_zoom_gesture"
namespace: "accessibility"
+ description: "Feature flag for system pinch zoom gesture detector and related opt-out apis"
+ bug: "283323770"
+}
+
+flag {
+ name: "enable_type_window_control"
+ namespace: "accessibility"
+ is_exported: true
+ description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations"
+ bug: "320445550"
+}
+
+flag {
name: "flash_notification_system_api"
+ namespace: "accessibility"
is_exported: true
description: "Makes flash notification APIs as system APIs for calling from mainline module"
bug: "303131332"
}
flag {
- namespace: "accessibility"
name: "focus_rect_min_size"
+ namespace: "accessibility"
description: "Ensures the a11y focus rect is big enough to be drawn as visible"
bug: "368667566"
metadata {
@@ -121,102 +136,101 @@ flag {
}
flag {
- namespace: "accessibility"
name: "force_invert_color"
+ namespace: "accessibility"
description: "Enable force force-dark for smart inversion and dark theme everywhere"
bug: "282821643"
}
flag {
- name: "migrate_enable_shortcuts"
+ name: "global_action_media_play_pause"
namespace: "accessibility"
- description: "Refactors deprecated code to use AccessibilityManager#enableShortcutsForTargets."
- bug: "332006721"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "motion_event_observing"
+ description: "Allow AccessibilityService to perform GLOBAL_ACTION_MEDIA_PLAY_PAUSE"
+ bug: "334954140"
is_exported: true
- namespace: "accessibility"
- description: "Allows accessibility services to intercept but not consume motion events from specified sources."
- bug: "297595990"
}
flag {
- namespace: "accessibility"
name: "global_action_menu"
+ namespace: "accessibility"
description: "Allow AccessibilityService to perform GLOBAL_ACTION_MENU"
bug: "334954140"
is_exported: true
}
flag {
+ name: "granular_scrolling"
namespace: "accessibility"
- name: "global_action_media_play_pause"
- description: "Allow AccessibilityService to perform GLOBAL_ACTION_MEDIA_PLAY_PAUSE"
- bug: "334954140"
is_exported: true
+ description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen"
+ bug: "302376158"
}
flag {
+ name: "indeterminate_range_info"
namespace: "accessibility"
- name: "granular_scrolling"
+ description: "Creates a way to create an INDETERMINATE RangeInfo"
+ bug: "376108874"
is_exported: true
- description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen"
- bug: "302376158"
}
flag {
+ name: "migrate_enable_shortcuts"
namespace: "accessibility"
- name: "reduce_window_content_changed_event_throttle"
- description: "Reduces the throttle of AccessibilityEvent of TYPE_WINDOW_CONTENT_CHANGED"
- bug: "277305460"
+ description: "Refactors deprecated code to use AccessibilityManager#enableShortcutsForTargets."
+ bug: "332006721"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
+ name: "motion_event_observing"
+ is_exported: true
namespace: "accessibility"
- name: "remove_child_hover_check_for_touch_exploration"
- description: "Remove a check for a hovered child that prevents touch events from being delegated to non-direct descendants"
- bug: "304770837"
+ description: "Allows accessibility services to intercept but not consume motion events from specified sources."
+ bug: "297595990"
}
flag {
- name: "skip_accessibility_warning_dialog_for_trusted_services"
+ name: "prevent_a11y_nontool_from_injecting_into_sensitive_views"
namespace: "accessibility"
- description: "Skips showing the accessibility warning dialog for trusted services."
- bug: "303511250"
+ description: "Prevents injected gestures from A11yServices without isAccessibilityTool=true from reaching AccessibilityDataSensitive UI elements"
+ bug: "284180538"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
+ name: "prevent_leaking_viewrootimpl"
namespace: "accessibility"
- name: "enable_type_window_control"
- is_exported: true
- description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations"
- bug: "320445550"
+ description: "Clear pending messages and callbacks of the handler in AccessibilityInteractionController when the ViewRootImpl is detached from Window to prevent leaking ViewRootImpl"
+ bug: "320701910"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
+ name: "reduce_window_content_changed_event_throttle"
namespace: "accessibility"
- name: "update_always_on_a11y_service"
- description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
- bug: "298869916"
+ description: "Reduces the throttle of AccessibilityEvent of TYPE_WINDOW_CONTENT_CHANGED"
+ bug: "277305460"
}
flag {
- name: "enable_system_pinch_zoom_gesture"
+ name: "remove_child_hover_check_for_touch_exploration"
namespace: "accessibility"
- description: "Feature flag for system pinch zoom gesture detector and related opt-out apis"
- bug: "283323770"
+ description: "Remove a check for a hovered child that prevents touch events from being delegated to non-direct descendants"
+ bug: "304770837"
}
flag {
- name: "prevent_leaking_viewrootimpl"
+ name: "restore_a11y_secure_settings_on_hsum_device"
namespace: "accessibility"
- description: "Clear pending messages and callbacks of the handler in AccessibilityInteractionController when the ViewRootImpl is detached from Window to prevent leaking ViewRootImpl"
- bug: "320701910"
+ description: "Grab the a11y settings and send the settings restored broadcast for current visible foreground user"
+ bug: "381294327"
metadata {
purpose: PURPOSE_BUGFIX
}
@@ -233,13 +247,10 @@ flag {
}
flag {
- name: "restore_a11y_secure_settings_on_hsum_device"
+ name: "skip_accessibility_warning_dialog_for_trusted_services"
namespace: "accessibility"
- description: "Grab the a11y settings and send the settings restored broadcast for current visible foreground user"
- bug: "381294327"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
+ description: "Skips showing the accessibility warning dialog for trusted services."
+ bug: "303511250"
}
flag {
@@ -274,6 +285,13 @@ flag {
}
flag {
+ namespace: "accessibility"
+ name: "update_always_on_a11y_service"
+ description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
+ bug: "298869916"
+}
+
+flag {
name: "warning_use_default_dialog_type"
namespace: "accessibility"
description: "Uses the default type for the A11yService warning dialog, instead of SYSTEM_ALERT_DIALOG"
@@ -282,11 +300,3 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
-
-flag {
- name: "indeterminate_range_info"
- namespace: "accessibility"
- description: "Creates a way to create an INDETERMINATE RangeInfo"
- bug: "376108874"
- is_exported: true
-}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 3f2aa1ccecd8..9468301ac996 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -95,7 +95,9 @@ public enum DesktopModeFlags {
Flags::enableTopVisibleRootTaskPerUserTracking, true),
ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
- ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true);
+ ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true),
+ ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES(
+ Flags::enableDesktopWindowingMultiInstanceFeatures, true);
/**
* Flag class, to be used in case the enum cannot be used because the flag is not accessible.
@@ -148,28 +150,22 @@ public enum DesktopModeFlags {
return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption);
}
+ public static boolean isDesktopModeForcedEnabled() {
+ return getToggleOverride() == ToggleOverride.OVERRIDE_ON;
+ }
+
private static boolean isFlagTrue(BooleanSupplier flagFunction,
boolean shouldOverrideByDevOption) {
if (!shouldOverrideByDevOption) return flagFunction.getAsBoolean();
if (Flags.showDesktopExperienceDevOption()) {
- return switch (getToggleOverride(null)) {
+ return switch (getToggleOverride()) {
case OVERRIDE_UNSET, OVERRIDE_OFF -> flagFunction.getAsBoolean();
case OVERRIDE_ON -> true;
};
}
if (Flags.showDesktopWindowingDevOption()) {
- Application application = ActivityThread.currentApplication();
- if (application == null) {
- Log.w(TAG, "Could not get the current application.");
- return flagFunction.getAsBoolean();
- }
- ContentResolver contentResolver = application.getContentResolver();
- if (contentResolver == null) {
- Log.w(TAG, "Could not get the content resolver for the application.");
- return flagFunction.getAsBoolean();
- }
boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode();
- return switch (getToggleOverride(contentResolver)) {
+ return switch (getToggleOverride()) {
case OVERRIDE_UNSET -> flagFunction.getAsBoolean();
// When toggle override matches its default state, don't override flags. This
// helps users reset their feature overrides.
@@ -180,14 +176,13 @@ public enum DesktopModeFlags {
return flagFunction.getAsBoolean();
}
- private static ToggleOverride getToggleOverride(@Nullable ContentResolver contentResolver) {
+ private static ToggleOverride getToggleOverride() {
// If cached, return it
if (sCachedToggleOverride != null) {
return sCachedToggleOverride;
}
-
// Otherwise, fetch and cache it
- ToggleOverride override = getToggleOverrideFromSystem(contentResolver);
+ ToggleOverride override = getToggleOverrideFromSystem();
sCachedToggleOverride = override;
Log.d(TAG, "Toggle override initialized to: " + override);
return override;
@@ -196,8 +191,7 @@ public enum DesktopModeFlags {
/**
* Returns {@link ToggleOverride} from Settings.Global set by toggle.
*/
- private static ToggleOverride getToggleOverrideFromSystem(
- @Nullable ContentResolver contentResolver) {
+ private static ToggleOverride getToggleOverrideFromSystem() {
int settingValue;
if (Flags.showDesktopExperienceDevOption()) {
settingValue = SystemProperties.getInt(
@@ -205,6 +199,16 @@ public enum DesktopModeFlags {
ToggleOverride.OVERRIDE_UNSET.getSetting()
);
} else {
+ final Application application = ActivityThread.currentApplication();
+ if (application == null) {
+ Log.w(TAG, "Could not get the current application.");
+ return ToggleOverride.OVERRIDE_UNSET;
+ }
+ final ContentResolver contentResolver = application.getContentResolver();
+ if (contentResolver == null) {
+ Log.w(TAG, "Could not get the content resolver for the application.");
+ return ToggleOverride.OVERRIDE_UNSET;
+ }
settingValue = Settings.Global.getInt(
contentResolver,
Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 51d488fdd76b..d20b06738f8c 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -600,3 +600,13 @@ flag {
description: "Enables split screen on non default displays"
bug: "384999213"
}
+
+flag {
+ name: "enable_desktop_mode_through_dev_option"
+ namespace: "lse_desktop_experience"
+ description: "Enables support for desktop mode through developer options for devices eligible for desktop mode."
+ bug: "382238347"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index c85b5d7aa7a6..af763e4c5fa9 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -58,15 +58,23 @@ public abstract class BaseCommand {
mRawArgs = args;
mArgs.init(null, null, null, null, args, 0);
+ int status = 1;
try {
onRun();
+ status = 0;
} catch (IllegalArgumentException e) {
onShowUsage(System.err);
System.err.println();
System.err.println("Error: " + e.getMessage());
+ status = 0;
} catch (Exception e) {
e.printStackTrace(System.err);
- System.exit(1);
+ } finally {
+ System.out.flush();
+ System.err.flush();
+ }
+ if (status != 0) {
+ System.exit(status);
}
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 72cb9d1a20ac..98d1ef6057fd 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -217,9 +217,9 @@ oneway interface IStatusBar
void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
/**
- * Notifies System UI that the display is ready to show system decorations.
+ * Notifies System UI that the system decorations should be added on the display.
*/
- void onDisplayReady(int displayId);
+ void onDisplayAddSystemDecorations(int displayId);
/**
* Notifies System UI that the system decorations should be removed from the display.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 3afe27ea591f..a2f4ca2c1b06 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -280,6 +280,7 @@ cc_library_shared_for_libandroid_runtime {
],
static_libs: [
+ "android.os.flags-aconfig-cc",
"libasync_safe",
"libbinderthreadstateutils",
"libdmabufinfo",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aea1734918d6..5c0b72013a06 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -22,6 +22,7 @@
#include <android-base/parsebool.h>
#include <android-base/properties.h>
#include <android/graphics/jni_runtime.h>
+#include <android_os.h>
#include <android_runtime/AndroidRuntime.h>
#include <assert.h>
#include <binder/IBinder.h>
@@ -893,9 +894,13 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p
madviseWillNeedFileSizeOdex,
"-XMadviseWillNeedOdexFileSize:");
- parseRuntimeOption("dalvik.vm.madvise.artfile.size",
- madviseWillNeedFileSizeArt,
- "-XMadviseWillNeedArtFileSize:");
+ // Historically, dalvik.vm.madvise.artfile.size was set to UINT_MAX by default. With the
+ // disable_madvise_art_default flag rollout, we use this default only when the flag is disabled.
+ // TODO(b/382110550): Remove this property/flag entirely after validating and ramping.
+ const char* madvise_artfile_size_default =
+ android::os::disable_madvise_artfile_default() ? "" : "4294967295";
+ parseRuntimeOption("dalvik.vm.madvise.artfile.size", madviseWillNeedFileSizeArt,
+ "-XMadviseWillNeedArtFileSize:", madvise_artfile_size_default);
/*
* Profile related options.
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 91b25c2bda06..639f5bff7614 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -74,7 +74,6 @@ static struct bindernative_offsets_t
jmethodID mExecTransact;
jmethodID mGetInterfaceDescriptor;
jmethodID mTransactionCallback;
- jmethodID mGetExtension;
// Object state.
jfieldID mObject;
@@ -490,12 +489,8 @@ public:
if (mVintf) {
::android::internal::Stability::markVintf(b.get());
}
- if (mSetExtensionCalled) {
- jobject javaIBinderObject = env->CallObjectMethod(obj, gBinderOffsets.mGetExtension);
- sp<IBinder> extensionFromJava = ibinderForJavaObject(env, javaIBinderObject);
- if (extensionFromJava != nullptr) {
- b.get()->setExtension(extensionFromJava);
- }
+ if (mExtension != nullptr) {
+ b.get()->setExtension(mExtension);
}
mBinder = b;
ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n",
@@ -521,12 +516,21 @@ public:
mVintf = false;
}
+ sp<IBinder> getExtension() {
+ AutoMutex _l(mLock);
+ sp<JavaBBinder> b = mBinder.promote();
+ if (b != nullptr) {
+ return b.get()->getExtension();
+ }
+ return mExtension;
+ }
+
void setExtension(const sp<IBinder>& extension) {
AutoMutex _l(mLock);
- mSetExtensionCalled = true;
+ mExtension = extension;
sp<JavaBBinder> b = mBinder.promote();
if (b != nullptr) {
- b.get()->setExtension(extension);
+ b.get()->setExtension(mExtension);
}
}
@@ -538,7 +542,8 @@ private:
// is too much binder state here, we can think about making JavaBBinder an
// sp here (avoid recreating it)
bool mVintf = false;
- bool mSetExtensionCalled = false;
+
+ sp<IBinder> mExtension;
};
// ----------------------------------------------------------------------------
@@ -1244,6 +1249,10 @@ static void android_os_Binder_blockUntilThreadAvailable(JNIEnv* env, jobject cla
return IPCThreadState::self()->blockUntilThreadAvailable();
}
+static jobject android_os_Binder_getExtension(JNIEnv* env, jobject obj) {
+ JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject);
+ return javaObjectForIBinder(env, jbh->getExtension());
+}
static void android_os_Binder_setExtension(JNIEnv* env, jobject obj, jobject extensionObject) {
JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject);
@@ -1286,7 +1295,8 @@ static const JNINativeMethod gBinderMethods[] = {
{ "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
{ "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
{ "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable },
- { "setExtensionNative", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
+ { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension },
+ { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
};
// clang-format on
@@ -1303,8 +1313,6 @@ static int int_register_android_os_Binder(JNIEnv* env)
gBinderOffsets.mTransactionCallback =
GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V");
gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
- gBinderOffsets.mGetExtension = GetMethodIDOrDie(env, clazz, "getExtension",
- "()Landroid/os/IBinder;");
return RegisterMethodsOrDie(
env, kBinderPathName,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index aad8f8a156b5..005c14ddcf69 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8308,26 +8308,9 @@
android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
android:protectionLevel="signature" />
- <!-- Allows a trusted application to perform actions on behalf of users inside of
- applications with privacy guarantees from the system.
- <p>This permission is currently only granted to system packages in the
- {@link android.app.role.SYSTEM_UI_INTELLIGENCE} role which complies with privacy
- requirements outlined in the Android CDD section "9.8.6 Content Capture".
- <p>Apps are not able to opt-out from caller having this permission.
- <p>Protection level: internal|role
- @SystemApi
- @hide
- @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) -->
- <permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED"
- android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
- android:protectionLevel="internal|role" />
-
<!-- Allows an application to perform actions on behalf of users inside of
applications.
<p>This permission is currently only granted to privileged system apps.
- <p>Apps contributing app functions can opt to disallow callers with this permission,
- limiting to only callers with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}
- instead.
<p>Protection level: internal|privileged
@FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) -->
<permission android:name="android.permission.EXECUTE_APP_FUNCTIONS"
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
index 1bde17358825..75bd244cbbf4 100644
--- a/core/res/res/layout/notification_2025_conversation_header.xml
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -29,7 +29,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="16sp"
+ android:textSize="@dimen/notification_2025_title_text_size"
android:singleLine="true"
android:layout_weight="1"
/>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index d29b7af9e24e..054583297d37 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -102,6 +102,7 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
index 5beab508aecf..9959b666b3bf 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -104,6 +104,7 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index d7c3263904d4..85ca124de8ff 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -130,6 +130,7 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 586cafdd2b57..a49e03484192 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7245,6 +7245,9 @@
<!-- Whether desktop mode is supported on the current device -->
<bool name="config_isDesktopModeSupported">false</bool>
+ <!-- Whether the developer option for desktop mode is supported on the current device -->
+ <bool name="config_isDesktopModeDevOptionSupported">false</bool>
+
<!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
<integer name="config_maxDesktopWindowingActiveTasks">0</integer>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d6b8704a978b..5644cf9dd61d 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -577,6 +577,9 @@
<dimen name="notification_text_size">14sp</dimen>
<!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) -->
<dimen name="notification_title_text_size">14sp</dimen>
+ <!-- Size of notification text titles, 2025 redesign version (see TextAppearance.StatusBar.EventContent.Title) -->
+ <!-- TODO: b/378660052 - When inlining the redesign flag, this should be updated directly in TextAppearance.DeviceDefault.Notification.Title -->
+ <dimen name="notification_2025_title_text_size">16sp</dimen>
<!-- Size of big notification text titles (see TextAppearance.StatusBar.EventContent.BigTitle) -->
<dimen name="notification_big_title_text_size">16sp</dimen>
<!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 772a7413a4a7..8bb3c995cf9f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -574,6 +574,7 @@
<java-symbol type="dimen" name="notification_text_size" />
<java-symbol type="dimen" name="notification_title_text_size" />
<java-symbol type="dimen" name="notification_subtext_size" />
+ <java-symbol type="dimen" name="notification_2025_title_text_size" />
<java-symbol type="dimen" name="notification_top_pad" />
<java-symbol type="dimen" name="notification_top_pad_narrow" />
<java-symbol type="dimen" name="notification_top_pad_large_text" />
@@ -5709,6 +5710,9 @@
<!-- Whether desktop mode is supported on the current device -->
<java-symbol type="bool" name="config_isDesktopModeSupported" />
+ <!-- Whether the developer option for desktop mode is supported on the current device -->
+ <java-symbol type="bool" name="config_isDesktopModeDevOptionSupported" />
+
<!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
<java-symbol type="integer" name="config_maxDesktopWindowingActiveTasks"/>
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 8fa510381060..dc2f0a69375d 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -307,8 +307,10 @@ public class DisplayManagerGlobalTest {
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
mDisplayManagerGlobal
.mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_ADDED, 0));
- assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal
- .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED, 0));
+ assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED,
+ mDisplayManagerGlobal
+ .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED,
+ 0));
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
mDisplayManagerGlobal.mapFiltersToInternalEventFlag(
DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, 0));
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index 31e07524d777..9aac02d0d07e 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -76,7 +76,7 @@ public class BundleTest {
/**
* Create a test bundle, parcel it and return the parcel.
*/
- private Parcel createBundleParcel(boolean withFd) throws Exception {
+ private Parcel createBundleParcel(boolean withFd, boolean hasIntent) throws Exception {
final Bundle source = new Bundle();
source.putString("string", "abc");
source.putInt("int", 1);
@@ -85,13 +85,14 @@ public class BundleTest {
pipe[1].close();
source.putParcelable("fd", pipe[0]);
}
+ source.setHasIntent(hasIntent);
return getParcelledBundle(source);
}
/**
* Verify a bundle generated by {@link #createBundleParcel(boolean)}.
*/
- private void checkBundle(Bundle b, boolean withFd) {
+ private void checkBundle(Bundle b, boolean withFd, boolean hasIntent) {
// First, do the checks without actually unparceling the bundle.
// (Note looking into the contents will unparcel a bundle, so we'll do it later.)
assertTrue("mParcelledData shouldn't be null here.", b.isParcelled());
@@ -107,6 +108,8 @@ public class BundleTest {
b.mFlags & (Bundle.FLAG_HAS_FDS | Bundle.FLAG_HAS_FDS_KNOWN));
}
+ assertEquals(b.hasIntent(), hasIntent);
+
// Then, check the contents.
assertEquals("abc", b.getString("string"));
assertEquals(1, b.getInt("int"));
@@ -139,42 +142,56 @@ public class BundleTest {
withFd = false;
// new Bundle with p
- p = createBundleParcel(withFd);
- checkBundle(new Bundle(p), withFd);
+ p = createBundleParcel(withFd, false);
+ checkBundle(new Bundle(p), withFd, false);
p.recycle();
// new Bundle with p and length
- p = createBundleParcel(withFd);
+ p = createBundleParcel(withFd, false);
length = p.readInt();
- checkBundle(new Bundle(p, length), withFd);
+ checkBundle(new Bundle(p, length), withFd, false);
+ p.recycle();
+
+ // readFromParcel()
+ p = createBundleParcel(withFd, false);
+ b = new Bundle();
+ b.readFromParcel(p);
+ checkBundle(b, withFd, false);
p.recycle();
// readFromParcel()
- p = createBundleParcel(withFd);
+ p = createBundleParcel(withFd, true);
b = new Bundle();
b.readFromParcel(p);
- checkBundle(b, withFd);
+ checkBundle(b, withFd, true);
p.recycle();
// Same test with FDs.
withFd = true;
// new Bundle with p
- p = createBundleParcel(withFd);
- checkBundle(new Bundle(p), withFd);
+ p = createBundleParcel(withFd, false);
+ checkBundle(new Bundle(p), withFd, false);
p.recycle();
// new Bundle with p and length
- p = createBundleParcel(withFd);
+ p = createBundleParcel(withFd, false);
length = p.readInt();
- checkBundle(new Bundle(p, length), withFd);
+ checkBundle(new Bundle(p, length), withFd, false);
+ p.recycle();
+
+ // readFromParcel()
+ p = createBundleParcel(withFd, false);
+ b = new Bundle();
+ b.readFromParcel(p);
+ checkBundle(b, withFd, false);
p.recycle();
// readFromParcel()
- p = createBundleParcel(withFd);
+ p = createBundleParcel(withFd, true);
b = new Bundle();
b.readFromParcel(p);
- checkBundle(b, withFd);
+ checkBundle(b, withFd, true);
p.recycle();
}
@@ -486,6 +503,7 @@ public class BundleTest {
p.writeInt(131313); // Invalid type
p.writeInt(0); // Anything, really
int end = p.dataPosition();
+ p.writeBoolean(false);
p.setDataPosition(0);
return new Bundle(p, end - start);
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index b8059d08756a..1edbffa9d572 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -594,7 +594,6 @@ applications that come with the platform
<!-- Permission required for CTS test - FileIntegrityManagerTest -->
<permission name="android.permission.SETUP_FSVERITY" />
<!-- Permissions required for CTS test - AppFunctionManagerTest -->
- <permission name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" />
<permission name="android.permission.EXECUTE_APP_FUNCTIONS" />
<!-- Permission required for CTS test - CtsNfcTestCases -->
<permission name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" />
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index 0ea3c2a80fb4..835456b2868e 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -20,20 +20,23 @@ import android.app.TaskInfo
import android.content.Context
import android.window.DesktopModeFlags
import com.android.internal.R
+import java.util.ArrayList
/**
* Class to decide whether to apply app compat policies in desktop mode.
*/
// TODO(b/347289970): Consider replacing with API
-class DesktopModeCompatPolicy(context: Context) {
+class DesktopModeCompatPolicy(private val context: Context) {
private val systemUiPackage: String = context.resources.getString(R.string.config_systemUi)
+ private val defaultHomePackage: String?
+ get() = context.getPackageManager().getHomeActivities(ArrayList())?.packageName
/**
* If the top activity should be exempt from desktop windowing and forced back to fullscreen.
- * Currently includes all system ui activities and modal dialogs. However if the top activity is
- * not being displayed, regardless of its configuration, we will not exempt it as to remain in
- * the desktop windowing environment.
+ * Currently includes all system ui, default home and transparent stack activities. However if
+ * the top activity is not being displayed, regardless of its configuration, we will not exempt
+ * it as to remain in the desktop windowing environment.
*/
fun isTopActivityExemptFromDesktopWindowing(task: TaskInfo) =
isTopActivityExemptFromDesktopWindowing(task.baseActivity?.packageName,
@@ -43,6 +46,7 @@ class DesktopModeCompatPolicy(context: Context) {
numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) =
DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue
&& ((isSystemUiTask(packageName)
+ || isPartOfDefaultHomePackage(packageName)
|| isTransparentTask(isActivityStackTransparent, numActivities))
&& !isTopActivityNoDisplay)
@@ -57,4 +61,10 @@ class DesktopModeCompatPolicy(context: Context) {
isActivityStackTransparent && numActivities > 0
private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
+
+ /**
+ * Returns true if the tasks base activity is part of the default home package.
+ */
+ private fun isPartOfDefaultHomePackage(packageName: String?) =
+ packageName != null && packageName == defaultHomePackage
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 1ee71ca78815..e196880aad0f 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -212,10 +212,18 @@ public class DesktopModeStatus {
}
/**
+ * Return {@code true} if the current device supports the developer option for desktop mode.
+ */
+ private static boolean isDesktopModeDevOptionSupported(@NonNull Context context) {
+ return context.getResources().getBoolean(R.bool.config_isDesktopModeDevOptionSupported);
+ }
+
+ /**
* Return {@code true} if desktop mode dev option should be shown on current device
*/
public static boolean canShowDesktopModeDevOption(@NonNull Context context) {
- return isDeviceEligibleForDesktopMode(context) && Flags.showDesktopWindowingDevOption();
+ return isDeviceEligibleForDesktopModeDevOption(context)
+ && Flags.showDesktopWindowingDevOption();
}
/**
@@ -226,17 +234,25 @@ public class DesktopModeStatus {
}
/** Returns if desktop mode dev option should be enabled if there is no user override. */
- public static boolean shouldDevOptionBeEnabledByDefault() {
- return Flags.enableDesktopWindowingMode();
+ public static boolean shouldDevOptionBeEnabledByDefault(Context context) {
+ return isDeviceEligibleForDesktopMode(context) && Flags.enableDesktopWindowingMode();
}
/**
* Return {@code true} if desktop mode is enabled and can be entered on the current device.
*/
public static boolean canEnterDesktopMode(@NonNull Context context) {
- if (!isDeviceEligibleForDesktopMode(context)) return false;
+ return (isDeviceEligibleForDesktopMode(context)
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue())
+ || isDesktopModeEnabledByDevOption(context);
+ }
- return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue();
+ /**
+ * Check if Desktop mode should be enabled because the dev option is shown and enabled.
+ */
+ private static boolean isDesktopModeEnabledByDevOption(@NonNull Context context) {
+ return DesktopModeFlags.isDesktopModeForcedEnabled()
+ && canShowDesktopModeDevOption(context);
}
/**
@@ -298,7 +314,21 @@ public class DesktopModeStatus {
* Return {@code true} if desktop mode is unrestricted and is supported in the device.
*/
public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
- return !enforceDeviceRestrictions() || isDesktopModeSupported(context);
+ return !enforceDeviceRestrictions() || isDesktopModeSupported(context) || (
+ Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionSupported(
+ context));
+ }
+
+ /**
+ * Return {@code true} if the developer option for desktop mode is unrestricted and is supported
+ * in the device.
+ *
+ * Note that, if {@link #isDeviceEligibleForDesktopMode(Context)} is true, then
+ * {@link #isDeviceEligibleForDesktopModeDevOption(Context)} is also true.
+ */
+ private static boolean isDeviceEligibleForDesktopModeDevOption(@NonNull Context context) {
+ return !enforceDeviceRestrictions() || isDesktopModeSupported(context)
+ || isDesktopModeDevOptionSupported(context);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index b4ef9f0fc2ac..55ed5fa4b56f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -168,7 +168,8 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
mAnimationRunner.cancelAnimationFromMerge();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 8dabd54a01ff..d1c7f7d7dcad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -1463,7 +1463,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (mClosePrepareTransition == transition) {
mClosePrepareTransition = null;
@@ -1476,7 +1478,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (info.getType() == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION
&& !mCloseTransitionRequested && info.getChanges().isEmpty() && mApps == null) {
finishCallback.onTransitionFinished(null);
- t.apply();
+ startT.apply();
applyFinishOpenTransition();
return;
}
@@ -1489,7 +1491,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
// Handle the commit transition if this handler is running the open transition.
finishCallback.onTransitionFinished(null);
- t.apply();
+ startT.apply();
if (mCloseTransitionRequested) {
if (mApps == null || mApps.length == 0) {
// animation was done
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
index 29fb1a23017c..48b83ce49e61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
@@ -248,7 +248,9 @@ public class BubbleTransitions {
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
}
@@ -423,7 +425,9 @@ public class BubbleTransitions {
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index e69d60ddd6c6..4c3bde9b2b3a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -39,6 +39,7 @@ import androidx.annotation.BinderThread;
import com.android.window.flags.Flags;
import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
@@ -91,7 +92,8 @@ public class DisplayController {
onDisplayAdded(displayIds[i]);
}
- if (Flags.enableConnectedDisplaysWindowDrag()) {
+ if (Flags.enableConnectedDisplaysWindowDrag()
+ && DesktopModeStatus.canEnterDesktopMode(mContext)) {
mDisplayManager.registerTopologyListener(mMainExecutor,
this::onDisplayTopologyChanged);
onDisplayTopologyChanged(mDisplayManager.getDisplayTopology());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt
index 0577f9e625ca..16938647001b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt
@@ -25,6 +25,7 @@ import android.util.SparseArray
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.UserChangeListener
+import androidx.core.util.size
/** Creates and manages contexts for all the profiles of the current user. */
class UserProfileContexts(
@@ -35,6 +36,8 @@ class UserProfileContexts(
// Contexts for all the profiles of the current user.
private val currentProfilesContext = SparseArray<Context>()
+ private val shellUserId = baseContext.userId
+
lateinit var userContext: Context
private set
@@ -49,6 +52,9 @@ class UserProfileContexts(
currentProfilesContext.clear()
this@UserProfileContexts.userContext = userContext
currentProfilesContext.put(newUserId, userContext)
+ if (newUserId != shellUserId) {
+ currentProfilesContext.put(shellUserId, baseContext)
+ }
}
override fun onUserProfilesChanged(profiles: List<UserInfo>) {
@@ -69,9 +75,9 @@ class UserProfileContexts(
currentProfilesContext.put(profile.id, profileContext)
}
val profilesToRemove = buildList<Int> {
- for (i in 0..<currentProfilesContext.size()) {
+ for (i in 0..<currentProfilesContext.size) {
val userId = currentProfilesContext.keyAt(i)
- if (profiles.none { it.id == userId }) {
+ if (userId != shellUserId && profiles.none { it.id == userId }) {
add(userId)
}
}
@@ -80,4 +86,12 @@ class UserProfileContexts(
}
operator fun get(userId: Int): Context? = currentProfilesContext.get(userId)
+
+ fun getOrCreate(userId: Int): Context {
+ val context = currentProfilesContext[userId]
+ if (context != null) return context
+ return baseContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0).also {
+ currentProfilesContext[userId] = it
+ }
+ }
}
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 c81838f56a74..0f232d50f2fc 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
@@ -111,6 +111,7 @@ import com.android.wm.shell.desktopmode.education.AppToWebEducationFilter;
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository;
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer;
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver;
import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer;
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer;
@@ -760,6 +761,7 @@ public abstract class WMShellModule {
Optional<BubbleController> bubbleController,
OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
DesksOrganizer desksOrganizer,
+ DesksTransitionObserver desksTransitionObserver,
UserProfileContexts userProfileContexts,
DesktopModeCompatPolicy desktopModeCompatPolicy) {
return new DesktopTasksController(
@@ -797,6 +799,7 @@ public abstract class WMShellModule {
bubbleController,
overviewToDesktopTransitionObserver,
desksOrganizer,
+ desksTransitionObserver,
userProfileContexts,
desktopModeCompatPolicy);
}
@@ -1134,6 +1137,7 @@ public abstract class WMShellModule {
Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
Optional<BackAnimationController> backAnimationController,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
+ @NonNull DesksTransitionObserver desksTransitionObserver,
ShellInit shellInit) {
return desktopUserRepositories.flatMap(
repository ->
@@ -1146,11 +1150,20 @@ public abstract class WMShellModule {
desktopMixedTransitionHandler.get(),
backAnimationController.get(),
desktopWallpaperActivityTokenProvider,
+ desksTransitionObserver,
shellInit)));
}
@WMSingleton
@Provides
+ static DesksTransitionObserver provideDesksTransitionObserver(
+ @NonNull @DynamicOverride DesktopUserRepositories desktopUserRepositories
+ ) {
+ return new DesksTransitionObserver(desktopUserRepositories);
+ }
+
+ @WMSingleton
+ @Provides
static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
Context context,
Transitions transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index 164d04bbde65..b93d2e396402 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -152,8 +152,8 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
pw.println("Error: desk id should be an integer")
return false
}
- pw.println("Not implemented.")
- return false
+ controller.removeDesk(deskId)
+ return true
}
private fun runRemoveAllDesks(args: Array<String>, pw: PrintWriter): Boolean {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 4ff1a5f1be31..043b353ba380 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -174,6 +174,9 @@ class DesktopRepository(
/** Returns the number of desks in the given display. */
fun getNumberOfDesks(displayId: Int) = desktopData.getNumberOfDesks(displayId)
+ /** Returns the display the given desk is in. */
+ fun getDisplayForDesk(deskId: Int) = desktopData.getDisplayForDesk(deskId)
+
/** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
desktopGestureExclusionListener = regionListener
@@ -207,6 +210,14 @@ class DesktopRepository(
desktopData.createDesk(displayId, deskId)
}
+ /** Returns the ids of the existing desks in the given display. */
+ @VisibleForTesting
+ fun getDeskIds(displayId: Int): Set<Int> =
+ desktopData.desksSequence(displayId).map { desk -> desk.deskId }.toSet()
+
+ /** Returns the id of the default desk in the given display. */
+ fun getDefaultDeskId(displayId: Int): Int? = getDefaultDesk(displayId)?.deskId
+
/** Returns the default desk in the given display. */
private fun getDefaultDesk(displayId: Int): Desk? = desktopData.getDefaultDesk(displayId)
@@ -716,17 +727,13 @@ class DesktopRepository(
}
}
- /**
- * Removes the active desk for the given [displayId] and returns the active tasks on that desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun removeDesk(displayId: Int): ArraySet<Int> {
- val desk = desktopData.getActiveDesk(displayId)
- if (desk == null) {
- logW("Could not find desk to remove: displayId=%d", displayId)
- return ArraySet()
- }
+ /** Removes the given desk and returns the active tasks in that desk. */
+ fun removeDesk(deskId: Int): Set<Int> {
+ val desk =
+ desktopData.getDesk(deskId)
+ ?: return emptySet<Int>().also {
+ logW("Could not find desk to remove: deskId=%d", deskId)
+ }
val activeTasks = ArraySet(desk.activeTasks)
desktopData.remove(desk.deskId)
return activeTasks
@@ -1066,7 +1073,7 @@ class DesktopRepository(
}
override fun getDisplayForDesk(deskId: Int): Int =
- getAllActiveDesks().find { it.deskId == deskId }?.displayId
+ desksSequence().find { it.deskId == deskId }?.displayId
?: error("Display for desk=$deskId not found")
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 4eaf8049f04f..3f88e7bddd34 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -103,7 +103,9 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCR
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DeskTransition
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
@@ -185,6 +187,7 @@ class DesktopTasksController(
private val bubbleController: Optional<BubbleController>,
private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
private val desksOrganizer: DesksOrganizer,
+ private val desksTransitionObserver: DesksTransitionObserver,
private val userProfileContexts: UserProfileContexts,
private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
) :
@@ -1525,11 +1528,16 @@ class DesktopTasksController(
private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
logV("addWallpaperActivity")
if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) {
+
+ // If the wallpaper activity for this display already exists, let's reorder it to top.
+ val wallpaperActivityToken = desktopWallpaperActivityTokenProvider.getToken(displayId)
+ if (wallpaperActivityToken != null) {
+ wct.reorder(wallpaperActivityToken, /* onTop= */ true)
+ return
+ }
+
val intent = Intent(context, DesktopWallpaperActivity::class.java)
- if (
- desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
- Flags.enablePerDisplayDesktopWallpaperActivity()
- ) {
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
@@ -1870,9 +1878,10 @@ class DesktopTasksController(
// need updates in some cases.
val baseActivity = callingTaskInfo.baseActivity ?: return
val fillIn: Intent =
- userProfileContexts[callingTaskInfo.userId]
- ?.packageManager
- ?.getLaunchIntentForPackage(baseActivity.packageName) ?: return
+ userProfileContexts
+ .getOrCreate(callingTaskInfo.userId)
+ .packageManager
+ .getLaunchIntentForPackage(baseActivity.packageName) ?: return
fillIn.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
val launchIntent =
PendingIntent.getActivity(
@@ -2368,20 +2377,62 @@ class DesktopTasksController(
)
}
- fun removeDesktop(displayId: Int) {
+ /** Removes the default desk in the given display. */
+ @Deprecated("Deprecated with multi-desks.", ReplaceWith("removeDesk()"))
+ fun removeDefaultDeskInDisplay(displayId: Int) {
+ val deskId =
+ checkNotNull(taskRepository.getDefaultDeskId(displayId)) {
+ "Expected a default desk to exist"
+ }
+ removeDesk(displayId = displayId, deskId = deskId)
+ }
+
+ /** Removes the given desk. */
+ fun removeDesk(deskId: Int) {
+ val displayId = taskRepository.getDisplayForDesk(deskId)
+ removeDesk(displayId = displayId, deskId = deskId)
+ }
+
+ private fun removeDesk(displayId: Int, deskId: Int) {
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
+ logV("removeDesk deskId=%d from displayId=%d", deskId, displayId)
- val tasksToRemove = taskRepository.removeDesk(displayId)
- val wct = WindowContainerTransaction()
- tasksToRemove.forEach {
- val task = shellTaskOrganizer.getRunningTaskInfo(it)
- if (task != null) {
- wct.removeTask(task.token)
+ val tasksToRemove =
+ if (Flags.enableMultipleDesktopsBackend()) {
+ taskRepository.getActiveTaskIdsInDesk(deskId)
} else {
- recentTasksController?.removeBackgroundTask(it)
+ // TODO: 362720497 - make sure minimized windows are also removed in WM
+ // and the repository.
+ taskRepository.removeDesk(deskId)
+ }
+
+ val wct = WindowContainerTransaction()
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ tasksToRemove.forEach {
+ val task = shellTaskOrganizer.getRunningTaskInfo(it)
+ if (task != null) {
+ wct.removeTask(task.token)
+ } else {
+ recentTasksController?.removeBackgroundTask(it)
+ }
}
+ } else {
+ // TODO: 362720497 - double check background tasks are also removed.
+ desksOrganizer.removeDesk(wct, deskId)
+ }
+ if (!Flags.enableMultipleDesktopsBackend() && wct.isEmpty) return
+ val transition = transitions.startTransition(TRANSIT_CLOSE, wct, /* handler= */ null)
+ if (Flags.enableMultipleDesktopsBackend()) {
+ desksTransitionObserver.addPendingTransition(
+ DeskTransition.RemoveDesk(
+ token = transition,
+ displayId = displayId,
+ deskId = deskId,
+ tasks = tasksToRemove,
+ onDeskRemovedListener = onDeskRemovedListener,
+ )
+ )
}
- if (!wct.isEmpty) transitions.startTransition(TRANSIT_CLOSE, wct, null)
}
/** Enter split by using the focused desktop task in given `displayId`. */
@@ -3085,7 +3136,7 @@ class DesktopTasksController(
override fun removeDesktop(displayId: Int) {
executeRemoteCallWithTaskPermission(controller, "removeDesktop") { c ->
- c.removeDesktop(displayId)
+ c.removeDefaultDeskInDisplay(displayId)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index b3648699ed0b..3ada988ba2a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -37,6 +37,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -58,6 +59,7 @@ class DesktopTasksTransitionObserver(
private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
private val backAnimationController: BackAnimationController,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
+ private val desksTransitionObserver: DesksTransitionObserver,
shellInit: ShellInit,
) : Transitions.TransitionObserver {
@@ -87,6 +89,7 @@ class DesktopTasksTransitionObserver(
finishTransaction: SurfaceControl.Transaction,
) {
// TODO: b/332682201 Update repository state
+ desksTransitionObserver.onTransitionReady(transition, info)
if (
DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
.isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 91f10dc4faf5..cc3d86c0c056 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -458,7 +458,8 @@ sealed class DragToDesktopTransitionHandler(
override fun mergeAnimation(
transition: IBinder,
info: TransitionInfo,
- t: SurfaceControl.Transaction,
+ startT: SurfaceControl.Transaction,
+ finishT: SurfaceControl.Transaction,
mergeTarget: IBinder,
finishCallback: Transitions.TransitionFinishCallback,
) {
@@ -488,18 +489,18 @@ sealed class DragToDesktopTransitionHandler(
if (isEndTransition) {
setupEndDragToDesktop(
info,
- startTransaction = t,
+ startTransaction = startT,
finishTransaction = startTransactionFinishT,
)
// Call finishCallback to merge animation before startTransitionFinishCb is called
finishCallback.onTransitionFinished(/* wct= */ null)
- animateEndDragToDesktop(startTransaction = t, startTransitionFinishCb)
+ animateEndDragToDesktop(startTransaction = startT, startTransitionFinishCb)
} else if (isCancelTransition) {
info.changes.forEach { change ->
- t.show(change.leash)
+ startT.show(change.leash)
startTransactionFinishT.show(change.leash)
}
- t.apply()
+ startT.apply()
finishCallback.onTransitionFinished(/* wct= */ null)
startTransitionFinishCb.onTransitionFinished(/* wct= */ null)
clearState()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
new file mode 100644
index 000000000000..47088c0b545a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 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.desktopmode.multidesks
+
+import android.os.IBinder
+
+/** Represents shell-started transitions involving desks. */
+sealed class DeskTransition {
+ /** The transition token. */
+ abstract val token: IBinder
+
+ /** A transition to remove a desk and its tasks from a display. */
+ data class RemoveDesk(
+ override val token: IBinder,
+ val displayId: Int,
+ val deskId: Int,
+ val tasks: Set<Int>,
+ val onDeskRemovedListener: OnDeskRemovedListener?,
+ ) : DeskTransition()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
new file mode 100644
index 000000000000..3e49b8a4538b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2025 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.desktopmode.multidesks
+
+import android.os.IBinder
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import com.android.window.flags.Flags
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+
+/**
+ * Observer of desk-related transitions, such as adding, removing or activating a whole desk. It
+ * tracks pending transitions and updates repository state once they finish.
+ */
+class DesksTransitionObserver(private val desktopUserRepositories: DesktopUserRepositories) {
+ private val deskTransitions = mutableMapOf<IBinder, DeskTransition>()
+
+ /** Adds a pending desk transition to be tracked. */
+ fun addPendingTransition(transition: DeskTransition) {
+ if (!Flags.enableMultipleDesktopsBackend()) return
+ deskTransitions[transition.token] = transition
+ }
+
+ /**
+ * Called when any transition is ready, which may include transitions not tracked by this
+ * observer.
+ */
+ fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
+ if (!Flags.enableMultipleDesktopsBackend()) return
+ val deskTransition = deskTransitions.remove(transition) ?: return
+ val desktopRepository = desktopUserRepositories.current
+ when (deskTransition) {
+ is DeskTransition.RemoveDesk -> {
+ check(info.type == TRANSIT_CLOSE) { "Expected close transition for desk removal" }
+ // TODO: b/362720497 - consider verifying the desk was actually removed through the
+ // DesksOrganizer. The transition info won't have changes if the desk was not
+ // visible, such as when dismissing from Overview.
+ val deskId = deskTransition.deskId
+ val displayId = deskTransition.displayId
+ desktopRepository.removeDesk(deskTransition.deskId)
+ deskTransition.onDeskRemovedListener?.onDeskRemoved(displayId, deskId)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 52b6c62b0721..31715f0444a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -175,7 +175,9 @@ public class FreeformTaskTransitionHandler
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ArrayList<Animator> animations = mAnimations.get(mergeTarget);
if (animations == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index f8e6285b0493..d666126b91ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -277,7 +277,8 @@ public class KeyguardTransitionHandler
@Override
public void mergeAnimation(@NonNull IBinder nextTransition, @NonNull TransitionInfo nextInfo,
- @NonNull SurfaceControl.Transaction nextT, @NonNull IBinder currentTransition,
+ @NonNull SurfaceControl.Transaction nextT, @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder currentTransition,
@NonNull TransitionFinishCallback nextFinishCallback) {
final StartedTransition playing = mStartedTransitions.get(currentTransition);
if (playing == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 2f3c15208621..f0e6ae45c389 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -372,7 +372,9 @@ public class PipTransition extends PipTransitionController {
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
end();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index d3ae411469cc..0fa6a116350e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -653,7 +653,9 @@ public class TvPipTransition extends PipTransitionController {
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: merge animation", TAG);
if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 229962488acf..78aa686a3a0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -247,7 +247,9 @@ public class PipTransition extends PipTransitionController implements
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
// Just jump-cut the current animation if any, but do not merge.
if (info.getType() == TRANSIT_EXIT_PIP) {
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 55133780f517..8ad2e1d3c7c9 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
@@ -307,7 +307,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
final RecentsController controller = findController(mergeTarget);
if (controller == null) {
@@ -315,7 +317,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
"RecentsTransitionHandler.mergeAnimation: no controller found");
return;
}
- controller.merge(info, t, mergeTarget, finishCallback);
+ controller.merge(info, startT, mergeTarget, finishCallback);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 3091be574a53..fed336b17f19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -461,12 +461,14 @@ class SplitScreenTransitions {
return transition;
}
- void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
+ void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
if (mergeTarget != mAnimatingTransition) return;
if (mActiveRemoteHandler != null) {
- mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mActiveRemoteHandler.mergeAnimation(transition, info, startT,
+ finishT, mergeTarget, finishCallback);
} else {
for (int i = mAnimations.size() - 1; i >= 0; --i) {
final Animator anim = mAnimations.get(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 2174017996a8..6783df8f8324 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2977,10 +2977,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "mergeAnimation: transition=%d", info.getDebugId());
- mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mSplitTransitions.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+ finishCallback);
}
/** Jump the current transition animation to the end. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 1eaae7ec83d9..9af23080351f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -652,9 +652,16 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
}
continue;
}
- startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
- finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl())
- .setPosition(chg.getLeash(), 0, 0);
+ final Rect boundsOnScreen = tv.prepareOpen(chg.getTaskInfo(), chg.getLeash());
+ if (boundsOnScreen != null) {
+ if (wct == null) wct = new WindowContainerTransaction();
+ updateBounds(tv, boundsOnScreen, startTransaction, finishTransaction,
+ chg.getTaskInfo(), chg.getLeash(), wct);
+ } else {
+ startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
+ finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl())
+ .setPosition(chg.getLeash(), 0, 0);
+ }
changesHandled++;
}
}
@@ -683,30 +690,8 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
WindowContainerTransaction wct) {
final Rect boundsOnScreen = taskView.prepareOpen(taskInfo, leash);
if (boundsOnScreen != null) {
- final SurfaceControl tvSurface = taskView.getSurfaceControl();
- // Surface is ready, so just reparent the task to this surface control
- startTransaction.reparent(leash, tvSurface)
- .show(leash);
- // Also reparent on finishTransaction since the finishTransaction will reparent back
- // to its "original" parent by default.
- if (finishTransaction != null) {
- finishTransaction.reparent(leash, tvSurface)
- .setPosition(leash, 0, 0)
- // TODO: maybe once b/280900002 is fixed this will be unnecessary
- .setWindowCrop(leash, boundsOnScreen.width(), boundsOnScreen.height());
- }
- if (useRepo()) {
- final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView);
- if (state != null) {
- state.mBounds.set(boundsOnScreen);
- state.mVisible = true;
- }
- } else {
- updateBoundsState(taskView, boundsOnScreen);
- updateVisibilityState(taskView, true /* visible */);
- }
- wct.setBounds(taskInfo.token, boundsOnScreen);
- taskView.applyCaptionInsetsIfNeeded();
+ updateBounds(taskView, boundsOnScreen, startTransaction, finishTransaction, taskInfo,
+ leash, wct);
} else {
// The surface has already been destroyed before the task has appeared,
// so go ahead and hide the task entirely
@@ -730,6 +715,36 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
taskView.notifyAppeared(newTask);
}
+ private void updateBounds(TaskViewTaskController taskView, Rect boundsOnScreen,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+ WindowContainerTransaction wct) {
+ final SurfaceControl tvSurface = taskView.getSurfaceControl();
+ // Surface is ready, so just reparent the task to this surface control
+ startTransaction.reparent(leash, tvSurface)
+ .show(leash);
+ // Also reparent on finishTransaction since the finishTransaction will reparent back
+ // to its "original" parent by default.
+ if (finishTransaction != null) {
+ finishTransaction.reparent(leash, tvSurface)
+ .setPosition(leash, 0, 0)
+ .setWindowCrop(leash, boundsOnScreen.width(), boundsOnScreen.height());
+ }
+ if (useRepo()) {
+ final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView);
+ if (state != null) {
+ state.mBounds.set(boundsOnScreen);
+ state.mVisible = true;
+ }
+ } else {
+ updateBoundsState(taskView, boundsOnScreen);
+ updateVisibilityState(taskView, true /* visible */);
+ }
+ wct.setBounds(taskInfo.token, boundsOnScreen);
+ taskView.applyCaptionInsetsIfNeeded();
+ }
+
/** Interface for running an external transition in this object's pending queue. */
public interface ExternalTransition {
/** Starts a transition and returns an identifying key for lookup. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index d8e7c2ccb15f..743bd052995e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -176,7 +176,9 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
abstract void mergeAnimation(
@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback);
abstract void onTransitionConsumed(
@@ -691,7 +693,9 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
for (int i = 0; i < mActiveTransitions.size(); ++i) {
if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
@@ -701,7 +705,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
// Already done, so no need to end it.
return;
}
- mixed.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mixed.mergeAnimation(transition, info, startT, finishT, mergeTarget, finishCallback);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index 29a58d7f75dc..1853ffa96dfc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -384,7 +384,8 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
@Override
void mergeAnimation(
@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
switch (mType) {
case TYPE_DISPLAY_AND_SPLIT_CHANGE:
@@ -394,7 +395,7 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
mPipHandler.end();
mActivityEmbeddingController.mergeAnimation(
- transition, info, t, mergeTarget, finishCallback);
+ transition, info, startT, finishT, mergeTarget, finishCallback);
return;
case TYPE_ENTER_PIP_FROM_SPLIT:
if (mAnimType == ANIM_TYPE_GOING_HOME) {
@@ -405,26 +406,28 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
mPipHandler.end();
if (mLeftoversHandler != null) {
mLeftoversHandler.mergeAnimation(
- transition, info, t, mergeTarget, finishCallback);
+ transition, info, startT, finishT, mergeTarget, finishCallback);
}
}
return;
case TYPE_KEYGUARD:
- mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mKeyguardHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+ finishCallback);
return;
case TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE:
mPipHandler.end();
if (mLeftoversHandler != null) {
mLeftoversHandler.mergeAnimation(
- transition, info, t, mergeTarget, finishCallback);
+ transition, info, startT, finishT, mergeTarget, finishCallback);
}
return;
case TYPE_UNFOLD:
- mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mUnfoldHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+ finishCallback);
return;
case TYPE_OPEN_IN_DESKTOP:
mDesktopTasksController.mergeAnimation(
- transition, info, t, mergeTarget, finishCallback);
+ transition, info, startT, finishT, mergeTarget, finishCallback);
return;
default:
throw new IllegalStateException("Playing a default mixed transition with unknown or"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index ac6e4c5cd69e..28bba2e5e731 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -708,7 +708,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ArrayList<Animator> anims = mAnimations.get(mergeTarget);
if (anims == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 209fc39b096a..ec737389c351 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -96,7 +96,9 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Merging registered One-shot remote"
+ " transition %s for (#%d).", mRemote, info.getDebugId());
@@ -111,7 +113,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
// process won't be cleared if the remote applied it. We don't actually know if the
// remote applied the transaction, but applying twice will break surfaceflinger
// so just assume the worst-case and clear the local transaction.
- t.clear();
+ startT.clear();
mMainExecutor.execute(() -> {
finishCallback.onTransitionFinished(wct);
});
@@ -121,8 +123,8 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
// If the remote is actually in the same process, then make a copy of parameters since
// remote impls assume that they have to clean-up native references.
final SurfaceControl.Transaction remoteT =
- RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition());
- final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ RemoteTransitionHandler.copyIfLocal(startT, mRemote.getRemoteTransition());
+ final TransitionInfo remoteInfo = remoteT == startT ? info : info.localRemoteCopy();
mRemote.getRemoteTransition().mergeAnimation(
transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 1847af07f275..f40dc8ad93b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -193,21 +193,24 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
@Override
void mergeAnimation(
@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
switch (mType) {
case TYPE_RECENTS_DURING_DESKTOP:
- mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mLeftoversHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+ finishCallback);
return;
case TYPE_RECENTS_DURING_KEYGUARD:
if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
- handoverTransitionLeashes(mInfo, info, t, mFinishT);
+ handoverTransitionLeashes(mInfo, info, startT, finishT);
if (animateKeyguard(
- this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) {
+ this, info, startT, finishT, mFinishCB, mKeyguardHandler,
+ mPipHandler)) {
finishCallback.onTransitionFinished(null);
}
}
- mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ mLeftoversHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
finishCallback);
return;
case TYPE_RECENTS_DURING_SPLIT:
@@ -216,7 +219,8 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
// another pair.
mAnimType = DefaultMixedHandler.MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
}
- mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mLeftoversHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+ finishCallback);
return;
default:
throw new IllegalStateException("Playing a Recents mixed transition with unknown or"
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 dec28fefd789..c4a410b0e28a 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
@@ -211,7 +211,9 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget);
if (remoteTransition == null) return;
@@ -230,7 +232,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
// process won't be cleared if the remote applied it. We don't actually know if the
// remote applied the transaction, but applying twice will break surfaceflinger
// so just assume the worst-case and clear the local transaction.
- t.clear();
+ startT.clear();
mMainExecutor.execute(() -> {
if (!mRequestedRemotes.containsKey(mergeTarget)) {
Log.e(TAG, "Merged transition finished after it's mergeTarget (the "
@@ -245,8 +247,8 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
try {
// If the remote is actually in the same process, then make a copy of parameters since
// remote impls assume that they have to clean-up native references.
- final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
- final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ final SurfaceControl.Transaction remoteT = copyIfLocal(startT, remote);
+ final TransitionInfo remoteInfo = remoteT == startT ? info : info.localRemoteCopy();
remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
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 b83b7e2f07a3..72cbc4702ac8 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
@@ -922,7 +922,7 @@ public class Transitions implements RemoteCallable<Transitions>,
+ " %s is still animating. Notify the animating transition"
+ " in case they can be merged", ready, playing);
mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
- playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
+ playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT, ready.mFinishT,
playing.mToken, (wct) -> onMerged(playingToken, readyToken));
}
@@ -1356,7 +1356,7 @@ public class Transitions implements RemoteCallable<Transitions>,
// fast-forward.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
+ " into %s via a SLEEP proxy", nextSync, playing);
- playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT,
+ playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT, dummyT,
playing.mToken, (wct) -> {});
// it's possible to complete immediately. If that happens, just repeat the signal
// loop until we either finish everything or start playing an animation that isn't
@@ -1404,7 +1404,9 @@ public class Transitions implements RemoteCallable<Transitions>,
* @param finishTransaction the transaction given to the handler to be applied after the
* transition animation. Unlike startTransaction, the handler is NOT
* expected to apply this transaction. The Transition system will
- * apply it when finishCallback is called.
+ * apply it when finishCallback is called. If additional transitions
+ * are merged, then the finish transactions for those transitions
+ * will be applied after this transaction.
* @param finishCallback Call this when finished. This MUST be called on main thread.
* @return true if transition was handled, false if not (falls-back to default).
*/
@@ -1414,6 +1416,17 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull TransitionFinishCallback finishCallback);
/**
+ * See {@link #mergeAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction, SurfaceControl.Transaction, IBinder, TransitionFinishCallback)}
+ *
+ * This deprecated method header is provided until downstream implementation can migrate to
+ * the call that takes both start & finish transactions.
+ */
+ @Deprecated
+ default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback) { }
+
+ /**
* Attempts to merge a different transition's animation into an animation that this handler
* is currently playing. If a merge is not possible/supported, this should be a no-op.
*
@@ -1430,14 +1443,25 @@ public class Transitions implements RemoteCallable<Transitions>,
*
* @param transition This is the transition that wants to be merged.
* @param info Information about what is changing in the transition.
- * @param t Contains surface changes that resulted from the transition.
+ * @param startTransaction The start transaction containing surface changes that resulted
+ * from the incoming transition. This should be applied by this
+ * active handler only if it chooses to merge the transition.
+ * @param finishTransaction The finish transaction for the incoming transition. Unlike
+ * startTransaction, the handler is NOT expected to apply this
+ * transaction. If the transition is merged, the Transition system
+ * will apply after finishCallback is called following the finish
+ * transaction provided in `#startAnimation()`.
* @param mergeTarget This is the transition that we are attempting to merge with (ie. the
* one this handler is currently already animating).
* @param finishCallback Call this if merged. This MUST be called on main thread.
*/
default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
- @NonNull TransitionFinishCallback finishCallback) { }
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback) {
+ // Call the legacy implementation by default
+ mergeAnimation(transition, info, startTransaction, mergeTarget, finishCallback);
+ }
/**
* Checks whether this handler is capable of taking over a transition matching `info`.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 3e0e15afc53a..7fd19a7d2a88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -225,7 +225,9 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull TransitionFinishCallback finishCallback) {
if (info.getType() != TRANSIT_CHANGE) {
return;
@@ -246,7 +248,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
}
}
// Apply changes happening during the unfold animation immediately
- t.apply();
+ startT.apply();
finishCallback.onTransitionFinished(null);
if (getDefaultDisplayChange(info) == DefaultDisplayChange.DEFAULT_DISPLAY_FOLD) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 195e8195089f..6a2a7b6becdc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -50,7 +50,6 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
@@ -1654,9 +1653,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
if (mDesktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)) {
return false;
}
- if (isPartOfDefaultHomePackage(taskInfo)) {
- return false;
- }
final boolean isOnLargeScreen = taskInfo.getConfiguration().smallestScreenWidthDp
>= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
if (!DesktopModeStatus.canEnterDesktopMode(mContext)
@@ -1672,14 +1668,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
&& !taskInfo.configuration.windowConfiguration.isAlwaysOnTop();
}
- private boolean isPartOfDefaultHomePackage(RunningTaskInfo taskInfo) {
- final ComponentName currentDefaultHome =
- mContext.getPackageManager().getHomeActivities(new ArrayList<>());
- return currentDefaultHome != null && taskInfo.baseActivity != null
- && currentDefaultHome.getPackageName()
- .equals(taskInfo.baseActivity.getPackageName());
- }
-
private void createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
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 b6765c477485..c1a6240516c1 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
@@ -1364,7 +1364,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
updateGenericLink();
final boolean supportsMultiInstance = mMultiInstanceHelper
.supportsMultiInstanceSplit(mTaskInfo.baseActivity, mTaskInfo.userId)
- && Flags.enableDesktopWindowingMultiInstanceFeatures();
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES.isTrue();
final boolean shouldShowManageWindowsButton = supportsMultiInstance
&& mMinimumInstancesFound;
final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
index 1bc48f89ea6d..801048adda4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
@@ -153,9 +153,7 @@ class WindowDecorTaskResourceLoader(
private fun loadAppResources(taskInfo: RunningTaskInfo): AppResources {
Trace.beginSection("$TAG#loadAppResources")
try {
- val pm = checkNotNull(userProfilesContexts[taskInfo.userId]?.packageManager) {
- "Could not get context for user ${taskInfo.userId}"
- }
+ val pm = userProfilesContexts.getOrCreate(taskInfo.userId).packageManager
val activityInfo = getActivityInfo(taskInfo, pm)
val appName = pm.getApplicationLabel(activityInfo.applicationInfo)
val appIconDrawable = iconProvider.getIcon(activityInfo)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index b5b7847e205d..80e4c47a5f68 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -51,6 +52,7 @@ import org.junit.runners.Parameterized
* apps are running before setup
* ```
*/
+@FlakyTest(bugId = 391734110)
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
index 40b685c243b4..4972fa907ce7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
@@ -23,6 +23,9 @@ import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.display.DisplayManager;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableContext;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -31,6 +34,8 @@ import com.android.internal.protolog.ProtoLog;
import org.junit.After;
import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
import org.mockito.MockitoAnnotations;
/**
@@ -38,6 +43,16 @@ import org.mockito.MockitoAnnotations;
*/
public abstract class ShellTestCase {
+ @ClassRule
+ public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
+
protected TestableContext mContext;
private PackageManager mPm;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index bba9418db66a..94dc774a6737 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -41,7 +41,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.window.TransitionInfo;
@@ -55,7 +54,6 @@ import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -73,9 +71,6 @@ import java.util.Arrays;
@RunWith(TestParameterInjector.class)
public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase {
- @Rule
- public SetFlagsRule mRule = new SetFlagsRule();
-
@Before
public void setup() {
super.setUp();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index 39d55079ca3a..9f29ef71930a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -34,7 +34,6 @@ import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -46,7 +45,6 @@ import com.android.window.flags.Flags;
import com.android.wm.shell.transition.TransitionInfoBuilder;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -64,9 +62,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
private static final Rect EMBEDDED_LEFT_BOUNDS = new Rect(0, 0, 500, 500);
private static final Rect EMBEDDED_RIGHT_BOUNDS = new Rect(500, 0, 1000, 500);
- @Rule
- public SetFlagsRule mRule = new SetFlagsRule();
-
@Before
public void setup() {
super.setUp();
@@ -276,7 +271,9 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
mController.startAnimation(mTransition, info, mStartTransaction,
mFinishTransaction, mFinishCallback);
verify(mFinishCallback, never()).onTransitionFinished(any());
- mController.mergeAnimation(mTransition, info, new SurfaceControl.Transaction(),
+ mController.mergeAnimation(mTransition, info,
+ new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(),
mTransition, (wct) -> {});
verify(mFinishCallback).onTransitionFinished(any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index bbdb90f0a37c..05750a54f566 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -60,7 +60,6 @@ import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.IRemoteAnimationRunner;
@@ -91,7 +90,6 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -152,9 +150,6 @@ public class BackAnimationControllerTest extends ShellTestCase {
private BackAnimationController.BackTransitionHandler mBackTransitionHandler;
- @Rule
- public SetFlagsRule mSetflagsRule = new SetFlagsRule();
-
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -671,7 +666,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
Transitions.TransitionFinishCallback mergeCallback =
mock(Transitions.TransitionFinishCallback.class);
mBackTransitionHandler.mergeAnimation(
- mock(IBinder.class), tInfo2, st, mock(IBinder.class), mergeCallback);
+ mock(IBinder.class), tInfo2, st, ft, mock(IBinder.class), mergeCallback);
mBackTransitionHandler.onAnimationFinished();
verify(callback).onTransitionFinished(any());
verify(mergeCallback).onTransitionFinished(any());
@@ -706,7 +701,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
mBackTransitionHandler.mClosePrepareTransition = mock(IBinder.class);
mergeCallback = mock(Transitions.TransitionFinishCallback.class);
mBackTransitionHandler.mergeAnimation(mBackTransitionHandler.mClosePrepareTransition,
- tInfo2, st, mock(IBinder.class), mergeCallback);
+ tInfo2, st, ft, mock(IBinder.class), mergeCallback);
assertTrue("Change should be consumed", tInfo2.getChanges().isEmpty());
verify(callback).onTransitionFinished(any());
}
@@ -752,7 +747,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
final TransitionInfo closeInfo = createTransitionInfo(TRANSIT_CLOSE, close);
Transitions.TransitionFinishCallback mergeCallback =
mock(Transitions.TransitionFinishCallback.class);
- mBackTransitionHandler.mergeAnimation(mock(IBinder.class), closeInfo, ft,
+ mBackTransitionHandler.mergeAnimation(mock(IBinder.class), closeInfo, st, ft,
mock(IBinder.class), mergeCallback);
verify(callback2).onTransitionFinished(any());
verify(mergeCallback, never()).onTransitionFinished(any());
@@ -771,7 +766,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
openTaskId2, TRANSIT_OPEN, FLAG_MOVED_TO_TOP);
final TransitionInfo openInfo = createTransitionInfo(TRANSIT_OPEN, open2, close);
mergeCallback = mock(Transitions.TransitionFinishCallback.class);
- mBackTransitionHandler.mergeAnimation(mock(IBinder.class), openInfo, ft,
+ mBackTransitionHandler.mergeAnimation(mock(IBinder.class), openInfo, st, ft,
mock(IBinder.class), mergeCallback);
verify(callback3).onTransitionFinished(any());
verify(mergeCallback, never()).onTransitionFinished(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 6d7a18d7fca4..2ef6c558b0b5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -32,6 +32,8 @@ import android.window.BackProgressAnimator;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.ShellTestCase;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,7 +44,7 @@ import java.util.concurrent.TimeUnit;
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
-public class BackProgressAnimatorTest {
+public class BackProgressAnimatorTest extends ShellTestCase {
private BackProgressAnimator mProgressAnimator;
private BackEvent mReceivedBackEvent;
private float mTargetProgress = 0.5f;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
index f8eb50b978a5..622e4cbf5ece 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
@@ -38,6 +38,7 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.transition.TransitionInfoBuilder;
import org.junit.Before;
@@ -49,7 +50,7 @@ import org.mockito.MockitoAnnotations;
* Tests of {@link BubblesTransitionObserver}.
*/
@SmallTest
-public class BubblesTransitionObserverTest {
+public class BubblesTransitionObserverTest extends ShellTestCase {
@Mock
private BubbleController mBubbleController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
index f8ee300e411c..3323740697f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
@@ -29,6 +29,7 @@ import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
@@ -41,7 +42,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class DevicePostureControllerTest {
+public class DevicePostureControllerTest extends ShellTestCase {
@Mock
private Context mContext;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 6f3a3ec4fd20..ee9d17706372 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -39,8 +39,6 @@ import android.graphics.Point;
import android.os.Looper;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.IWindowManager;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
@@ -55,7 +53,6 @@ import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -70,9 +67,6 @@ import java.util.concurrent.Executor;
*/
@SmallTest
public class DisplayImeControllerTest extends ShellTestCase {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Mock
private SurfaceControl.Transaction mT;
@Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt
index ef0b8ab14c81..56d401779654 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt
@@ -69,6 +69,7 @@ class UserProfileContextsTest : ShellTestCase() {
}
.whenever(baseContext)
.createContextAsUser(any<UserHandle>(), anyInt())
+ doReturn(DEFAULT_USER).whenever(baseContext).userId
// Define users and profiles
val currentUser = ActivityManager.getCurrentUser()
whenever(userManager.getProfiles(eq(currentUser)))
@@ -136,6 +137,25 @@ class UserProfileContextsTest : ShellTestCase() {
assertThat(userProfilesContexts[SECOND_PROFILE]?.userId).isEqualTo(SECOND_PROFILE)
}
+ @Test
+ fun onUserProfilesChanged_keepDefaultUser() {
+ val userChangeListener = retrieveUserChangeListener()
+ val newUserContext = createContextForUser(SECOND_USER)
+
+ userChangeListener.onUserChanged(SECOND_USER, newUserContext)
+ userChangeListener.onUserProfilesChanged(SECOND_PROFILES)
+
+ assertThat(userProfilesContexts[DEFAULT_USER]).isEqualTo(baseContext)
+ }
+
+ @Test
+ fun getOrCreate_newUser_shouldCreateTheUser() {
+ val newContext = userProfilesContexts.getOrCreate(SECOND_USER)
+
+ assertThat(newContext).isNotNull()
+ assertThat(userProfilesContexts[SECOND_USER]).isEqualTo(newContext)
+ }
+
private fun retrieveUserChangeListener(): UserChangeListener {
val captor = argumentCaptor<UserChangeListener>()
@@ -155,6 +175,7 @@ class UserProfileContextsTest : ShellTestCase() {
const val MAIN_PROFILE = 11
const val SECOND_PROFILE = 15
const val SECOND_PROFILE_2 = 17
+ const val DEFAULT_USER = 25
val SECOND_PROFILES =
listOf(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java
index 5a49d01f2991..979cee9d63c2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java
@@ -16,24 +16,10 @@
package com.android.wm.shell.compatui;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.SetFlagsRule;
-
import com.android.wm.shell.ShellTestCase;
-import org.junit.Rule;
-
/**
* Base class for CompatUI tests.
*/
public class CompatUIShellTestCase extends ShellTestCase {
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 61b6d803c8be..010474e42195 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -38,6 +38,7 @@ import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
@@ -394,8 +395,8 @@ public class CompatUIWindowManagerTest extends CompatUIShellTestCase {
@Test
@RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @EnableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON)
public void testShouldShowSizeCompatRestartButton() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
doReturn(85).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
index 319122d1e051..d3a2c9a411ef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
@@ -18,7 +18,6 @@ package com.android.wm.shell.compatui.impl
import android.graphics.Point
-import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
@@ -29,7 +28,6 @@ import com.android.wm.shell.compatui.api.CompatUISpec
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,9 +43,6 @@ class DefaultCompatUIRepositoryTest {
lateinit var repository: CompatUIRepository
- @get:Rule
- val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
-
@Before
fun setUp() {
repository = DefaultCompatUIRepository()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
index 78bb721d1028..008c499cb88e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
@@ -20,7 +20,6 @@ import android.graphics.Point
import android.graphics.Rect
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
@@ -36,14 +35,12 @@ import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.TransitionObserverInputBuilder
import com.android.wm.shell.util.executeTransitionObserverTest
import java.util.function.Consumer
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
-import org.mockito.kotlin.times
import org.mockito.kotlin.verify
/**
@@ -56,9 +53,6 @@ import org.mockito.kotlin.verify
@SmallTest
class LetterboxTransitionObserverTest : ShellTestCase() {
- @get:Rule
- val setFlagsRule: SetFlagsRule = SetFlagsRule()
-
@Test
@DisableFlags(Flags.FLAG_APP_COMPAT_REFACTORING)
fun `when initialized and flag disabled the observer is not registered`() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 957fdf995776..09ffd946ea19 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -25,7 +25,6 @@ import android.graphics.Rect
import android.os.Binder
import android.os.UserManager
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.window.WindowContainerTransaction
@@ -62,7 +61,6 @@ import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -89,8 +87,6 @@ import org.mockito.quality.Strictness
@ExperimentalCoroutinesApi
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE)
class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@Mock lateinit var transitions: Transitions
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
index fae7363e0676..0d5741fccbcc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -23,7 +23,6 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.ContentResolver
import android.os.Binder
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
import android.testing.AndroidTestingRunner
@@ -51,7 +50,6 @@ import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.isNull
@@ -74,9 +72,6 @@ import org.mockito.quality.Strictness
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopDisplayEventHandlerTest : ShellTestCase() {
-
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var transitions: Transitions
@Mock lateinit var displayController: DisplayController
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
index 47d133b974e6..006c3cae121c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
@@ -23,7 +23,6 @@ import android.os.Binder
import android.os.IBinder
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display.DEFAULT_DISPLAY
@@ -73,7 +72,6 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
class DesktopImmersiveControllerTest : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
@JvmField @Rule val animatorTestRule = AnimatorTestRule(this)
@Mock private lateinit var mockTransitions: Transitions
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index f61ea4a194d6..f48bc99a8cfa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -27,7 +27,6 @@ import android.os.Handler
import android.os.IBinder
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
@@ -57,7 +56,6 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -82,8 +80,6 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
class DesktopMixedTransitionHandlerTest : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
@Mock lateinit var transitions: Transitions
@Mock lateinit var userRepositories: DesktopUserRepositories
@Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index bddc06204a52..8a5acfa70f50 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -19,7 +19,6 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
import android.graphics.Rect
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker
@@ -65,15 +64,13 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
val displayLayout = mock<DisplayLayout>()
@JvmField
- @Rule(order = 0)
+ @Rule()
val extendedMockitoRule =
ExtendedMockitoRule.Builder(this)
.mockStatic(FrameworkStatsLog::class.java)
.mockStatic(EventLogTags::class.java)
.build()!!
- @JvmField @Rule(order = 1) val setFlagsRule = SetFlagsRule()
-
@Before
fun setUp() {
doReturn(displayLayout).whenever(displayController).getDisplayLayout(anyInt())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index 016e04039b12..470c110fd49b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -24,7 +24,6 @@ import android.hardware.input.InputManager
import android.hardware.input.InputManager.KeyGestureEventHandler
import android.hardware.input.KeyGestureEvent
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.KeyEvent
@@ -64,7 +63,6 @@ import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.anyInt
@@ -87,8 +85,6 @@ import org.mockito.quality.Strictness
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
private val focusTransitionObserver = mock<FocusTransitionObserver>()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index f5c93ee8ffe4..6a343c56d364 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -20,7 +20,6 @@ import android.graphics.Rect
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
-import android.platform.test.flag.junit.SetFlagsRule
import android.util.ArraySet
import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.INVALID_DISPLAY
@@ -28,6 +27,7 @@ import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.ShellExecutor
@@ -36,6 +36,7 @@ import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.fail
+import kotlin.test.assertEquals
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,7 +48,6 @@ import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
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
@@ -71,8 +71,6 @@ import platform.test.runner.parameterized.Parameters
@ExperimentalCoroutinesApi
class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule(flags)
-
private lateinit var repo: DesktopRepository
private lateinit var shellInit: ShellInit
private lateinit var datastoreScope: CoroutineScope
@@ -1080,13 +1078,37 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- val tasksBeforeRemoval = repo.removeDesk(displayId = DEFAULT_DISPLAY)
+ val tasksBeforeRemoval = repo.removeDesk(deskId = DEFAULT_DISPLAY)
assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder()
assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty()
}
@Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleDesks_active_removes() {
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+ repo.removeDesk(deskId = 3)
+
+ assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(3)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleDesks_inactive_removes() {
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+ repo.removeDesk(deskId = 2)
+
+ assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(2)
+ }
+
+ @Test
fun getTaskInFullImmersiveState_byDisplay() {
repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -1168,6 +1190,26 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
assertThat(repo.getActiveTaskIdsInDesk(999)).contains(6)
}
+ @Test
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun getDisplayForDesk() {
+ repo.addDesk(SECOND_DISPLAY, SECOND_DISPLAY)
+
+ assertEquals(SECOND_DISPLAY, repo.getDisplayForDesk(deskId = SECOND_DISPLAY))
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun getDisplayForDesk_multipleDesks() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 7)
+ repo.addDesk(SECOND_DISPLAY, deskId = 8)
+ repo.addDesk(SECOND_DISPLAY, deskId = 9)
+
+ assertEquals(DEFAULT_DISPLAY, repo.getDisplayForDesk(deskId = 7))
+ assertEquals(SECOND_DISPLAY, repo.getDisplayForDesk(deskId = 8))
+ }
+
class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
index 12c7ff61399f..50590f021a2a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
@@ -18,7 +18,6 @@ package com.android.wm.shell.desktopmode
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
@@ -26,7 +25,6 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -44,8 +42,6 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
class DesktopTaskChangeListenerTest : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
private lateinit var desktopTaskChangeListener: DesktopTaskChangeListener
private val desktopUserRepositories = mock<DesktopUserRepositories>()
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 a139f1670181..8e7545c2a99c 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
@@ -35,6 +35,7 @@ import android.content.pm.ActivityInfo.CONFIG_DENSITY
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+import android.content.pm.PackageManager
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.content.res.Resources
@@ -49,7 +50,6 @@ import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
-import android.platform.test.flag.junit.SetFlagsRule
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.Gravity
@@ -118,7 +118,9 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCR
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DeskTransition
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
@@ -165,7 +167,6 @@ import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -181,6 +182,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
@@ -201,8 +203,6 @@ import platform.test.runner.parameterized.Parameters
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule(flags)
-
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var shellCommandHandler: ShellCommandHandler
@Mock lateinit var shellController: ShellController
@@ -254,6 +254,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
private lateinit var overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver
@Mock private lateinit var desksOrganizer: DesksOrganizer
@Mock private lateinit var userProfileContexts: UserProfileContexts
+ @Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -351,6 +352,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
.thenReturn(ExitResult.NoExit)
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
whenever(userProfileContexts[anyInt()]).thenReturn(context)
+ whenever(userProfileContexts.getOrCreate(anyInt())).thenReturn(context)
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -408,6 +410,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
Optional.of(bubbleController),
overviewToDesktopTransitionObserver,
desksOrganizer,
+ desksTransitionsObserver,
userProfileContexts,
desktopModeCompatPolicy,
)
@@ -538,6 +541,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -726,6 +730,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskVisible(task1)
@@ -764,7 +769,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
+ fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersOnlyFreeformTasks() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -781,6 +787,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
wct.assertReorderAt(index = 2, task2)
}
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersAll() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() {
@@ -796,7 +820,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
+ fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_addsDesktopWallpaper() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct =
@@ -805,6 +831,16 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_reordersDesktopWallpaper() {
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -828,6 +864,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
@@ -872,6 +909,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val minimizedTask = setUpFreeformTask()
@@ -1337,6 +1375,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
@@ -1422,6 +1461,41 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
.isEqualTo(WINDOWING_MODE_FREEFORM)
}
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveRunningTaskToDesktop_defaultHomePackageWithDisplay_doesNothing() {
+ val packageManager: PackageManager = org.mockito.kotlin.mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }
+ mContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ verifyEnterDesktopWCTNotExecuted()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveRunningTaskToDesktop_defaultHomePackageWithoutDisplay_doesNothing() {
+ val packageManager: PackageManager = org.mockito.kotlin.mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }
+ mContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() {
@@ -1484,6 +1558,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask = setUpFreeformTask()
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
@@ -1586,6 +1661,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
@@ -2606,6 +2682,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -2737,6 +2814,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask1 = setUpFreeformTask()
val freeformTask2 = createFreeformTask()
@@ -2771,7 +2849,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createFreeformTask()
+
val result = controller.handleRequest(Binder(), createTransition(task))
assertNotNull(result, "Should handle request")
@@ -2799,6 +2879,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
// Second display task
createFreeformTask(displayId = SECOND_DISPLAY)
@@ -3052,6 +3133,46 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
.isEqualTo(WINDOWING_MODE_FREEFORM)
}
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_defaultHomePackageWithDisplay_returnSwitchToFullscreenWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ val packageManager: PackageManager = org.mockito.kotlin.mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }
+ mContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_defaultHomePackageWithoutDisplay_returnSwitchToFreeformWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ val packageManager: PackageManager = org.mockito.kotlin.mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }
+ mContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
@Test
fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT_enforcedDesktop() {
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
@@ -3503,13 +3624,14 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun removeDesktop_multipleTasks_removesAll() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleTasks_removesAll() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
val task3 = setUpFreeformTask()
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
- controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+ controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
val wct = getLatestWct(TRANSIT_CLOSE)
assertThat(wct.hierarchyOps).hasSize(3)
@@ -3520,14 +3642,15 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun removeDesktop_multipleTasksWithBackgroundTask_removesAll() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleTasksWithBackgroundTask_removesAll() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
val task3 = setUpFreeformTask()
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
whenever(shellTaskOrganizer.getRunningTaskInfo(task3.taskId)).thenReturn(null)
- controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+ controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
val wct = getLatestWct(TRANSIT_CLOSE)
assertThat(wct.hierarchyOps).hasSize(2)
@@ -3537,6 +3660,30 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun removeDesk_multipleDesks_addsPendingTransition() {
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_CLOSE), any(), anyOrNull()))
+ .thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 2)
+
+ controller.removeDesk(deskId = 2)
+
+ verify(desksOrganizer).removeDesk(any(), eq(2))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.RemoveDesk &&
+ this.token == transition &&
+ this.deskId == 2
+ }
+ )
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
val spyController = spy(controller)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 554b09f130bd..d33209dbc30e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -24,7 +24,6 @@ import android.os.IBinder
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
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.SurfaceControl
@@ -66,7 +65,6 @@ import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.setMain
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
@@ -88,8 +86,6 @@ import org.mockito.quality.Strictness
@ExperimentalCoroutinesApi
class DesktopTasksLimiterTest : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@Mock lateinit var transitions: Transitions
@Mock lateinit var interactionJankMonitor: InteractionJankMonitor
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index ca1e3edb3fd3..c29edece5537 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -25,7 +25,6 @@ import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
@@ -48,11 +47,13 @@ import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
+import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
@@ -77,9 +78,6 @@ import org.mockito.kotlin.whenever
* Build/Install/Run: atest WMShellUnitTests:DesktopTasksTransitionObserverTest
*/
class DesktopTasksTransitionObserverTest {
-
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
@JvmField
@Rule
val extendedMockitoRule =
@@ -96,6 +94,7 @@ class DesktopTasksTransitionObserverTest {
private val backAnimationController = mock<BackAnimationController>()
private val desktopWallpaperActivityTokenProvider =
mock<DesktopWallpaperActivityTokenProvider>()
+ private val desksTransitionObserver = mock<DesksTransitionObserver>()
private val wallpaperToken = MockToken().token()
private lateinit var transitionObserver: DesktopTasksTransitionObserver
@@ -119,6 +118,7 @@ class DesktopTasksTransitionObserverTest {
mixedHandler,
backAnimationController,
desktopWallpaperActivityTokenProvider,
+ desksTransitionObserver,
shellInit,
)
}
@@ -415,6 +415,21 @@ class DesktopTasksTransitionObserverTest {
verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
}
+ @Test
+ fun onTransitionReady_forwardsToDesksTransitionObserver() {
+ val transition = Binder()
+ val info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0)
+
+ transitionObserver.onTransitionReady(
+ transition = transition,
+ info = info,
+ StubTransaction(),
+ StubTransaction(),
+ )
+
+ verify(desksTransitionObserver).onTransitionReady(transition, info)
+ }
+
private fun createBackNavigationTransition(
task: RunningTaskInfo?,
type: Int = TRANSIT_TO_BACK,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
index b9e307fa5973..83e48728c4f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
@@ -21,7 +21,6 @@ import android.content.pm.UserInfo
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
@@ -44,7 +43,6 @@ import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.spy
@@ -56,8 +54,6 @@ import org.mockito.quality.Strictness
@RunWith(AndroidTestingRunner::class)
@ExperimentalCoroutinesApi
class DesktopUserRepositoriesTest : ShellTestCase() {
- @get:Rule val setFlagsRule = SetFlagsRule()
-
private lateinit var userRepositories: DesktopUserRepositories
private lateinit var shellInit: ShellInit
private lateinit var datastoreScope: CoroutineScope
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index bf9cf00050dc..33dc1aadf548 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -284,6 +284,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
cancelToken,
TransitionInfo(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, 0),
mock<SurfaceControl.Transaction>(),
+ mock<SurfaceControl.Transaction>(),
startToken,
mock<Transitions.TransitionFinishCallback>(),
)
@@ -385,21 +386,23 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
@Test
fun mergeAnimation_otherTransition_doesNotMerge() {
- val transaction = mock<SurfaceControl.Transaction>()
+ val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+ val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
val finishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
startDrag(defaultHandler, task)
defaultHandler.mergeAnimation(
- transition = mock(),
+ transition = mock<IBinder>(),
info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = task),
- t = transaction,
- mergeTarget = mock(),
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
+ mergeTarget = mock<IBinder>(),
finishCallback = finishCallback,
)
// Should NOT have any transaction changes
- verifyZeroInteractions(transaction)
+ verifyZeroInteractions(mergedStartTransaction)
// Should NOT merge animation
verify(finishCallback, never()).onTransitionFinished(any())
}
@@ -408,6 +411,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
fun mergeAnimation_endTransition_mergesAnimation() {
val playingFinishTransaction = mock<SurfaceControl.Transaction>()
val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+ val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
val finishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
val startTransition =
@@ -415,13 +419,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
defaultHandler.onTaskResizeAnimationListener = mock()
defaultHandler.mergeAnimation(
- transition = mock(),
+ transition = mock<IBinder>(),
info =
createTransitionInfo(
type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
draggedTask = task,
),
- t = mergedStartTransaction,
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
mergeTarget = startTransition,
finishCallback = finishCallback,
)
@@ -440,6 +445,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF())
val playingFinishTransaction = mock<SurfaceControl.Transaction>()
val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+ val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
val finishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
val startTransition =
@@ -447,13 +453,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
springHandler.onTaskResizeAnimationListener = mock()
springHandler.mergeAnimation(
- transition = mock(),
+ transition = mock<IBinder>(),
info =
createTransitionInfo(
type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
draggedTask = task,
),
- t = mergedStartTransaction,
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
mergeTarget = startTransition,
finishCallback = finishCallback,
)
@@ -564,7 +571,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
draggedTask = task,
),
- t = mock<SurfaceControl.Transaction>(),
+ startT = mock<SurfaceControl.Transaction>(),
+ finishT = mock<SurfaceControl.Transaction>(),
mergeTarget = startTransition,
finishCallback = mock<Transitions.TransitionFinishCallback>(),
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 1160a9286572..86e8142b1aaa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -19,7 +19,6 @@ package com.android.wm.shell.desktopmode.education
import android.os.SystemProperties
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.TestableContext
import androidx.test.filters.SmallTest
@@ -75,7 +74,6 @@ class AppHandleEducationControllerTest : ShellTestCase() {
.mockStatic(DesktopModeStatus::class.java)
.mockStatic(SystemProperties::class.java)
.build()!!
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
private lateinit var educationController: AppHandleEducationController
private lateinit var testableContext: TestableContext
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
new file mode 100644
index 000000000000..bfbaa84e9312
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.desktopmode.multidesks
+
+import android.os.Binder
+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.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Tests for [DesksTransitionObserver].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesksTransitionObserverTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesksTransitionObserverTest : ShellTestCase() {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private lateinit var desktopUserRepositories: DesktopUserRepositories
+ private lateinit var observer: DesksTransitionObserver
+
+ private val repository: DesktopRepository
+ get() = desktopUserRepositories.current
+
+ @Before
+ fun setUp() {
+ desktopUserRepositories =
+ DesktopUserRepositories(
+ context,
+ ShellInit(TestShellExecutor()),
+ /* shellController= */ mock(),
+ /* persistentRepository= */ mock(),
+ /* repositoryInitializer= */ mock(),
+ /* mainCoroutineScope= */ mock(),
+ /* userManager= */ mock(),
+ )
+ observer = DesksTransitionObserver(desktopUserRepositories)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_removeDesk_removesFromRepository() {
+ val transition = Binder()
+ val removeTransition =
+ DeskTransition.RemoveDesk(
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = 5,
+ tasks = setOf(10, 11),
+ onDeskRemovedListener = null,
+ )
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(removeTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0),
+ )
+
+ assertThat(repository.getDeskIds(DEFAULT_DISPLAY)).doesNotContain(5)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_removeDesk_invokesOnRemoveListener() {
+ class FakeOnDeskRemovedListener : OnDeskRemovedListener {
+ var lastDeskRemoved: Int? = null
+
+ override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
+ lastDeskRemoved = deskId
+ }
+ }
+ val transition = Binder()
+ val removeListener = FakeOnDeskRemovedListener()
+ val removeTransition =
+ DeskTransition.RemoveDesk(
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = 5,
+ tasks = setOf(10, 11),
+ onDeskRemovedListener = removeListener,
+ )
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(removeTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0),
+ )
+
+ assertThat(removeListener.lastDeskRemoved).isEqualTo(5)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
index 9a8f264e98a4..dd9e6ca0deae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
@@ -19,7 +19,6 @@ package com.android.wm.shell.desktopmode.persistence
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import androidx.test.filters.SmallTest
@@ -42,7 +41,6 @@ import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.spy
@@ -54,8 +52,6 @@ import org.mockito.kotlin.whenever
@ExperimentalCoroutinesApi
class DesktopRepositoryInitializerTest : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
private lateinit var repositoryInitializer: DesktopRepositoryInitializer
private lateinit var shellInit: ShellInit
private lateinit var datastoreScope: CoroutineScope
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 4174bbd89b76..9509aaf20c9b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -35,7 +35,6 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.view.SurfaceControl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -56,7 +55,6 @@ import com.android.wm.shell.windowdecor.WindowDecorViewModel;
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;
@@ -72,9 +70,6 @@ import java.util.Optional;
@RunWith(AndroidJUnit4.class)
public final class FreeformTaskListenerTests extends ShellTestCase {
- @Rule
- public final SetFlagsRule setFlagsRule = new SetFlagsRule();
-
@Mock
private ShellTaskOrganizer mTaskOrganizer;
@Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 5aed4611cdc8..bc918450a3cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -34,7 +34,6 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.view.SurfaceControl;
import android.window.IWindowContainerToken;
import android.window.TransitionInfo;
@@ -43,6 +42,7 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
import com.android.window.flags.Flags;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.FocusTransitionObserver;
@@ -60,9 +60,8 @@ import java.util.Optional;
/** Tests for {@link FreeformTaskTransitionObserver}. */
@SmallTest
-public class FreeformTaskTransitionObserverTest {
+public class FreeformTaskTransitionObserverTest extends ShellTestCase {
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock private ShellInit mShellInit;
@Mock private Transitions mTransitions;
@Mock private DesktopImmersiveController mDesktopImmersiveController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 836f4c24a979..26688236d5be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -37,7 +37,6 @@ import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.RemoteException;
import android.platform.test.annotations.DisableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Rational;
@@ -70,8 +69,6 @@ import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
@@ -88,11 +85,6 @@ import java.util.Optional;
@TestableLooper.RunWithLooper
@DisableFlags(Flags.FLAG_ENABLE_PIP2)
public class PipTaskOrganizerTest extends ShellTestCase {
- @ClassRule
- public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
- @Rule
- public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
-
private PipTaskOrganizer mPipTaskOrganizer;
@Mock private DisplayController mMockDisplayController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 5ef934ce8394..13fce2a27524 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -43,7 +43,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.platform.test.annotations.DisableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -77,8 +76,6 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -95,9 +92,6 @@ import java.util.Set;
@TestableLooper.RunWithLooper
@DisableFlags(Flags.FLAG_ENABLE_PIP2)
public class PipControllerTest extends ShellTestCase {
- @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
- @Rule public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
-
private PipController mPipController;
private ShellInit mShellInit;
private ShellController mShellController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index b11715b669f4..273cb2727508 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -25,7 +25,6 @@ import static org.mockito.Mockito.verify;
import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Size;
@@ -49,7 +48,6 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -68,9 +66,6 @@ import java.util.Optional;
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class PipTouchHandlerTest extends ShellTestCase {
- @Rule
- public SetFlagsRule setFlagsRule = new SetFlagsRule();
-
private static final int INSET = 10;
private static final int PIP_LENGTH = 100;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 542289db6cfc..5028479b6ace 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -63,7 +63,6 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.view.SurfaceControl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -89,7 +88,6 @@ import com.android.wm.shell.sysui.ShellInit;
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;
@@ -129,9 +127,6 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Mock
private DesktopRepository mDesktopRepository;
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
private RecentTasksController mRecentTasksControllerReal;
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
index ab43119b14c0..b50af741b2a6 100644
--- 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
@@ -47,7 +47,6 @@ 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 android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -77,7 +76,6 @@ import com.android.wm.shell.util.StubTransaction;
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;
@@ -115,9 +113,6 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
@Mock private DesktopRepository mDesktopRepository;
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
private RecentTasksController mRecentTasksControllerReal;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
index 99194620c313..769407b370c3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
@@ -26,7 +26,6 @@ import android.content.Intent
import android.os.IBinder
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
import android.view.WindowManager
@@ -42,6 +41,7 @@ import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.TestSyncExecutor
import com.android.wm.shell.common.ShellExecutor
@@ -53,7 +53,6 @@ import com.android.wm.shell.windowdecor.extension.isFullscreen
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -71,9 +70,7 @@ import org.mockito.kotlin.whenever
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class TaskStackTransitionObserverTest {
-
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
+class TaskStackTransitionObserverTest : ShellTestCase() {
@Mock private lateinit var shellInit: ShellInit
@Mock private lateinit var shellTaskOrganizerLazy: Lazy<ShellTaskOrganizer>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index 8c78debdc19f..a8a7be8fe7e3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.shared.desktopmode
import android.content.ComponentName
+import android.content.pm.PackageManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.R
@@ -27,6 +28,9 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
/**
* Tests for [@link DesktopModeCompatPolicy].
@@ -110,4 +114,32 @@ class DesktopModeCompatPolicyTest : CompatUIShellTestCase() {
isTopActivityNoDisplay = true
}))
}
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage() {
+ val packageManager: PackageManager = mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+ mContext.setMockPackageManager(packageManager)
+ assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }))
+ }
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage_notDisplayed() {
+ val packageManager: PackageManager = mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+ mContext.setMockPackageManager(packageManager)
+ assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = true
+ }))
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
new file mode 100644
index 000000000000..4dac99b14aaf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2025 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.shared.desktopmode
+
+import android.content.Context
+import android.content.res.Resources
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES
+import android.window.DesktopModeFlags
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@Presubmit
+@EnableFlags(Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+class DesktopModeStatusTest : ShellTestCase() {
+ @get:Rule
+ val mSetFlagsRule = SetFlagsRule()
+
+ private val mockContext = mock<Context>()
+ private val mockResources = mock<Resources>()
+
+ @Before
+ fun setUp() {
+ doReturn(mockResources).whenever(mockContext).getResources()
+ doReturn(false).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+ doReturn(false).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+ doReturn(context.contentResolver).whenever(mockContext).contentResolver
+ resetDesktopModeFlagsCache()
+ resetEnforceDeviceRestriction()
+ resetFlagOverride()
+ }
+
+ @After
+ fun tearDown() {
+ resetDesktopModeFlagsCache()
+ resetEnforceDeviceRestriction()
+ resetFlagOverride()
+ }
+
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+ )
+ @Test
+ fun canEnterDesktopMode_DWFlagDisabled_configsOff_returnsFalse() {
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+ }
+
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+ )
+ @Test
+ fun canEnterDesktopMode_DWFlagDisabled_configsOn_disableDeviceRestrictions_returnsFalse() {
+ doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+ disableEnforceDeviceRestriction()
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+ }
+
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+ )
+ @Test
+ fun canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_returnsFalse() {
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+ }
+
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+ )
+ @Test
+ fun canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+ setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON)
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ fun canEnterDesktopMode_DWFlagEnabled_configsOff_returnsFalse() {
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ fun canEnterDesktopMode_DWFlagEnabled_configDesktopModeOff_returnsFalse() {
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ fun canEnterDesktopMode_DWFlagEnabled_configDesktopModeOn_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ fun canEnterDesktopMode_DWFlagEnabled_configsOff_disableDeviceRestrictions_returnsTrue() {
+ disableEnforceDeviceRestriction()
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ fun canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+ setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON)
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+ }
+
+ @Test
+ fun isDeviceEligibleForDesktopMode_configDEModeOn_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ fun isDeviceEligibleForDesktopMode_supportFlagOff_returnsFalse() {
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ fun isDeviceEligibleForDesktopMode_supportFlagOn_returnsFalse() {
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ fun isDeviceEligibleForDesktopMode_supportFlagOn_configDevOptModeOn_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
+ }
+
+ private fun resetEnforceDeviceRestriction() {
+ setEnforceDeviceRestriction(true)
+ }
+
+ private fun disableEnforceDeviceRestriction() {
+ setEnforceDeviceRestriction(false)
+ }
+
+ private fun setEnforceDeviceRestriction(value: Boolean) {
+ val field = DesktopModeStatus::class.java.getDeclaredField("ENFORCE_DEVICE_RESTRICTIONS")
+ field.isAccessible = true
+ field.setBoolean(null, value)
+ }
+
+ private fun resetDesktopModeFlagsCache() {
+ val cachedToggleOverride =
+ DesktopModeFlags::class.java.getDeclaredField("sCachedToggleOverride")
+ cachedToggleOverride.isAccessible = true
+ cachedToggleOverride.set(null, null)
+ }
+
+ private fun resetFlagOverride() {
+ Settings.Global.putString(
+ context.contentResolver,
+ DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null
+ )
+ }
+
+ private fun setFlagOverride(override: DesktopModeFlags.ToggleOverride) {
+ Settings.Global.putInt(
+ context.contentResolver,
+ DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.setting
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 6ac34d736f6f..2d454a55a51c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -51,7 +51,6 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.os.Looper;
import android.platform.test.flag.junit.FlagsParameterization;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableLooper;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
@@ -72,7 +71,6 @@ 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.ArgumentCaptor;
@@ -96,9 +94,6 @@ public class TaskViewTest extends ShellTestCase {
return FlagsParameterization.allCombinationsOf(Flags.FLAG_TASK_VIEW_REPOSITORY);
}
- @Rule
- public final SetFlagsRule mSetFlagsRule;
-
@Mock
TaskView.Listener mViewListener;
@Mock
@@ -127,9 +122,7 @@ public class TaskViewTest extends ShellTestCase {
TaskViewTransitions mTaskViewTransitions;
TaskViewTaskController mTaskViewTaskController;
- public TaskViewTest(FlagsParameterization flags) {
- mSetFlagsRule = new SetFlagsRule(flags);
- }
+ public TaskViewTest(FlagsParameterization flags) {}
@Before
public void setUp() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 326f11e300fd..3a455ba6b5df 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -34,7 +34,6 @@ import android.app.ActivityManager;
import android.graphics.Rect;
import android.os.IBinder;
import android.platform.test.flag.junit.FlagsParameterization;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableLooper;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -50,7 +49,6 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -74,9 +72,6 @@ public class TaskViewTransitionsTest extends ShellTestCase {
Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP);
}
- @Rule
- public final SetFlagsRule mSetFlagsRule;
-
@Mock
Transitions mTransitions;
@Mock
@@ -95,9 +90,7 @@ public class TaskViewTransitionsTest extends ShellTestCase {
TaskViewRepository mTaskViewRepository;
TaskViewTransitions mTaskViewTransitions;
- public TaskViewTransitionsTest(FlagsParameterization flags) {
- mSetFlagsRule = new SetFlagsRule(flags);
- }
+ public TaskViewTransitionsTest(FlagsParameterization flags) {}
@Before
public void setUp() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
index e540322a96a1..82392e0e3bc0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -292,6 +292,7 @@ public class DefaultTransitionHandlerTest extends ShellTestCase {
new Binder(),
new TransitionInfoBuilder(TRANSIT_SLEEP, FLAG_SYNC).build(),
MockTransactionPool.create(),
+ MockTransactionPool.create(),
token,
mock(Transitions.TransitionFinishCallback.class));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
index 74c2f0e6753a..96e4f4955dc8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
@@ -35,8 +35,6 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
import android.os.RemoteException;
import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionInfo.TransitionMode;
@@ -50,7 +48,6 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.shared.FocusTransitionListener;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,8 +64,6 @@ public class FocusTransitionObserverTest extends ShellTestCase {
static final int SECONDARY_DISPLAY_ID = 1;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private FocusTransitionListener mListener;
private final TestShellExecutor mShellExecutor = new TestShellExecutor();
private FocusTransitionObserver mFocusTransitionObserver;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 3e53ee5cfb9f..6f28e656d060 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -39,8 +39,6 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionInfo.TransitionMode;
@@ -60,7 +58,6 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,9 +70,6 @@ import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class HomeTransitionObserverTest extends ShellTestCase {
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private final ShellTaskOrganizer mOrganizer = mock(ShellTaskOrganizer.class);
private final TransactionPool mTransactionPool = mock(TransactionPool.class);
private final Context mContext =
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 0a19be4eb959..6f73db0bacc3 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
@@ -74,7 +74,8 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
-import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.util.ArraySet;
import android.util.Pair;
import android.view.Surface;
@@ -117,7 +118,6 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.StubTransaction;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -145,9 +145,6 @@ public class ShellTransitionTests extends ShellTestCase {
private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
- @Rule
- public final SetFlagsRule setFlagsRule = new SetFlagsRule();
-
@Before
public void setUp() {
doAnswer(invocation -> new Binder())
@@ -553,7 +550,8 @@ public class ShellTransitionTests extends ShellTestCase {
}
@Test
- public void testRegisteredRemoteTransitionTakeover() {
+ @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
+ public void testRegisteredRemoteTransitionTakeover_flagDisabled() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -608,7 +606,6 @@ public class ShellTransitionTests extends ShellTestCase {
mMainExecutor.flushAll();
// Takeover shouldn't happen when the flag is disabled.
- setFlagsRule.disableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED);
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
@@ -621,12 +618,69 @@ public class ShellTransitionTests extends ShellTestCase {
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
+ public void testRegisteredRemoteTransitionTakeover_flagEnabled() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IRemoteTransition testRemote = new RemoteTransitionStub() {
+ @Override
+ public void startAnimation(IBinder token, TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+ final Transitions.TransitionHandler takeoverHandler =
+ transitions.getHandlerForTakeover(token, info);
+
+ if (takeoverHandler == null) {
+ finishCallback.onTransitionFinished(null /* wct */, null /* sct */);
+ return;
+ }
+
+ takeoverHandler.takeOverAnimation(token, info, new SurfaceControl.Transaction(),
+ wct -> {
+ try {
+ finishCallback.onTransitionFinished(wct, null /* sct */);
+ } catch (RemoteException e) {
+ // Fail
+ }
+ }, new WindowAnimationState[info.getChanges().size()]);
+ }
+ };
+ final boolean[] takeoverRemoteCalled = new boolean[]{false};
+ IRemoteTransition testTakeoverRemote = new RemoteTransitionStub() {
+ @Override
+ public void startAnimation(IBinder token, TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback) {}
+
+ @Override
+ public void takeOverAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction startTransaction,
+ IRemoteTransitionFinishedCallback finishCallback, WindowAnimationState[] states)
+ throws RemoteException {
+ takeoverRemoteCalled[0] = true;
+ finishCallback.onTransitionFinished(null /* wct */, null /* sct */);
+ }
+ };
+
+ TransitionFilter filter = new TransitionFilter();
+ filter.mRequirements =
+ new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+ filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+
+ transitions.registerRemote(filter, new RemoteTransition(testRemote, "Test"));
+ transitions.registerRemoteForTakeover(
+ filter, new RemoteTransition(testTakeoverRemote, "Test"));
+ mMainExecutor.flushAll();
// Takeover should happen when the flag is enabled.
- setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED);
+ IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.onTransitionReady(transitToken, info, new StubTransaction(),
new StubTransaction());
@@ -634,7 +688,7 @@ public class ShellTransitionTests extends ShellTestCase {
assertTrue(takeoverRemoteCalled[0]);
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
- verify(mOrganizer, times(2)).finishTransition(eq(transitToken), any());
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
}
@Test
@@ -1705,7 +1759,9 @@ public class ShellTransitionTests extends ShellTestCase {
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (mFinishOnSync && info.getType() == TRANSIT_SLEEP) {
for (int i = 0; i < mFinishes.size(); ++i) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index 71af97e5add3..aad18cba4436 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -43,6 +43,7 @@ import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestSyncExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.shared.TransactionPool;
@@ -61,7 +62,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
-public class UnfoldTransitionHandlerTest {
+public class UnfoldTransitionHandlerTest extends ShellTestCase {
private UnfoldTransitionHandler mUnfoldTransitionHandler;
@@ -169,7 +170,8 @@ public class UnfoldTransitionHandlerTest {
// Send fold transition request
TransitionFinishCallback mergeFinishCallback = mock(TransitionFinishCallback.class);
mUnfoldTransitionHandler.mergeAnimation(new Binder(), createFoldTransitionInfo(),
- mock(SurfaceControl.Transaction.class), mTransition, mergeFinishCallback);
+ mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+ mTransition, mergeFinishCallback);
mTestLooper.dispatchAll();
// Verify that fold transition is merged into unfold and that unfold is finished
@@ -387,6 +389,7 @@ public class UnfoldTransitionHandlerTest {
new Binder(),
new TransitionInfoBuilder(TRANSIT_CHANGE, TRANSIT_FLAG_KEYGUARD_GOING_AWAY).build(),
mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
mTransition,
mergeCallback);
verify(finishCallback, never()).onTransitionFinished(any());
@@ -396,6 +399,7 @@ public class UnfoldTransitionHandlerTest {
new Binder(),
new TransitionInfoBuilder(TRANSIT_CHANGE).build(),
mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
mTransition,
mergeCallback);
verify(mergeCallback).onTransitionFinished(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
index cf6c3a5e03a0..257bbb5603a7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
@@ -19,7 +19,6 @@ import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.SurfaceControl
@@ -35,7 +34,6 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystem
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -51,10 +49,6 @@ import org.mockito.kotlin.mock
@RunWith(AndroidTestingRunner::class)
class DesktopHeaderManageWindowsMenuTest : ShellTestCase() {
- @JvmField
- @Rule
- val setFlagsRule: SetFlagsRule = SetFlagsRule()
-
private lateinit var userRepositories: DesktopUserRepositories
private lateinit var menu: DesktopHeaderManageWindowsMenu
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index baccbee0893d..737780ed8024 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -28,6 +28,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_MAIN
+import android.content.pm.PackageManager
import android.graphics.Rect
import android.graphics.Region
import android.hardware.display.DisplayManager
@@ -96,7 +97,6 @@ import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
import java.util.function.Consumer
-
/**
* Tests of [DesktopModeWindowDecorViewModel]
* Usage: atest WMShellUnitTests:DesktopModeWindowDecorViewModelTests
@@ -307,6 +307,23 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun testDecorationIsNotCreatedForDefaultHomePackage() {
+ val packageManager: PackageManager = org.mockito.kotlin.mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }
+ mContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+ onTaskOpening(task)
+
+ assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
fun testInsetsStateChanged_notifiesAllDecorsInDisplay() {
val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 1)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index c5c827467c75..7468c54321a0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -25,9 +25,6 @@ import android.graphics.Rect
import android.hardware.input.InputManager
import android.os.Handler
import android.os.UserHandle
-import android.platform.test.flag.junit.CheckFlagsRule
-import android.platform.test.flag.junit.DeviceFlagsValueProvider
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.TestableContext
import android.util.SparseArray
import android.view.Choreographer
@@ -84,7 +81,6 @@ import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder
import org.junit.After
-import org.junit.Rule
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
import org.mockito.kotlin.any
@@ -105,14 +101,6 @@ import kotlinx.coroutines.MainCoroutineDispatcher
*/
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
- @JvmField
- @Rule
- val setFlagsRule = SetFlagsRule()
-
- @JvmField
- @Rule
- val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
-
private val mockDesktopModeWindowDecorFactory = mock<DesktopModeWindowDecoration.Factory>()
protected val mockMainHandler = mock<Handler>()
protected val mockMainChoreographer = mock<Choreographer>()
@@ -162,7 +150,6 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
val display = mock<Display>()
protected lateinit var spyContext: TestableContext
private lateinit var desktopModeEventLogger: DesktopModeEventLogger
- private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
@@ -177,6 +164,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
DisplayChangeController.OnDisplayChangingListener
internal lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener
protected lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
+ protected lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
fun setUpCommon() {
spyContext = spy(mContext)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 76e1e805f5a9..18a780bbb07c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.WindowInsets.Type.captionBar;
@@ -69,7 +68,6 @@ import android.os.Handler;
import android.os.SystemProperties;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
@@ -129,7 +127,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -171,8 +168,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final boolean DEFAULT_HAS_GLOBAL_FOCUS = true;
private static final boolean DEFAULT_SHOULD_IGNORE_CORNER_RADIUS = false;
- @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
-
@Mock
private DisplayController mMockDisplayController;
@Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index a20a89c644ed..ab9dbc98e15f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -23,7 +23,6 @@ import android.graphics.Rect
import android.os.IBinder
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display
import android.window.WindowContainerToken
@@ -44,7 +43,6 @@ import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
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
@@ -85,10 +83,6 @@ class DragPositioningCallbackUtilityTest {
@Mock
private lateinit var mockResources: Resources
- @JvmField
- @Rule
- val setFlagsRule = SetFlagsRule()
-
private lateinit var mockitoSession: StaticMockitoSession
@Before
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 479f1567ed31..3389ec11f9d0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -30,7 +30,6 @@ import android.graphics.Point;
import android.graphics.Region;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Size;
@@ -41,7 +40,6 @@ import com.android.wm.shell.ShellTestCase;
import com.google.common.testing.EqualsTester;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -87,9 +85,6 @@ public class DragResizeWindowGeometryTests extends ShellTestCase {
private static final Point BOTTOM_INSET_POINT = new Point(TASK_SIZE.getWidth() / 2,
TASK_SIZE.getHeight() - EDGE_RESIZE_HANDLE_INSET / 2);
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
/**
* Check that both groups of objects satisfy equals/hashcode within each group, and that each
* group is distinct from the next.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index f90988e90b22..f984f6db13fc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -26,7 +26,6 @@ import android.graphics.Point
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
@@ -60,7 +59,6 @@ import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -81,10 +79,6 @@ import org.mockito.kotlin.whenever
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner::class)
class HandleMenuTest : ShellTestCase() {
- @JvmField
- @Rule
- val setFlagsRule: SetFlagsRule = SetFlagsRule()
-
@Mock
private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
@Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index d9693460008f..3a8dcd674b74 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.WindowInsets.Type.captionBar;
@@ -62,7 +61,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Handler;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
import android.view.AttachedSurfaceControl;
@@ -93,7 +91,6 @@ import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -120,9 +117,6 @@ public class WindowDecorationTests extends ShellTestCase {
private static final int SHADOW_RADIUS = 10;
private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
-
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
new WindowDecoration.RelayoutResult<>();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
index 431de896f433..c61e0eb3b5af 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
@@ -86,6 +86,7 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() {
spyContext.setMockPackageManager(mockPackageManager)
doReturn(spyContext).whenever(spyContext).createContextAsUser(any(), anyInt())
doReturn(spyContext).whenever(mMockUserProfileContexts)[anyInt()]
+ doReturn(spyContext).whenever(mMockUserProfileContexts).getOrCreate(anyInt())
loader =
WindowDecorTaskResourceLoader(
shellInit = shellInit,
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index 139ccfd22b0e..7280b12aaca9 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -24,8 +24,8 @@ package com.android.extensions.appfunctions {
public final class AppFunctionManager {
ctor public AppFunctionManager(android.content.Context);
- method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
- method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+ method @RequiresPermission(android.Manifest.permission.EXECUTE_APP_FUNCTIONS) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
+ method @RequiresPermission(android.Manifest.permission.EXECUTE_APP_FUNCTIONS) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
index 9eb66a33fedc..1e31390854b8 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
@@ -104,12 +104,7 @@ public final class AppFunctionManager {
* <p>See {@link android.app.appfunctions.AppFunctionManager#executeAppFunction} for the
* documented behaviour of this method.
*/
- @RequiresPermission(
- anyOf = {
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- Manifest.permission.EXECUTE_APP_FUNCTIONS
- },
- conditional = true)
+ @RequiresPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS)
public void executeAppFunction(
@NonNull ExecuteAppFunctionRequest sidecarRequest,
@NonNull @CallbackExecutor Executor executor,
@@ -150,12 +145,7 @@ public final class AppFunctionManager {
* <p>See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the
* documented behaviour of this method.
*/
- @RequiresPermission(
- anyOf = {
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- Manifest.permission.EXECUTE_APP_FUNCTIONS
- },
- conditional = true)
+ @RequiresPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS)
public void isAppFunctionEnabled(
@NonNull String functionIdentifier,
@NonNull String targetPackage,
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index c4886836f451..1a8437108b72 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -48,6 +48,7 @@ import android.os.IHwBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
+import android.os.Trace;
import android.view.Surface;
import java.io.IOException;
@@ -3107,6 +3108,7 @@ final public class MediaCodec {
int index,
int offset, int size, long presentationTimeUs, int flags)
throws CryptoException {
+ Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueInputBuffer#java");
if ((flags & BUFFER_FLAG_DECODE_ONLY) != 0
&& (flags & BUFFER_FLAG_END_OF_STREAM) != 0) {
throw new InvalidBufferFlagsException(EOS_AND_DECODE_ONLY_ERROR_MESSAGE);
@@ -3126,6 +3128,8 @@ final public class MediaCodec {
} catch (CryptoException | IllegalStateException e) {
revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
throw e;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
}
}
@@ -3167,6 +3171,7 @@ final public class MediaCodec {
public final void queueInputBuffers(
int index,
@NonNull ArrayDeque<BufferInfo> bufferInfos) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueInputBuffers#java");
synchronized(mBufferLock) {
if (mBufferMode == BUFFER_MODE_BLOCK) {
throw new IncompatibleWithBlockModelException("queueInputBuffers() "
@@ -3182,6 +3187,8 @@ final public class MediaCodec {
} catch (CryptoException | IllegalStateException | IllegalArgumentException e) {
revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
throw e;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
}
}
@@ -3442,6 +3449,7 @@ final public class MediaCodec {
@NonNull CryptoInfo info,
long presentationTimeUs,
int flags) throws CryptoException {
+ Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueSecureInputBuffer#java");
if ((flags & BUFFER_FLAG_DECODE_ONLY) != 0
&& (flags & BUFFER_FLAG_END_OF_STREAM) != 0) {
throw new InvalidBufferFlagsException(EOS_AND_DECODE_ONLY_ERROR_MESSAGE);
@@ -3461,6 +3469,8 @@ final public class MediaCodec {
} catch (CryptoException | IllegalStateException e) {
revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
throw e;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
}
}
@@ -3491,6 +3501,7 @@ final public class MediaCodec {
int index,
@NonNull ArrayDeque<BufferInfo> bufferInfos,
@NonNull ArrayDeque<CryptoInfo> cryptoInfos) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueSecureInputBuffers#java");
synchronized(mBufferLock) {
if (mBufferMode == BUFFER_MODE_BLOCK) {
throw new IncompatibleWithBlockModelException("queueSecureInputBuffers() "
@@ -3506,6 +3517,8 @@ final public class MediaCodec {
} catch (CryptoException | IllegalStateException | IllegalArgumentException e) {
revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
throw e;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
}
}
@@ -3533,6 +3546,7 @@ final public class MediaCodec {
* @throws MediaCodec.CodecException upon codec error.
*/
public final int dequeueInputBuffer(long timeoutUs) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::dequeueInputBuffer#java");
synchronized (mBufferLock) {
if (mBufferMode == BUFFER_MODE_BLOCK) {
throw new IncompatibleWithBlockModelException("dequeueInputBuffer() "
@@ -3546,6 +3560,7 @@ final public class MediaCodec {
validateInputByteBufferLocked(mCachedInputBuffers, res);
}
}
+ Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
return res;
}
diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS
index b6096a1acd85..a600017119e2 100644
--- a/media/java/android/media/OWNERS
+++ b/media/java/android/media/OWNERS
@@ -1,6 +1,7 @@
# Bug component: 1344
-fgoldfain@google.com
+pshehane@google.com
elaurent@google.com
+etalvala@google.com
lajos@google.com
jmtrivi@google.com
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 3b8cf3fb2909..87bb6eab6c4d 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -85,8 +85,7 @@ public final class RoutingSessionInfo implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface TransferReason {}
- @NonNull
- final String mId;
+ @NonNull final String mOriginalId;
@Nullable
final CharSequence mName;
@Nullable
@@ -120,7 +119,7 @@ public final class RoutingSessionInfo implements Parcelable {
RoutingSessionInfo(@NonNull Builder builder) {
Objects.requireNonNull(builder, "builder must not be null.");
- mId = builder.mId;
+ mOriginalId = builder.mOriginalId;
mName = builder.mName;
mOwnerPackageName = builder.mOwnerPackageName;
mClientPackageName = builder.mClientPackageName;
@@ -148,8 +147,8 @@ public final class RoutingSessionInfo implements Parcelable {
}
RoutingSessionInfo(@NonNull Parcel src) {
- mId = src.readString();
- Preconditions.checkArgument(!TextUtils.isEmpty(mId));
+ mOriginalId = src.readString();
+ Preconditions.checkArgument(!TextUtils.isEmpty(mOriginalId));
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src);
mOwnerPackageName = src.readString();
@@ -221,9 +220,9 @@ public final class RoutingSessionInfo implements Parcelable {
@NonNull
public String getId() {
if (!TextUtils.isEmpty(mProviderId)) {
- return MediaRouter2Utils.toUniqueId(mProviderId, mId);
+ return MediaRouter2Utils.toUniqueId(mProviderId, mOriginalId);
} else {
- return mId;
+ return mOriginalId;
}
}
@@ -236,12 +235,16 @@ public final class RoutingSessionInfo implements Parcelable {
}
/**
- * Gets the original id set by {@link Builder#Builder(String, String)}.
+ * Gets the original id as assigned by the {@link MediaRoute2ProviderService route provider}.
+ *
+ * <p>This may be different from {@link #getId()}, which may convert this original id into a
+ * unique one by adding information about the provider that created this session info.
+ *
* @hide
*/
@NonNull
public String getOriginalId() {
- return mId;
+ return mOriginalId;
}
/**
@@ -423,7 +426,7 @@ public final class RoutingSessionInfo implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mId);
+ dest.writeString(mOriginalId);
dest.writeCharSequence(mName);
dest.writeString(mOwnerPackageName);
dest.writeString(mClientPackageName);
@@ -454,7 +457,7 @@ public final class RoutingSessionInfo implements Parcelable {
String indent = prefix + " ";
- pw.println(indent + "mId=" + mId);
+ pw.println(indent + "mOriginalId=" + mOriginalId);
pw.println(indent + "mName=" + mName);
pw.println(indent + "mOwnerPackageName=" + mOwnerPackageName);
pw.println(indent + "mClientPackageName=" + mClientPackageName);
@@ -485,7 +488,7 @@ public final class RoutingSessionInfo implements Parcelable {
}
RoutingSessionInfo other = (RoutingSessionInfo) obj;
- return Objects.equals(mId, other.mId)
+ return Objects.equals(mOriginalId, other.mOriginalId)
&& Objects.equals(mName, other.mName)
&& Objects.equals(mOwnerPackageName, other.mOwnerPackageName)
&& Objects.equals(mClientPackageName, other.mClientPackageName)
@@ -500,13 +503,13 @@ public final class RoutingSessionInfo implements Parcelable {
&& (mTransferReason == other.mTransferReason)
&& Objects.equals(mTransferInitiatorUserHandle, other.mTransferInitiatorUserHandle)
&& Objects.equals(
- mTransferInitiatorPackageName, other.mTransferInitiatorPackageName);
+ mTransferInitiatorPackageName, other.mTransferInitiatorPackageName);
}
@Override
public int hashCode() {
return Objects.hash(
- mId,
+ mOriginalId,
mName,
mOwnerPackageName,
mClientPackageName,
@@ -585,8 +588,7 @@ public final class RoutingSessionInfo implements Parcelable {
* Builder class for {@link RoutingSessionInfo}.
*/
public static final class Builder {
- @NonNull
- private final String mId;
+ @NonNull private final String mOriginalId;
@Nullable
private CharSequence mName;
@Nullable
@@ -616,23 +618,22 @@ public final class RoutingSessionInfo implements Parcelable {
/**
* Constructor for builder to create {@link RoutingSessionInfo}.
- * <p>
- * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of
- * {@link RoutingSessionInfo#getId()} can be different from what was set in
- * {@link MediaRoute2ProviderService}.
- * </p>
*
- * @param id ID of the session. Must not be empty.
- * @param clientPackageName package name of the client app which uses this session.
- * If is is unknown, then just use an empty string.
+ * <p>In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of {@link
+ * RoutingSessionInfo#getId()} can be different from what was set in {@link
+ * MediaRoute2ProviderService}.
+ *
+ * @param originalId ID of the session. Must not be empty.
+ * @param clientPackageName package name of the client app which uses this session. If is is
+ * unknown, then just use an empty string.
* @see MediaRoute2Info#getId()
*/
- public Builder(@NonNull String id, @NonNull String clientPackageName) {
- if (TextUtils.isEmpty(id)) {
+ public Builder(@NonNull String originalId, @NonNull String clientPackageName) {
+ if (TextUtils.isEmpty(originalId)) {
throw new IllegalArgumentException("id must not be empty");
}
- mId = id;
+ mOriginalId = originalId;
mClientPackageName =
Objects.requireNonNull(clientPackageName, "clientPackageName must not be null");
mSelectedRoutes = new ArrayList<>();
@@ -650,7 +651,7 @@ public final class RoutingSessionInfo implements Parcelable {
public Builder(@NonNull RoutingSessionInfo sessionInfo) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
- mId = sessionInfo.mId;
+ mOriginalId = sessionInfo.mOriginalId;
mName = sessionInfo.mName;
mClientPackageName = sessionInfo.mClientPackageName;
mProviderId = sessionInfo.mProviderId;
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 94454cf9ab9b..405d292dfafa 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -125,6 +125,13 @@ flag {
}
flag {
+ name: "enable_output_switcher_session_grouping"
+ namespace: "media_better_together"
+ description: "Enables selected items in Output Switcher to be grouped together."
+ bug: "388347018"
+}
+
+flag {
name: "enable_prevention_of_keep_alive_route_providers"
namespace: "media_solutions"
description: "Enables mechanisms to prevent route providers from keeping malicious apps alive."
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index e558209420e0..e4de3e4420fe 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -341,6 +341,13 @@ public class MediaQualityContract {
public static final String PARAMETER_FILM_MODE = "film_mode";
/**
+ * Enable/disable black color auto stretch
+ *
+ * @hide
+ */
+ public static final String PARAMETER_BLACK_STRETCH = "black_stretch";
+
+ /**
* Enable/disable blue color auto stretch
*
* <p>Type: BOOLEAN
@@ -457,6 +464,27 @@ public class MediaQualityContract {
* @hide
*
*/
+ public static final String PARAMETER_COLOR_TEMPERATURE_RED_GAIN =
+ "color_temperature_red_gain";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TEMPERATURE_GREEN_GAIN =
+ "color_temperature_green_gain";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TEMPERATURE_BLUE_GAIN =
+ "color_temperature_blue_gain";
+
+ /**
+ * @hide
+ *
+ */
public static final String PARAMETER_COLOR_TEMPERATURE_RED_OFFSET =
"color_temperature_red_offset";
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8419ce761a4a..3bc238a812d9 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -16,7 +16,9 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaCodec-JNI"
+#define ATRACE_TAG ATRACE_TAG_VIDEO
#include <utils/Log.h>
+#include <utils/Trace.h>
#include <type_traits>
@@ -2106,7 +2108,7 @@ static void android_media_MediaCodec_queueInputBuffer(
jlong timestampUs,
jint flags) {
ALOGV("android_media_MediaCodec_queueInputBuffer");
-
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueInputBuffer#jni");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
@@ -2192,6 +2194,7 @@ static void android_media_MediaCodec_queueInputBuffers(
jint index,
jobjectArray objArray) {
ALOGV("android_media_MediaCodec_queueInputBuffers");
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueInputBuffers#jni");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK || objArray == NULL) {
throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
@@ -2431,6 +2434,7 @@ static void android_media_MediaCodec_queueSecureInputBuffer(
jobject cryptoInfoObj,
jlong timestampUs,
jint flags) {
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueSecureInputBuffer#jni");
ALOGV("android_media_MediaCodec_queueSecureInputBuffer");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -2641,6 +2645,7 @@ static void android_media_MediaCodec_queueSecureInputBuffers(
jint index,
jobjectArray bufferInfosObjs,
jobjectArray cryptoInfoObjs) {
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueSecureInputBuffers#jni");
ALOGV("android_media_MediaCodec_queueSecureInputBuffers");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -2685,6 +2690,7 @@ static void android_media_MediaCodec_queueSecureInputBuffers(
}
static jobject android_media_MediaCodec_mapHardwareBuffer(JNIEnv *env, jclass, jobject bufferObj) {
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::mapHardwareBuffer#jni");
ALOGV("android_media_MediaCodec_mapHardwareBuffer");
AHardwareBuffer *hardwareBuffer = android_hardware_HardwareBuffer_getNativeHardwareBuffer(
env, bufferObj);
@@ -3028,6 +3034,7 @@ static void extractBufferFromContext(
static void android_media_MediaCodec_native_queueLinearBlock(
JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
jobjectArray cryptoInfoArray, jobjectArray objArray, jobject keys, jobject values) {
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueLinearBlock#jni");
ALOGV("android_media_MediaCodec_native_queueLinearBlock");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -3145,6 +3152,7 @@ static void android_media_MediaCodec_native_queueHardwareBuffer(
JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
jlong presentationTimeUs, jint flags, jobject keys, jobject values) {
ALOGV("android_media_MediaCodec_native_queueHardwareBuffer");
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueHardwareBuffer#jni");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
diff --git a/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
index cae8db27a435..428997e0c446 100644
--- a/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
@@ -19,6 +19,7 @@ package android.app.ondeviceintelligence;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -51,7 +52,8 @@ public final class InferenceInfo implements Parcelable {
private final long endTimeMs;
/**
- * Suspended time in milliseconds.
+ * The total duration of the period(s) during which the inference was
+ * suspended (i.e. not running), in milliseconds.
*/
private final long suspendedTimeMs;
@@ -61,7 +63,7 @@ public final class InferenceInfo implements Parcelable {
* @param uid Uid for the caller app.
* @param startTimeMs Inference start time (milliseconds from the epoch time).
* @param endTimeMs Inference end time (milliseconds from the epoch time).
- * @param suspendedTimeMs Suspended time in milliseconds.
+ * @param suspendedTimeMs Suspended duration, in milliseconds.
*/
InferenceInfo(int uid, long startTimeMs, long endTimeMs,
long suspendedTimeMs) {
@@ -128,11 +130,12 @@ public final class InferenceInfo implements Parcelable {
}
/**
- * Returns the suspended time in milliseconds.
+ * Returns the suspended duration, in milliseconds.
*
- * @return the suspended time in milliseconds.
+ * @return the total duration of the period(s) during which the inference
+ * was suspended (i.e. not running), in milliseconds.
*/
- @CurrentTimeMillisLong
+ @DurationMillisLong
public long getSuspendedTimeMillis() {
return suspendedTimeMs;
}
@@ -197,12 +200,14 @@ public final class InferenceInfo implements Parcelable {
}
/**
- * Sets the suspended time in milliseconds.
+ * Sets the suspended duration, in milliseconds.
*
- * @param suspendedTimeMs the suspended time in milliseconds.
+ * @param suspendedTimeMs the total duration of the period(s) in which
+ * the request was suspended (i.e. not running),
+ * in milliseconds.
* @return the Builder instance.
*/
- public @NonNull Builder setSuspendedTimeMillis(@CurrentTimeMillisLong long suspendedTimeMs) {
+ public @NonNull Builder setSuspendedTimeMillis(@DurationMillisLong long suspendedTimeMs) {
this.suspendedTimeMs = suspendedTimeMs;
return this;
}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
index cae8db27a435..64524fb096cb 100644
--- a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
@@ -19,6 +19,7 @@ package android.app.ondeviceintelligence;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -50,8 +51,9 @@ public final class InferenceInfo implements Parcelable {
*/
private final long endTimeMs;
- /**
- * Suspended time in milliseconds.
+ /**
+ * The total duration of the period(s) during which the inference was
+ * suspended (i.e. not running), in milliseconds.
*/
private final long suspendedTimeMs;
@@ -61,7 +63,7 @@ public final class InferenceInfo implements Parcelable {
* @param uid Uid for the caller app.
* @param startTimeMs Inference start time (milliseconds from the epoch time).
* @param endTimeMs Inference end time (milliseconds from the epoch time).
- * @param suspendedTimeMs Suspended time in milliseconds.
+ * @param suspendedTimeMs Suspended duration, in milliseconds.
*/
InferenceInfo(int uid, long startTimeMs, long endTimeMs,
long suspendedTimeMs) {
@@ -128,11 +130,12 @@ public final class InferenceInfo implements Parcelable {
}
/**
- * Returns the suspended time in milliseconds.
+ * Returns the suspended duration, in milliseconds.
*
- * @return the suspended time in milliseconds.
+ * @return the total duration of the period(s) during which the inference
+ * was suspended (i.e. not running), in milliseconds.
*/
- @CurrentTimeMillisLong
+ @DurationMillisLong
public long getSuspendedTimeMillis() {
return suspendedTimeMs;
}
@@ -197,12 +200,14 @@ public final class InferenceInfo implements Parcelable {
}
/**
- * Sets the suspended time in milliseconds.
+ * Sets the suspended duration, in milliseconds.
*
- * @param suspendedTimeMs the suspended time in milliseconds.
+ * @param suspendedTimeMs the total duration of the period(s) in which
+ * the request was suspended (i.e. not running),
+ * in milliseconds.
* @return the Builder instance.
*/
- public @NonNull Builder setSuspendedTimeMillis(@CurrentTimeMillisLong long suspendedTimeMs) {
+ public @NonNull Builder setSuspendedTimeMillis(@DurationMillisLong long suspendedTimeMs) {
this.suspendedTimeMs = suspendedTimeMs;
return this;
}
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index 60a9ebd6f98b..c82829d6ccea 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -116,8 +116,8 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
// Default attention level is High.
private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
- private String mSubtitle;
- private String mHeader;
+ private CharSequence mSubtitle;
+ private CharSequence mHeader;
private int mButtonOrientation;
public BannerMessagePreference(Context context) {
@@ -351,7 +351,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in positive button.
*/
- public BannerMessagePreference setPositiveButtonText(String positiveButtonText) {
+ public BannerMessagePreference setPositiveButtonText(CharSequence positiveButtonText) {
if (!TextUtils.equals(positiveButtonText, mPositiveButtonInfo.mText)) {
mPositiveButtonInfo.mText = positiveButtonText;
notifyChanged();
@@ -369,7 +369,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in negative button.
*/
- public BannerMessagePreference setNegativeButtonText(String negativeButtonText) {
+ public BannerMessagePreference setNegativeButtonText(CharSequence negativeButtonText) {
if (!TextUtils.equals(negativeButtonText, mNegativeButtonInfo.mText)) {
mNegativeButtonInfo.mText = negativeButtonText;
notifyChanged();
@@ -401,7 +401,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
* Sets the subtitle.
*/
@RequiresApi(Build.VERSION_CODES.S)
- public BannerMessagePreference setSubtitle(String subtitle) {
+ public BannerMessagePreference setSubtitle(CharSequence subtitle) {
if (!TextUtils.equals(subtitle, mSubtitle)) {
mSubtitle = subtitle;
notifyChanged();
@@ -421,8 +421,8 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
* Sets the header.
*/
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
- public BannerMessagePreference setHeader(String header) {
- if (!TextUtils.equals(header, mSubtitle)) {
+ public BannerMessagePreference setHeader(CharSequence header) {
+ if (!TextUtils.equals(header, mHeader)) {
mHeader = header;
notifyChanged();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index ad196b8c1f7b..4ee9ff059502 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -658,12 +658,9 @@ public abstract class InfoMediaManager {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
RouteListingPreference routeListingPreference = getRouteListingPreference();
if (routeListingPreference != null) {
- final List<RouteListingPreference.Item> preferenceRouteListing =
- Api34Impl.composePreferenceRouteListing(
- routeListingPreference);
availableRoutes = Api34Impl.arrangeRouteListByPreference(selectedRoutes,
getAvailableRoutesFromRouter(),
- preferenceRouteListing);
+ routeListingPreference);
}
return Api34Impl.filterDuplicatedIds(availableRoutes);
} else {
@@ -760,11 +757,15 @@ public abstract class InfoMediaManager {
@DoNotInline
static List<RouteListingPreference.Item> composePreferenceRouteListing(
RouteListingPreference routeListingPreference) {
+ boolean preferRouteListingOrdering =
+ com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping()
+ && preferRouteListingOrdering(routeListingPreference);
List<RouteListingPreference.Item> finalizedItemList = new ArrayList<>();
List<RouteListingPreference.Item> itemList = routeListingPreference.getItems();
for (RouteListingPreference.Item item : itemList) {
// Put suggested devices on the top first before further organization
- if ((item.getFlags() & RouteListingPreference.Item.FLAG_SUGGESTED) != 0) {
+ if (!preferRouteListingOrdering
+ && (item.getFlags() & RouteListingPreference.Item.FLAG_SUGGESTED) != 0) {
finalizedItemList.add(0, item);
} else {
finalizedItemList.add(item);
@@ -792,7 +793,7 @@ public abstract class InfoMediaManager {
* Returns an ordered list of available devices based on the provided {@code
* routeListingPreferenceItems}.
*
- * <p>The result has the following order:
+ * <p>The resulting order if enableOutputSwitcherSessionGrouping is disabled is:
*
* <ol>
* <li>Selected routes.
@@ -800,22 +801,54 @@ public abstract class InfoMediaManager {
* <li>Not-selected, non-system, available routes sorted by route listing preference.
* </ol>
*
+ * <p>The resulting order if enableOutputSwitcherSessionGrouping is enabled is:
+ *
+ * <ol>
+ * <li>Selected routes sorted by route listing preference.
+ * <li>Selected routes not defined by route listing preference.
+ * <li>Not-selected system routes.
+ * <li>Not-selected, non-system, available routes sorted by route listing preference.
+ * </ol>
+ *
+ *
* @param selectedRoutes List of currently selected routes.
* @param availableRoutes List of available routes that match the app's requested route
* features.
- * @param routeListingPreferenceItems Ordered list of {@link RouteListingPreference.Item} to
- * sort routes with.
+ * @param routeListingPreference Preferences provided by the app to determine route order.
*/
@DoNotInline
static List<MediaRoute2Info> arrangeRouteListByPreference(
List<MediaRoute2Info> selectedRoutes,
List<MediaRoute2Info> availableRoutes,
- List<RouteListingPreference.Item> routeListingPreferenceItems) {
+ RouteListingPreference routeListingPreference) {
+ final List<RouteListingPreference.Item> routeListingPreferenceItems =
+ Api34Impl.composePreferenceRouteListing(routeListingPreference);
+
Set<String> sortedRouteIds = new LinkedHashSet<>();
+ boolean addSelectedRlpItemsFirst =
+ com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping()
+ && preferRouteListingOrdering(routeListingPreference);
+ Set<String> selectedRouteIds = new HashSet<>();
+
+ if (addSelectedRlpItemsFirst) {
+ // Add selected RLP items first
+ for (MediaRoute2Info selectedRoute : selectedRoutes) {
+ selectedRouteIds.add(selectedRoute.getId());
+ }
+ for (RouteListingPreference.Item item: routeListingPreferenceItems) {
+ if (selectedRouteIds.contains(item.getRouteId())) {
+ sortedRouteIds.add(item.getRouteId());
+ }
+ }
+ }
+
// Add selected routes first.
- for (MediaRoute2Info selectedRoute : selectedRoutes) {
- sortedRouteIds.add(selectedRoute.getId());
+ if (com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping()
+ && sortedRouteIds.size() != selectedRoutes.size()) {
+ for (MediaRoute2Info selectedRoute : selectedRoutes) {
+ sortedRouteIds.add(selectedRoute.getId());
+ }
}
// Add not-yet-added system routes.
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index e1447dc8410c..1a83f0a2e775 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -48,15 +48,20 @@ import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
import android.media.session.MediaSessionManager;
import android.os.Build;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import com.android.media.flags.Flags;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.media.InfoMediaManager.Api34Impl;
import com.android.settingslib.testutils.shadow.ShadowRouter2Manager;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -122,6 +127,8 @@ public class InfoMediaManagerTest {
.addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
.build();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private MediaRouter2Manager mRouterManager;
@Mock
@@ -377,21 +384,26 @@ public class InfoMediaManagerTest {
}
private RouteListingPreference setUpPreferenceList(String packageName) {
+ return setUpPreferenceList(packageName, false);
+ }
+
+ private RouteListingPreference setUpPreferenceList(
+ String packageName, boolean useSystemOrdering) {
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
final List<RouteListingPreference.Item> preferenceItemList = new ArrayList<>();
- RouteListingPreference.Item item1 =
+ RouteListingPreference.Item item1 = new RouteListingPreference.Item.Builder(
+ TEST_ID_3).build();
+ RouteListingPreference.Item item2 =
new RouteListingPreference.Item.Builder(TEST_ID_4)
.setFlags(RouteListingPreference.Item.FLAG_SUGGESTED)
.build();
- RouteListingPreference.Item item2 = new RouteListingPreference.Item.Builder(
- TEST_ID_3).build();
preferenceItemList.add(item1);
preferenceItemList.add(item2);
RouteListingPreference routeListingPreference =
new RouteListingPreference.Builder().setItems(
- preferenceItemList).setUseSystemOrdering(false).build();
+ preferenceItemList).setUseSystemOrdering(useSystemOrdering).build();
when(mRouterManager.getRouteListingPreference(packageName))
.thenReturn(routeListingPreference);
return routeListingPreference;
@@ -908,4 +920,66 @@ public class InfoMediaManagerTest {
assertThat(device.getState()).isEqualTo(STATE_SELECTED);
assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(device);
}
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void composePreferenceRouteListing_useSystemOrderingIsFalse() {
+ RouteListingPreference routeListingPreference =
+ setUpPreferenceList(TEST_PACKAGE_NAME, false);
+
+ List<RouteListingPreference.Item> routeOrder =
+ Api34Impl.composePreferenceRouteListing(routeListingPreference);
+
+ assertThat(routeOrder.get(0).getRouteId()).isEqualTo(TEST_ID_3);
+ assertThat(routeOrder.get(1).getRouteId()).isEqualTo(TEST_ID_4);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void composePreferenceRouteListing_useSystemOrderingIsTrue() {
+ RouteListingPreference routeListingPreference =
+ setUpPreferenceList(TEST_PACKAGE_NAME, true);
+
+ List<RouteListingPreference.Item> routeOrder =
+ Api34Impl.composePreferenceRouteListing(routeListingPreference);
+
+ assertThat(routeOrder.get(0).getRouteId()).isEqualTo(TEST_ID_4);
+ assertThat(routeOrder.get(1).getRouteId()).isEqualTo(TEST_ID_3);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void arrangeRouteListByPreference_useSystemOrderingIsFalse() {
+ RouteListingPreference routeListingPreference =
+ setUpPreferenceList(TEST_PACKAGE_NAME, false);
+ List<MediaRoute2Info> routes = setAvailableRoutesList(TEST_PACKAGE_NAME);
+ when(mRouterManager.getSelectedRoutes(any())).thenReturn(routes);
+
+ List<MediaRoute2Info> routeOrder =
+ Api34Impl.arrangeRouteListByPreference(
+ routes, routes, routeListingPreference);
+
+ assertThat(routeOrder.get(0).getId()).isEqualTo(TEST_ID_3);
+ assertThat(routeOrder.get(1).getId()).isEqualTo(TEST_ID_4);
+ assertThat(routeOrder.get(2).getId()).isEqualTo(TEST_ID_2);
+ assertThat(routeOrder.get(3).getId()).isEqualTo(TEST_ID_1);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void arrangeRouteListByPreference_useSystemOrderingIsTrue() {
+ RouteListingPreference routeListingPreference =
+ setUpPreferenceList(TEST_PACKAGE_NAME, true);
+ List<MediaRoute2Info> routes = setAvailableRoutesList(TEST_PACKAGE_NAME);
+ when(mRouterManager.getSelectedRoutes(any())).thenReturn(routes);
+
+ List<MediaRoute2Info> routeOrder =
+ Api34Impl.arrangeRouteListByPreference(
+ routes, routes, routeListingPreference);
+
+ assertThat(routeOrder.get(0).getId()).isEqualTo(TEST_ID_2);
+ assertThat(routeOrder.get(1).getId()).isEqualTo(TEST_ID_3);
+ assertThat(routeOrder.get(2).getId()).isEqualTo(TEST_ID_4);
+ assertThat(routeOrder.get(3).getId()).isEqualTo(TEST_ID_1);
+ }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
index 292aa51db18f..6d30f492075e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
@@ -43,6 +43,7 @@ final class WritableNamespaces {
"netd_native",
"network_security",
"on_device_personalization",
+ "testing",
"tethering",
"tethering_u_or_later_native",
"thread_network"
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2b4e65f2415c..9ab8aa8898a3 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -952,7 +952,6 @@
<uses-permission android:name="android.permission.SETUP_FSVERITY" />
<!-- Permissions required for CTS test - AppFunctionManagerTest -->
- <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" />
<uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS" />
<!-- Permission required for CTS test - CtsNfcTestCases -->
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index dace50fafbf1..bb0d5d7755cf 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -196,6 +196,18 @@ flag {
}
flag {
+ name: "notification_undo_guts_on_config_changed"
+ namespace: "systemui"
+ description: "Fixes a bug where a theme or font change while notification guts were open"
+ " (e.g. the snooze options or notification info) would show an empty notification by"
+ " closing the guts and undoing changes."
+ bug: "379267630"
+ 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"
@@ -575,20 +587,6 @@ flag {
}
flag {
- name: "clock_reactive_variants"
- namespace: "systemui"
- description: "Add reactive variant fonts to some clocks"
- bug: "343495953"
-}
-
-flag {
- name: "lockscreen_custom_clocks"
- namespace: "systemui"
- description: "Enable lockscreen custom clocks"
- bug: "378486437"
-}
-
-flag {
name: "faster_unlock_transition"
namespace: "systemui"
description: "Faster wallpaper unlock transition"
@@ -1828,6 +1826,13 @@ flag {
}
flag {
+ name: "notification_row_transparency"
+ namespace: "systemui"
+ description: "Enables transparency on the Notification Shade."
+ bug: "392187268"
+}
+
+flag {
name: "shade_expands_on_status_bar_long_press"
namespace: "systemui"
description: "Expands the shade on long press of any status bar"
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index 0f6e6a7c4383..f490968b7a7c 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -34,7 +34,6 @@ import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
import javax.inject.Provider
-import kotlinx.coroutines.ExperimentalCoroutinesApi
@Module(includes = [LockscreenSceneBlueprintModule::class])
interface LockscreenSceneModule {
@@ -43,7 +42,6 @@ interface LockscreenSceneModule {
companion object {
- @OptIn(ExperimentalCoroutinesApi::class)
@Provides
@SysUISingleton
@KeyguardRootView
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 31aebc28d072..2c6d09a4593a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -19,13 +19,11 @@ package com.android.systemui.communal.ui.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntRect
@@ -35,6 +33,7 @@ import com.android.compose.animation.scene.ContentScope
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
+import com.android.systemui.communal.ui.compose.section.CommunalLockSection
import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
import com.android.systemui.communal.ui.compose.section.CommunalToDreamButtonSection
import com.android.systemui.communal.ui.compose.section.HubOnboardingSection
@@ -43,7 +42,6 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
-import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
import kotlin.math.min
@@ -58,6 +56,7 @@ constructor(
private val communalSettingsInteractor: CommunalSettingsInteractor,
private val dialogFactory: SystemUIDialogFactory,
private val lockSection: LockSection,
+ private val communalLockSection: CommunalLockSection,
private val bottomAreaSection: BottomAreaSection,
private val ambientStatusBarSection: AmbientStatusBarSection,
private val communalPopupSection: CommunalPopupSection,
@@ -88,12 +87,9 @@ constructor(
with(hubOnboardingSection) { BottomSheet() }
}
if (communalSettingsInteractor.isV2FlagEnabled()) {
- Icon(
- painter = painterResource(id = R.drawable.ic_lock),
- contentDescription = null,
- tint = MaterialTheme.colorScheme.onPrimaryContainer,
- modifier = Modifier.element(Communal.Elements.LockIcon),
- )
+ with(communalLockSection) {
+ LockIcon(modifier = Modifier.element(Communal.Elements.LockIcon))
+ }
} else {
with(lockSection) {
LockIcon(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalLockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalLockSection.kt
new file mode 100644
index 000000000000..eab2b8717405
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalLockSection.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2025 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.communal.ui.compose.section
+
+import android.content.Context
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.communal.ui.binder.CommunalLockIconViewBinder
+import com.android.systemui.communal.ui.viewmodel.CommunalLockIconViewModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LongPressHandlingViewLogger
+import com.android.systemui.log.dagger.LongPressTouchLog
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+class CommunalLockSection
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val windowManager: WindowManager,
+ private val authController: AuthController,
+ private val viewModel: Lazy<CommunalLockIconViewModel>,
+ private val falsingManager: Lazy<FalsingManager>,
+ private val vibratorHelper: Lazy<VibratorHelper>,
+ private val featureFlags: FeatureFlagsClassic,
+ @LongPressTouchLog private val logBuffer: LogBuffer,
+) {
+ @Composable
+ fun ContentScope.LockIcon(modifier: Modifier = Modifier) {
+ val context = LocalContext.current
+
+ AndroidView(
+ factory = { context ->
+ DeviceEntryIconView(
+ context,
+ null,
+ logger = LongPressHandlingViewLogger(logBuffer, tag = TAG),
+ )
+ .apply {
+ id = R.id.device_entry_icon_view
+ CommunalLockIconViewBinder.bind(
+ applicationScope,
+ this,
+ viewModel.get(),
+ falsingManager.get(),
+ vibratorHelper.get(),
+ )
+ }
+ },
+ modifier =
+ modifier.element(LockIconElementKey).layout { measurable, _ ->
+ val lockIconBounds = lockIconBounds(context)
+ val placeable =
+ measurable.measure(
+ Constraints.fixed(
+ width = lockIconBounds.width,
+ height = lockIconBounds.height,
+ )
+ )
+ layout(
+ width = placeable.width,
+ height = placeable.height,
+ alignmentLines =
+ mapOf(
+ BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
+ BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
+ BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
+ BlueprintAlignmentLines.LockIcon.Bottom to lockIconBounds.bottom,
+ ),
+ ) {
+ placeable.place(0, 0)
+ }
+ },
+ )
+ }
+
+ /** Returns the bounds of the lock icon, in window view coordinates. */
+ private fun lockIconBounds(context: Context): IntRect {
+ val windowViewBounds = windowManager.currentWindowMetrics.bounds
+ var widthPx = windowViewBounds.right.toFloat()
+ if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
+ val insets = windowManager.currentWindowMetrics.windowInsets
+ // Assumed to be initially neglected as there are no left or right insets in portrait.
+ // However, on landscape, these insets need to included when calculating the midpoint.
+ @Suppress("DEPRECATION")
+ widthPx -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat()
+ }
+ val defaultDensity =
+ DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+ DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ val lockIconRadiusPx = (defaultDensity * 36).toInt()
+
+ val scaleFactor = authController.scaleFactor
+ val bottomPaddingPx =
+ context.resources.getDimensionPixelSize(
+ com.android.systemui.customization.R.dimen.lock_icon_margin_bottom
+ )
+ val heightPx = windowViewBounds.bottom.toFloat()
+ val (center, radius) =
+ Pair(
+ IntOffset(
+ x = (widthPx / 2).toInt(),
+ y = (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt(),
+ ),
+ (lockIconRadiusPx * scaleFactor).toInt(),
+ )
+
+ return IntRect(center, radius)
+ }
+
+ companion object {
+ private const val TAG = "CommunalLockSection"
+ }
+}
+
+private val LockIconElementKey = ElementKey("CommunalLockIcon")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
index d02215083679..500527f4a508 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
@@ -57,9 +57,7 @@ import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaVie
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
import com.android.systemui.log.LongPressHandlingViewLogger
import com.android.systemui.res.R
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-@ExperimentalCoroutinesApi
@Composable
fun AlternateBouncer(
alternateBouncerDependencies: AlternateBouncerDependencies,
@@ -127,7 +125,6 @@ fun AlternateBouncer(
}
}
-@ExperimentalCoroutinesApi
@Composable
private fun StatusMessage(
viewModel: AlternateBouncerMessageAreaViewModel,
@@ -156,7 +153,6 @@ private fun StatusMessage(
}
}
-@ExperimentalCoroutinesApi
@Composable
private fun DeviceEntryIcon(
viewModel: AlternateBouncerUdfpsIconViewModel,
@@ -179,7 +175,6 @@ private fun DeviceEntryIcon(
}
/** TODO (b/353955910): Validate accessibility CUJs */
-@ExperimentalCoroutinesApi
@Composable
private fun UdfpsA11yOverlay(
viewModel: AlternateBouncerUdfpsAccessibilityOverlayViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 478970f4e210..d3417022565b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -45,7 +45,6 @@ import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
import java.util.Optional
import javax.inject.Inject
import kotlin.math.roundToInt
@@ -130,9 +129,7 @@ constructor(
if (!isShadeLayoutWide && !isBypassEnabled) {
Box(modifier = Modifier.weight(weight = 1f)) {
Column(Modifier.align(alignment = Alignment.TopStart)) {
- if (PromotedNotificationUiAod.isEnabled) {
- AodPromotedNotification()
- }
+ AodPromotedNotificationArea()
AodNotificationIcons(
modifier = Modifier.padding(start = aodIconPadding)
)
@@ -145,9 +142,7 @@ constructor(
}
} else {
Column {
- if (PromotedNotificationUiAod.isEnabled) {
- AodPromotedNotification()
- }
+ AodPromotedNotificationArea()
AodNotificationIcons(
modifier = Modifier.padding(start = aodIconPadding)
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index b66690c2fe89..abf7fdc05f2e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -54,6 +54,7 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.Notificati
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
import com.android.systemui.statusbar.notification.promoted.ui.viewmodel.AODPromotedNotificationViewModel
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -110,8 +111,23 @@ constructor(
}
@Composable
- fun AodPromotedNotification() {
- AODPromotedNotification(aodPromotedNotificationViewModelFactory)
+ fun AodPromotedNotificationArea(modifier: Modifier = Modifier) {
+ if (!PromotedNotificationUiAod.isEnabled) {
+ return
+ }
+
+ val isVisible by
+ keyguardRootViewModel.isAodPromotedNotifVisible.collectAsStateWithLifecycle()
+ val burnIn = rememberBurnIn(clockInteractor)
+
+ AnimatedVisibility(
+ visible = isVisible,
+ enter = fadeIn(),
+ exit = fadeOut(),
+ modifier = modifier.burnInAware(aodBurnInViewModel, burnIn.parameters),
+ ) {
+ AODPromotedNotification(aodPromotedNotificationViewModelFactory)
+ }
}
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
index 6738b97de015..1423d4acca21 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.scene.ui.composable
import androidx.compose.runtime.snapshotFlow
@@ -26,7 +24,6 @@ import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.observableTransitionState
import com.android.systemui.scene.shared.model.SceneDataSource
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 51483a894e1e..358d01874229 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.compose.nestedscroll
import androidx.compose.foundation.gestures.FlingBehavior
@@ -28,7 +26,6 @@ import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
index 0245cf2a26b8..97fba3d0dc8e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -3,7 +3,6 @@ package com.android.compose.test
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.TestMonotonicFrameClock
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -18,7 +17,7 @@ import kotlinx.coroutines.withContext
* Note: Please refer to the documentation for [runTest], as this feature utilizes it. This will
* provide a comprehensive understanding of all its behaviors.
*/
-@OptIn(ExperimentalTestApi::class, ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalTestApi::class)
fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
val testScope: TestScope = this
diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py
deleted file mode 100755
index d78ef5a5f1bf..000000000000
--- a/packages/SystemUI/flag_check.py
+++ /dev/null
@@ -1,138 +0,0 @@
-#! /usr/bin/env python3
-
-import sys
-import re
-import argparse
-
-# partially copied from tools/repohooks/rh/hooks.py
-
-TEST_MSG = """Commit message is missing a "Flag:" line. It must match one of the
-following case-sensitive regex:
-
- %s
-
-The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags.
-As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the flag.
-For legacy flags use EXEMPT with your flag name.
-
-Some examples below:
-
-Flag: NONE Repohook Update
-Flag: TEST_ONLY
-Flag: EXEMPT resource only update
-Flag: EXEMPT bugfix
-Flag: EXEMPT refactor
-Flag: com.android.launcher3.enable_twoline_allapps
-Flag: com.google.android.apps.nexuslauncher.zero_state_web_data_loader
-
-Check the git history for more examples. It's a regex matched field. See go/android-flag-directive for more details on various formats.
-"""
-
-def main():
- """Check the commit message for a 'Flag:' line."""
- parser = argparse.ArgumentParser(
- description='Check the commit message for a Flag: line.')
- parser.add_argument('--msg',
- metavar='msg',
- type=str,
- nargs='?',
- default='HEAD',
- help='commit message to process.')
- parser.add_argument(
- '--files',
- metavar='files',
- nargs='?',
- default='',
- help=
- 'PREUPLOAD_FILES in repo upload to determine whether the check should run for the files.')
- parser.add_argument(
- '--project',
- metavar='project',
- type=str,
- nargs='?',
- default='',
- help=
- 'REPO_PROJECT in repo upload to determine whether the check should run for this project.')
-
- # Parse the arguments
- args = parser.parse_args()
- desc = args.msg
- files = args.files
- project = args.project
-
- if not should_run_path(project, files):
- return
-
- field = 'Flag'
- none = 'NONE'
- testOnly = 'TEST_ONLY'
- docsOnly = 'DOCS_ONLY'
- exempt = 'EXEMPT'
- justification = '<justification>'
-
- # Aconfig Flag name format = <packageName>.<flagName>
- # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3
- # For now alphabets, digits, "_", "." characters are allowed in flag name.
- # Checks if there is "one dot" between packageName and flagName and not adding stricter format check
- #common_typos_disable
- flagName = '([a-zA-Z0-9.]+)([.]+)([a-zA-Z0-9_.]+)'
-
- # None and Exempt needs justification
- exemptRegex = fr'{exempt}\s*[a-zA-Z]+'
- noneRegex = fr'{none}\s*[a-zA-Z]+'
- #common_typos_enable
-
- readableRegexMsg = '\n\tFlag: '+none+' '+justification+'\n\tFlag: <packageName>.<flagName>\n\tFlag: ' +exempt+' '+justification+'\n\tFlag: '+testOnly+'\n\tFlag: '+docsOnly
-
- flagRegex = fr'^{field}: .*$'
- check_flag = re.compile(flagRegex) #Flag:
-
- # Ignore case for flag name format.
- flagNameRegex = fr'(?i)^{field}:\s*({noneRegex}|{flagName}|{testOnly}|{docsOnly}|{exemptRegex})\s*'
- check_flagName = re.compile(flagNameRegex) #Flag: <flag name format>
-
- flagError = False
- foundFlag = []
- # Check for multiple "Flag:" lines and all lines should match this format
- for line in desc.splitlines():
- if check_flag.match(line):
- if not check_flagName.match(line):
- flagError = True
- break
- foundFlag.append(line)
-
- # Throw error if
- # 1. No "Flag:" line is found
- # 2. "Flag:" doesn't follow right format.
- if (not foundFlag) or (flagError):
- error = TEST_MSG % (readableRegexMsg)
- print(error)
- sys.exit(1)
-
- sys.exit(0)
-
-
-def should_run_path(project, files):
- """Returns a boolean if this check should run with these paths.
- If you want to check for a particular subdirectory under the path,
- add a check here, call should_run_files and check for a specific sub dir path in should_run_files.
- """
- if not project:
- return False
- if project == 'platform/frameworks/base':
- return should_run_files(files)
- # Default case, run for all other projects which calls this script.
- return True
-
-
-def should_run_files(files):
- """Returns a boolean if this check should run with these files."""
- if not files:
- return False
- if 'packages/SystemUI' in files:
- return True
- return False
-
-
-if __name__ == '__main__':
- main()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index 809099e0d464..eb1f1d9c52f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -38,19 +38,19 @@ import com.android.systemui.communal.data.model.DisabledReason
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING
import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.model.CommunalBackgroundType
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.communal.shared.model.WhenToDream
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,9 +62,11 @@ import platform.test.runner.parameterized.Parameters
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos =
- testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources }
- private val testScope = kosmos.testScope
- private lateinit var underTest: CommunalSettingsRepository
+ testKosmos()
+ .apply { mainResources = mContext.orCreateTestableResources.resources }
+ .useUnconfinedTestDispatcher()
+
+ private val Kosmos.underTest by Kosmos.Fixture { communalSettingsRepository }
init {
mSetFlagsRule.setFlagsParameterization(flags!!)
@@ -76,98 +78,105 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_FEATURES_NONE)
- underTest = kosmos.communalSettingsRepository
}
@EnableFlags(FLAG_COMMUNAL_HUB)
@DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
- fun getFlagEnabled_bothEnabled() {
- kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+ fun getFlagEnabled_bothEnabled() =
+ kosmos.runTest {
+ fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
- assertThat(underTest.getFlagEnabled()).isTrue()
- }
+ assertThat(underTest.getFlagEnabled()).isTrue()
+ }
@DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
@Test
- fun getFlagEnabled_bothDisabled() {
- kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+ fun getFlagEnabled_bothDisabled() =
+ kosmos.runTest {
+ fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
- assertThat(underTest.getFlagEnabled()).isFalse()
- }
+ assertThat(underTest.getFlagEnabled()).isFalse()
+ }
@DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
@Test
- fun getFlagEnabled_onlyClassicFlagEnabled() {
- kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+ fun getFlagEnabled_onlyClassicFlagEnabled() =
+ kosmos.runTest {
+ fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
- assertThat(underTest.getFlagEnabled()).isFalse()
- }
+ assertThat(underTest.getFlagEnabled()).isFalse()
+ }
@EnableFlags(FLAG_COMMUNAL_HUB)
@DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
- fun getFlagEnabled_onlyTrunkFlagEnabled() {
- kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+ fun getFlagEnabled_onlyTrunkFlagEnabled() =
+ kosmos.runTest {
+ fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
- assertThat(underTest.getFlagEnabled()).isFalse()
- }
+ assertThat(underTest.getFlagEnabled()).isFalse()
+ }
@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
@DisableFlags(FLAG_COMMUNAL_HUB)
@Test
- fun getFlagEnabled_mobileConfigEnabled() {
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_glanceableHubEnabled,
- true,
- )
+ fun getFlagEnabled_mobileConfigEnabled() =
+ kosmos.runTest {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_glanceableHubEnabled,
+ true,
+ )
- assertThat(underTest.getFlagEnabled()).isTrue()
- }
+ assertThat(underTest.getFlagEnabled()).isTrue()
+ }
@DisableFlags(FLAG_GLANCEABLE_HUB_V2, FLAG_COMMUNAL_HUB)
@Test
- fun getFlagEnabled_onlyMobileConfigEnabled() {
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_glanceableHubEnabled,
- true,
- )
+ fun getFlagEnabled_onlyMobileConfigEnabled() =
+ kosmos.runTest {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_glanceableHubEnabled,
+ true,
+ )
- assertThat(underTest.getFlagEnabled()).isFalse()
- }
+ assertThat(underTest.getFlagEnabled()).isFalse()
+ }
@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
@DisableFlags(FLAG_COMMUNAL_HUB)
@Test
- fun getFlagEnabled_onlyMobileFlagEnabled() {
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_glanceableHubEnabled,
- false,
- )
+ fun getFlagEnabled_onlyMobileFlagEnabled() =
+ kosmos.runTest {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_glanceableHubEnabled,
+ false,
+ )
- assertThat(underTest.getFlagEnabled()).isFalse()
- }
+ assertThat(underTest.getFlagEnabled()).isFalse()
+ }
@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
@DisableFlags(FLAG_COMMUNAL_HUB)
@Test
- fun getFlagEnabled_oldFlagIgnored() {
- // New config flag enabled.
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_glanceableHubEnabled,
- true,
- )
+ fun getFlagEnabled_oldFlagIgnored() =
+ kosmos.runTest {
+ // New config flag enabled.
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_glanceableHubEnabled,
+ true,
+ )
- // Old config flag disabled.
- kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+ // Old config flag disabled.
+ fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
- assertThat(underTest.getFlagEnabled()).isTrue()
- }
+ assertThat(underTest.getFlagEnabled()).isTrue()
+ }
@EnableFlags(FLAG_COMMUNAL_HUB)
@Test
fun secondaryUserIsInvalid() =
- testScope.runTest {
+ kosmos.runTest {
val enabledState by collectLastValue(underTest.getEnabledState(SECONDARY_USER))
assertThat(enabledState?.enabled).isFalse()
@@ -187,7 +196,7 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
@DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
@Test
fun communalHubFlagIsDisabled() =
- testScope.runTest {
+ kosmos.runTest {
val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
assertThat(enabledState?.enabled).isFalse()
assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
@@ -196,35 +205,23 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
@EnableFlags(FLAG_COMMUNAL_HUB)
@Test
fun hubIsDisabledByUser() =
- testScope.runTest {
- kosmos.fakeSettings.putIntForUser(
- Settings.Secure.GLANCEABLE_HUB_ENABLED,
- 0,
- PRIMARY_USER.id,
- )
+ kosmos.runTest {
+ fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
assertThat(enabledState?.enabled).isFalse()
assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_USER_SETTING)
- kosmos.fakeSettings.putIntForUser(
- Settings.Secure.GLANCEABLE_HUB_ENABLED,
- 1,
- SECONDARY_USER.id,
- )
+ fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, SECONDARY_USER.id)
assertThat(enabledState?.enabled).isFalse()
- kosmos.fakeSettings.putIntForUser(
- Settings.Secure.GLANCEABLE_HUB_ENABLED,
- 1,
- PRIMARY_USER.id,
- )
+ fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, PRIMARY_USER.id)
assertThat(enabledState?.enabled).isTrue()
}
@EnableFlags(FLAG_COMMUNAL_HUB)
@Test
fun hubIsDisabledByDevicePolicy() =
- testScope.runTest {
+ kosmos.runTest {
val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
assertThat(enabledState?.enabled).isTrue()
@@ -236,7 +233,7 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
@EnableFlags(FLAG_COMMUNAL_HUB)
@Test
fun widgetsAllowedForWorkProfile_isFalse_whenDisallowedByDevicePolicy() =
- testScope.runTest {
+ kosmos.runTest {
val widgetsAllowedForWorkProfile by
collectLastValue(underTest.getAllowedByDevicePolicy(WORK_PROFILE))
assertThat(widgetsAllowedForWorkProfile).isTrue()
@@ -248,7 +245,7 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
@EnableFlags(FLAG_COMMUNAL_HUB)
@Test
fun hubIsEnabled_whenDisallowedByDevicePolicyForWorkProfile() =
- testScope.runTest {
+ kosmos.runTest {
val enabledStateForPrimaryUser by
collectLastValue(underTest.getEnabledState(PRIMARY_USER))
assertThat(enabledStateForPrimaryUser?.enabled).isTrue()
@@ -260,15 +257,11 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
@EnableFlags(FLAG_COMMUNAL_HUB)
@Test
fun hubIsDisabledByUserAndDevicePolicy() =
- testScope.runTest {
+ kosmos.runTest {
val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
assertThat(enabledState?.enabled).isTrue()
- kosmos.fakeSettings.putIntForUser(
- Settings.Secure.GLANCEABLE_HUB_ENABLED,
- 0,
- PRIMARY_USER.id,
- )
+ fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
assertThat(enabledState?.enabled).isFalse()
@@ -282,17 +275,17 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
@Test
@DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
fun backgroundType_defaultValue() =
- testScope.runTest {
+ kosmos.runTest {
val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
assertThat(backgroundType).isEqualTo(CommunalBackgroundType.ANIMATED)
}
@Test
fun backgroundType_verifyAllValues() =
- testScope.runTest {
+ kosmos.runTest {
val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
for (type in CommunalBackgroundType.entries) {
- kosmos.fakeSettings.putIntForUser(
+ fakeSettings.putIntForUser(
GLANCEABLE_HUB_BACKGROUND_SETTING,
type.value,
PRIMARY_USER.id,
@@ -308,30 +301,71 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
@Test
fun screensaverDisabledByUser() =
- testScope.runTest {
+ kosmos.runTest {
val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
- kosmos.fakeSettings.putIntForUser(
- Settings.Secure.SCREENSAVER_ENABLED,
- 0,
- PRIMARY_USER.id,
- )
+ fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 0, PRIMARY_USER.id)
assertThat(enabledState).isFalse()
}
@Test
fun screensaverEnabledByUser() =
- testScope.runTest {
+ kosmos.runTest {
val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
- kosmos.fakeSettings.putIntForUser(
- Settings.Secure.SCREENSAVER_ENABLED,
+ fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 1, PRIMARY_USER.id)
+
+ assertThat(enabledState).isTrue()
+ }
+
+ @Test
+ fun whenToDream_charging() =
+ kosmos.runTest {
+ val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
1,
PRIMARY_USER.id,
)
- assertThat(enabledState).isTrue()
+ assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_CHARGING)
+ }
+
+ @Test
+ fun whenToDream_docked() =
+ kosmos.runTest {
+ val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ 1,
+ PRIMARY_USER.id,
+ )
+
+ assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_DOCKED)
+ }
+
+ @Test
+ fun whenToDream_postured() =
+ kosmos.runTest {
+ val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ 1,
+ PRIMARY_USER.id,
+ )
+
+ assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_POSTURED)
+ }
+
+ @Test
+ fun whenToDream_default() =
+ kosmos.runTest {
+ val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+ assertThat(whenToDreamState).isEqualTo(WhenToDream.NEVER)
}
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 7ae0577bd289..c9e7a5d7df05 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -38,13 +38,9 @@ import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
-import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
-import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
-import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository
-import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
@@ -53,52 +49,49 @@ import com.android.systemui.communal.data.repository.fakeCommunalTutorialReposit
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
+import com.android.systemui.communal.posturing.data.repository.fake
+import com.android.systemui.communal.posturing.data.repository.posturingRepository
+import com.android.systemui.communal.posturing.shared.model.PosturedState
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.fakeDockManager
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.plugins.activityStarter
-import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.statusbar.phone.fakeManagedProfileController
import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.utils.leaks.FakeManagedProfileController
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -107,32 +100,15 @@ import platform.test.runner.parameterized.Parameters
* [CommunalInteractorCommunalDisabledTest].
*/
@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
- @Mock private lateinit var mainUser: UserInfo
- @Mock private lateinit var secondaryUser: UserInfo
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- private lateinit var tutorialRepository: FakeCommunalTutorialRepository
- private lateinit var communalRepository: FakeCommunalSceneRepository
- private lateinit var mediaRepository: FakeCommunalMediaRepository
- private lateinit var widgetRepository: FakeCommunalWidgetRepository
- private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
- private lateinit var userRepository: FakeUserRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
- private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
- private lateinit var sceneInteractor: SceneInteractor
- private lateinit var communalSceneInteractor: CommunalSceneInteractor
- private lateinit var userTracker: FakeUserTracker
- private lateinit var activityStarter: ActivityStarter
- private lateinit var userManager: UserManager
- private lateinit var managedProfileController: FakeManagedProfileController
-
- private lateinit var underTest: CommunalInteractor
+ private val mainUser =
+ UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
+ private val secondaryUser = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val Kosmos.underTest by Kosmos.Fixture { communalInteractor }
init {
mSetFlagsRule.setFlagsParameterization(flags)
@@ -140,128 +116,104 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- tutorialRepository = kosmos.fakeCommunalTutorialRepository
- communalRepository = kosmos.fakeCommunalSceneRepository
- mediaRepository = kosmos.fakeCommunalMediaRepository
- widgetRepository = kosmos.fakeCommunalWidgetRepository
- smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
- userRepository = kosmos.fakeUserRepository
- keyguardRepository = kosmos.fakeKeyguardRepository
- editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
- communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
- sceneInteractor = kosmos.sceneInteractor
- communalSceneInteractor = kosmos.communalSceneInteractor
- userTracker = kosmos.fakeUserTracker
- activityStarter = kosmos.activityStarter
- userManager = kosmos.userManager
- managedProfileController = kosmos.fakeManagedProfileController
-
- whenever(mainUser.isMain).thenReturn(true)
- whenever(secondaryUser.isMain).thenReturn(false)
- whenever(userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
- whenever(userManager.isManagedProfile(anyInt())).thenReturn(false)
- userRepository.setUserInfos(listOf(mainUser, secondaryUser))
+ whenever(kosmos.userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
+ whenever(kosmos.userManager.isManagedProfile(anyInt())).thenReturn(false)
+ kosmos.fakeUserRepository.setUserInfos(listOf(mainUser, secondaryUser))
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
-
- underTest = kosmos.communalInteractor
}
@Test
fun communalEnabled_true() =
- testScope.runTest {
- userRepository.setSelectedUserInfo(mainUser)
- runCurrent()
+ kosmos.runTest {
+ fakeUserRepository.setSelectedUserInfo(mainUser)
assertThat(underTest.isCommunalEnabled.value).isTrue()
}
@Test
fun isCommunalAvailable_storageUnlockedAndMainUser_true() =
- testScope.runTest {
+ kosmos.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
- keyguardRepository.setIsEncryptedOrLockdown(false)
- userRepository.setSelectedUserInfo(mainUser)
- keyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+ fakeUserRepository.setSelectedUserInfo(mainUser)
+ fakeKeyguardRepository.setKeyguardShowing(true)
assertThat(isAvailable).isTrue()
}
@Test
fun isCommunalAvailable_storageLockedAndMainUser_false() =
- testScope.runTest {
+ kosmos.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
- keyguardRepository.setIsEncryptedOrLockdown(true)
- userRepository.setSelectedUserInfo(mainUser)
- keyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setIsEncryptedOrLockdown(true)
+ fakeUserRepository.setSelectedUserInfo(mainUser)
+ fakeKeyguardRepository.setKeyguardShowing(true)
assertThat(isAvailable).isFalse()
}
@Test
fun isCommunalAvailable_storageUnlockedAndSecondaryUser_false() =
- testScope.runTest {
+ kosmos.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
- keyguardRepository.setIsEncryptedOrLockdown(false)
- userRepository.setSelectedUserInfo(secondaryUser)
- keyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+ fakeUserRepository.setSelectedUserInfo(secondaryUser)
+ fakeKeyguardRepository.setKeyguardShowing(true)
assertThat(isAvailable).isFalse()
}
@Test
fun isCommunalAvailable_whenKeyguardShowing_true() =
- testScope.runTest {
+ kosmos.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
- keyguardRepository.setIsEncryptedOrLockdown(false)
- userRepository.setSelectedUserInfo(mainUser)
- keyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+ fakeUserRepository.setSelectedUserInfo(mainUser)
+ fakeKeyguardRepository.setKeyguardShowing(true)
assertThat(isAvailable).isTrue()
}
@Test
fun isCommunalAvailable_communalDisabled_false() =
- testScope.runTest {
+ kosmos.runTest {
mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
- keyguardRepository.setIsEncryptedOrLockdown(false)
- userRepository.setSelectedUserInfo(mainUser)
- keyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+ fakeUserRepository.setSelectedUserInfo(mainUser)
+ fakeKeyguardRepository.setKeyguardShowing(true)
assertThat(isAvailable).isFalse()
}
@Test
fun widget_tutorialCompletedAndWidgetsAvailable_showWidgetContent() =
- testScope.runTest {
+ kosmos.runTest {
// Keyguard showing, and tutorial completed.
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setKeyguardOccluded(false)
+ fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
- userRepository.setUserInfos(userInfos)
- userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
- runCurrent()
+ fakeUserRepository.setUserInfos(userInfos)
+ fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
// Widgets available.
- widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
- widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
- widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
val widgetContent by collectLastValue(underTest.widgetContent)
@@ -356,18 +308,18 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
totalTargets: Int,
expectedSizes: List<CommunalContentSize>,
) =
- testScope.runTest {
+ kosmos.runTest {
// Keyguard showing, and tutorial completed.
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setKeyguardOccluded(false)
+ fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
val targets = mutableListOf<CommunalSmartspaceTimer>()
for (index in 0 until totalTargets) {
targets.add(smartspaceTimer(index.toString()))
}
- smartspaceRepository.setTimers(targets)
+ fakeCommunalSmartspaceRepository.setTimers(targets)
val smartspaceContent by collectLastValue(underTest.ongoingContent(false))
assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
@@ -378,12 +330,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun umo_mediaPlaying_showsUmo() =
- testScope.runTest {
+ kosmos.runTest {
// Tutorial completed.
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
// Media is playing.
- mediaRepository.mediaActive()
+ fakeCommunalMediaRepository.mediaActive()
val umoContent by collectLastValue(underTest.ongoingContent(true))
@@ -394,12 +346,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun umo_mediaPlaying_mediaHostNotVisible_hidesUmo() =
- testScope.runTest {
+ kosmos.runTest {
// Tutorial completed.
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
// Media is playing.
- mediaRepository.mediaActive()
+ fakeCommunalMediaRepository.mediaActive()
val umoContent by collectLastValue(underTest.ongoingContent(false))
assertThat(umoContent?.size).isEqualTo(0)
@@ -409,26 +361,26 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun ongoing_shouldOrderAndSizeByTimestamp() =
- testScope.runTest {
+ kosmos.runTest {
// Keyguard showing, and tutorial completed.
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setKeyguardOccluded(false)
+ fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
// Timer1 started
val timer1 = smartspaceTimer("timer1", timestamp = 1L)
- smartspaceRepository.setTimers(listOf(timer1))
+ fakeCommunalSmartspaceRepository.setTimers(listOf(timer1))
// Umo started
- mediaRepository.mediaActive(timestamp = 2L)
+ fakeCommunalMediaRepository.mediaActive(timestamp = 2L)
// Timer2 started
val timer2 = smartspaceTimer("timer2", timestamp = 3L)
- smartspaceRepository.setTimers(listOf(timer1, timer2))
+ fakeCommunalSmartspaceRepository.setTimers(listOf(timer1, timer2))
// Timer3 started
val timer3 = smartspaceTimer("timer3", timestamp = 4L)
- smartspaceRepository.setTimers(listOf(timer1, timer2, timer3))
+ fakeCommunalSmartspaceRepository.setTimers(listOf(timer1, timer2, timer3))
val ongoingContent by collectLastValue(underTest.ongoingContent(true))
assertThat(ongoingContent?.size).isEqualTo(4)
@@ -447,8 +399,8 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun ctaTile_showsByDefault() =
- testScope.runTest {
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ kosmos.runTest {
+ fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
val ctaTileContent by collectLastValue(underTest.ctaTileContent)
@@ -461,14 +413,13 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun ctaTile_afterDismiss_doesNotShow() =
- testScope.runTest {
+ kosmos.runTest {
// Set to main user, so we can dismiss the tile for the main user.
- val user = userRepository.asMainUser()
- userTracker.set(userInfos = listOf(user), selectedUserIndex = 0)
- runCurrent()
+ val user = fakeUserRepository.asMainUser()
+ fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0)
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalPrefsRepository.setCtaDismissed(user)
+ fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ fakeCommunalPrefsRepository.setCtaDismissed(user)
val ctaTileContent by collectLastValue(underTest.ctaTileContent)
@@ -477,36 +428,30 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun listensToSceneChange() =
- testScope.runTest {
+ kosmos.runTest {
kosmos.setCommunalAvailable(true)
- runCurrent()
- var desiredScene = collectLastValue(underTest.desiredScene)
- runCurrent()
- assertThat(desiredScene()).isEqualTo(CommunalScenes.Blank)
+ val desiredScene by collectLastValue(underTest.desiredScene)
+ assertThat(desiredScene).isEqualTo(CommunalScenes.Blank)
val targetScene = CommunalScenes.Communal
- communalRepository.changeScene(targetScene)
- desiredScene = collectLastValue(underTest.desiredScene)
- runCurrent()
- assertThat(desiredScene()).isEqualTo(targetScene)
+ fakeCommunalSceneRepository.changeScene(targetScene)
+ assertThat(desiredScene).isEqualTo(targetScene)
}
@Test
fun updatesScene() =
- testScope.runTest {
+ kosmos.runTest {
val targetScene = CommunalScenes.Communal
-
underTest.changeScene(targetScene, "test")
- val desiredScene = collectLastValue(communalRepository.currentScene)
- runCurrent()
- assertThat(desiredScene()).isEqualTo(targetScene)
+ val desiredScene by collectLastValue(fakeCommunalSceneRepository.currentScene)
+ assertThat(desiredScene).isEqualTo(targetScene)
}
@Test
fun transitionProgress_onTargetScene_fullProgress() =
- testScope.runTest {
+ kosmos.runTest {
val targetScene = CommunalScenes.Blank
val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
val transitionProgress by collectLastValue(transitionProgressFlow)
@@ -524,7 +469,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun transitionProgress_notOnTargetScene_noProgress() =
- testScope.runTest {
+ kosmos.runTest {
val targetScene = CommunalScenes.Blank
val currentScene = CommunalScenes.Communal
val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
@@ -543,7 +488,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun transitionProgress_transitioningToTrackedScene() =
- testScope.runTest {
+ kosmos.runTest {
val currentScene = CommunalScenes.Communal
val targetScene = CommunalScenes.Blank
val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
@@ -591,7 +536,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun transitionProgress_transitioningAwayFromTrackedScene() =
- testScope.runTest {
+ kosmos.runTest {
val currentScene = CommunalScenes.Blank
val targetScene = CommunalScenes.Communal
val transitionProgressFlow = underTest.transitionProgressToScene(currentScene)
@@ -642,52 +587,42 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun isCommunalShowing() =
- testScope.runTest {
+ kosmos.runTest {
kosmos.setCommunalAvailable(true)
- runCurrent()
- var isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
- runCurrent()
- assertThat(isCommunalShowing()).isEqualTo(false)
+ val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
+ assertThat(isCommunalShowing).isEqualTo(false)
underTest.changeScene(CommunalScenes.Communal, "test")
-
- isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
- runCurrent()
- assertThat(isCommunalShowing()).isEqualTo(true)
+ assertThat(isCommunalShowing).isEqualTo(true)
}
@Test
fun isCommunalShowing_whenSceneContainerDisabled() =
- testScope.runTest {
+ kosmos.runTest {
kosmos.setCommunalAvailable(true)
- runCurrent()
// Verify default is false
val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
- runCurrent()
assertThat(isCommunalShowing).isFalse()
// Verify scene changes with the flag doesn't have any impact
sceneInteractor.changeScene(Scenes.Communal, loggingReason = "")
- runCurrent()
assertThat(isCommunalShowing).isFalse()
// Verify scene changes (without the flag) to communal sets the value to true
underTest.changeScene(CommunalScenes.Communal, "test")
- runCurrent()
assertThat(isCommunalShowing).isTrue()
// Verify scene changes (without the flag) to blank sets the value back to false
underTest.changeScene(CommunalScenes.Blank, "test")
- runCurrent()
assertThat(isCommunalShowing).isFalse()
}
@Test
@EnableSceneContainer
fun isCommunalShowing_whenSceneContainerEnabled() =
- testScope.runTest {
+ kosmos.runTest {
// Verify default is false
val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
assertThat(isCommunalShowing).isFalse()
@@ -704,7 +639,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@EnableSceneContainer
fun isCommunalShowing_whenSceneContainerEnabledAndChangeToLegacyScene() =
- testScope.runTest {
+ kosmos.runTest {
// Verify default is false
val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
assertThat(isCommunalShowing).isFalse()
@@ -720,21 +655,19 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun isIdleOnCommunal() =
- testScope.runTest {
+ kosmos.runTest {
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(CommunalScenes.Blank)
)
- communalRepository.setTransitionState(transitionState)
+ fakeCommunalSceneRepository.setTransitionState(transitionState)
// isIdleOnCommunal is false when not on communal.
val isIdleOnCommunal by collectLastValue(underTest.isIdleOnCommunal)
- runCurrent()
assertThat(isIdleOnCommunal).isEqualTo(false)
// Transition to communal.
transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal)
- runCurrent()
// isIdleOnCommunal is now true since we're on communal.
assertThat(isIdleOnCommunal).isEqualTo(true)
@@ -749,7 +682,6 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
isInitiatedByUserInput = false,
isUserInputOngoing = flowOf(false),
)
- runCurrent()
// isIdleOnCommunal turns false as soon as transition away starts.
assertThat(isIdleOnCommunal).isEqualTo(false)
@@ -757,12 +689,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun isCommunalVisible() =
- testScope.runTest {
+ kosmos.runTest {
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(CommunalScenes.Blank)
)
- communalRepository.setTransitionState(transitionState)
+ fakeCommunalSceneRepository.setTransitionState(transitionState)
// isCommunalVisible is false when not on communal.
val isCommunalVisible by collectLastValue(underTest.isCommunalVisible)
@@ -805,7 +737,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun testShowWidgetEditorStartsActivity() =
- testScope.runTest {
+ kosmos.runTest {
val editModeState by collectLastValue(communalSceneInteractor.editModeState)
underTest.showWidgetEditor()
@@ -816,14 +748,14 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun showWidgetEditor_openWidgetPickerOnStart_startsActivity() =
- testScope.runTest {
+ kosmos.runTest {
underTest.showWidgetEditor(shouldOpenWidgetPickerOnStart = true)
verify(editWidgetsActivityStarter).startActivity(shouldOpenWidgetPickerOnStart = true)
}
@Test
fun navigateToCommunalWidgetSettings_startsActivity() =
- testScope.runTest {
+ kosmos.runTest {
underTest.navigateToCommunalWidgetSettings()
val intentCaptor = argumentCaptor<Intent>()
verify(activityStarter)
@@ -833,23 +765,22 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun filterWidgets_whenUserProfileRemoved() =
- testScope.runTest {
+ kosmos.runTest {
// Keyguard showing, and tutorial completed.
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setKeyguardOccluded(false)
+ fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
// Only main user exists.
val userInfos = listOf(MAIN_USER_INFO)
- userRepository.setUserInfos(userInfos)
- userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
- runCurrent()
+ fakeUserRepository.setUserInfos(userInfos)
+ fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
val widgetContent by collectLastValue(underTest.widgetContent)
// Given three widgets, and one of them is associated with pre-existing work profile.
- widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
- widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
- widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
// One widget is filtered out and the remaining two link to main user id.
assertThat(checkNotNull(widgetContent).size).isEqualTo(2)
@@ -867,17 +798,16 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun widgetContent_inQuietMode() =
- testScope.runTest {
+ kosmos.runTest {
// Keyguard showing, and tutorial completed.
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setKeyguardOccluded(false)
+ fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
// Work profile is set up.
val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
- userRepository.setUserInfos(userInfos)
- userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
- runCurrent()
+ fakeUserRepository.setUserInfos(userInfos)
+ fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
// When work profile is paused.
whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id))))
@@ -885,9 +815,9 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true)
val widgetContent by collectLastValue(underTest.widgetContent)
- widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
- widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
- widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
// The work profile widget is in quiet mode, while other widgets are not.
assertThat(widgetContent).hasSize(3)
@@ -911,23 +841,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun filterWidgets_whenDisallowedByDevicePolicyForWorkProfile() =
- testScope.runTest {
+ kosmos.runTest {
// Keyguard showing, and tutorial completed.
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setKeyguardOccluded(false)
+ fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
- userRepository.setUserInfos(userInfos)
- userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
- runCurrent()
+ fakeUserRepository.setUserInfos(userInfos)
+ fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
+ fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
val widgetContent by collectLastValue(underTest.widgetContent)
// One available work widget, one pending work widget, and one regular available widget.
- widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
- widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id)
- widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+ fakeCommunalWidgetRepository.addPendingWidget(
+ appWidgetId = 2,
+ userId = USER_INFO_WORK.id,
+ )
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
setKeyguardFeaturesDisabled(
USER_INFO_WORK,
@@ -941,23 +873,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun filterWidgets_whenAllowedByDevicePolicyForWorkProfile() =
- testScope.runTest {
+ kosmos.runTest {
// Keyguard showing, and tutorial completed.
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeKeyguardRepository.setKeyguardOccluded(false)
+ fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
- userRepository.setUserInfos(userInfos)
- userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
- runCurrent()
+ fakeUserRepository.setUserInfos(userInfos)
+ fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
+ fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
val widgetContent by collectLastValue(underTest.widgetContent)
// Given three widgets, and one of them is associated with work profile.
- widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
- widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id)
- widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+ fakeCommunalWidgetRepository.addPendingWidget(
+ appWidgetId = 2,
+ userId = USER_INFO_WORK.id,
+ )
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
setKeyguardFeaturesDisabled(
USER_INFO_WORK,
@@ -973,7 +907,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun showCommunalFromOccluded_enteredOccludedFromHub() =
- testScope.runTest {
+ kosmos.runTest {
kosmos.setCommunalAvailable(true)
val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
assertThat(showCommunalFromOccluded).isFalse()
@@ -989,7 +923,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun showCommunalFromOccluded_enteredOccludedFromLockscreen() =
- testScope.runTest {
+ kosmos.runTest {
kosmos.setCommunalAvailable(true)
val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
assertThat(showCommunalFromOccluded).isFalse()
@@ -1005,7 +939,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun showCommunalFromOccluded_communalBecomesUnavailableWhileOccluded() =
- testScope.runTest {
+ kosmos.runTest {
kosmos.setCommunalAvailable(true)
val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
assertThat(showCommunalFromOccluded).isFalse()
@@ -1015,7 +949,6 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
to = KeyguardState.OCCLUDED,
testScope,
)
- runCurrent()
kosmos.setCommunalAvailable(false)
assertThat(showCommunalFromOccluded).isFalse()
@@ -1023,7 +956,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun showCommunalFromOccluded_showBouncerWhileOccluded() =
- testScope.runTest {
+ kosmos.runTest {
kosmos.setCommunalAvailable(true)
val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
assertThat(showCommunalFromOccluded).isFalse()
@@ -1033,7 +966,6 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
to = KeyguardState.OCCLUDED,
testScope,
)
- runCurrent()
kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.OCCLUDED,
to = KeyguardState.PRIMARY_BOUNCER,
@@ -1045,7 +977,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun showCommunalFromOccluded_enteredOccludedFromDreaming() =
- testScope.runTest {
+ kosmos.runTest {
kosmos.setCommunalAvailable(true)
val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
assertThat(showCommunalFromOccluded).isFalse()
@@ -1069,7 +1001,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun dismissDisclaimerSetsDismissedFlag() =
- testScope.runTest {
+ kosmos.runTest {
val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
assertThat(disclaimerDismissed).isFalse()
underTest.setDisclaimerDismissed()
@@ -1078,17 +1010,17 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun dismissDisclaimerTimeoutResetsDismissedFlag() =
- testScope.runTest {
+ kosmos.runTest {
val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
underTest.setDisclaimerDismissed()
assertThat(disclaimerDismissed).isTrue()
- advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS)
+ testScope.advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS)
assertThat(disclaimerDismissed).isFalse()
}
@Test
fun settingSelectedKey_flowUpdated() {
- testScope.runTest {
+ kosmos.runTest {
val key = "test"
val selectedKey by collectLastValue(underTest.selectedKey)
underTest.setSelectedKey(key)
@@ -1098,36 +1030,35 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun unpauseWorkProfileEnablesWorkMode() =
- testScope.runTest {
+ kosmos.runTest {
underTest.unpauseWorkProfile()
- assertThat(managedProfileController.isWorkModeEnabled()).isTrue()
+ assertThat(fakeManagedProfileController.isWorkModeEnabled()).isTrue()
}
@Test
@EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
@DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun resizeWidget_withoutUpdatingOrder() =
- testScope.runTest {
+ kosmos.runTest {
val userInfos = listOf(MAIN_USER_INFO)
- userRepository.setUserInfos(userInfos)
- userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
- runCurrent()
+ fakeUserRepository.setUserInfos(userInfos)
+ fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
// Widgets available.
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 1,
userId = MAIN_USER_INFO.id,
rank = 0,
spanY = CommunalContentSize.FixedSize.HALF.span,
)
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 2,
userId = MAIN_USER_INFO.id,
rank = 1,
spanY = CommunalContentSize.FixedSize.HALF.span,
)
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 3,
userId = MAIN_USER_INFO.id,
rank = 2,
@@ -1159,26 +1090,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID)
fun resizeWidget_withoutUpdatingOrder_responsive() =
- testScope.runTest {
+ kosmos.runTest {
val userInfos = listOf(MAIN_USER_INFO)
- userRepository.setUserInfos(userInfos)
- userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
- runCurrent()
+ fakeUserRepository.setUserInfos(userInfos)
+ fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
// Widgets available.
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 1,
userId = MAIN_USER_INFO.id,
rank = 0,
spanY = 1,
)
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 2,
userId = MAIN_USER_INFO.id,
rank = 1,
spanY = 1,
)
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 3,
userId = MAIN_USER_INFO.id,
rank = 2,
@@ -1211,26 +1141,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
@DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun resizeWidget_andUpdateOrder() =
- testScope.runTest {
+ kosmos.runTest {
val userInfos = listOf(MAIN_USER_INFO)
- userRepository.setUserInfos(userInfos)
- userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
- runCurrent()
+ fakeUserRepository.setUserInfos(userInfos)
+ fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
// Widgets available.
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 1,
userId = MAIN_USER_INFO.id,
rank = 0,
spanY = CommunalContentSize.FixedSize.HALF.span,
)
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 2,
userId = MAIN_USER_INFO.id,
rank = 1,
spanY = CommunalContentSize.FixedSize.HALF.span,
)
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 3,
userId = MAIN_USER_INFO.id,
rank = 2,
@@ -1266,26 +1195,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID)
fun resizeWidget_andUpdateOrder_responsive() =
- testScope.runTest {
+ kosmos.runTest {
val userInfos = listOf(MAIN_USER_INFO)
- userRepository.setUserInfos(userInfos)
- userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
- runCurrent()
+ fakeUserRepository.setUserInfos(userInfos)
+ fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
// Widgets available.
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 1,
userId = MAIN_USER_INFO.id,
rank = 0,
spanY = 1,
)
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 2,
userId = MAIN_USER_INFO.id,
rank = 1,
spanY = 1,
)
- widgetRepository.addWidget(
+ fakeCommunalWidgetRepository.addWidget(
appWidgetId = 3,
userId = MAIN_USER_INFO.id,
rank = 2,
@@ -1318,6 +1246,66 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
.inOrder()
}
+ @Test
+ fun showCommunalWhileCharging() =
+ kosmos.runTest {
+ fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+ fakeUserRepository.setSelectedUserInfo(mainUser)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1,
+ mainUser.id,
+ )
+
+ val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
+ batteryRepository.fake.setDevicePluggedIn(false)
+ assertThat(shouldShowCommunal).isFalse()
+
+ batteryRepository.fake.setDevicePluggedIn(true)
+ assertThat(shouldShowCommunal).isTrue()
+ }
+
+ @Test
+ fun showCommunalWhilePosturedAndCharging() =
+ kosmos.runTest {
+ fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+ fakeUserRepository.setSelectedUserInfo(mainUser)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ 1,
+ mainUser.id,
+ )
+
+ val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
+ batteryRepository.fake.setDevicePluggedIn(true)
+ posturingRepository.fake.setPosturedState(PosturedState.NotPostured)
+ assertThat(shouldShowCommunal).isFalse()
+
+ posturingRepository.fake.setPosturedState(PosturedState.Postured(1f))
+ assertThat(shouldShowCommunal).isTrue()
+ }
+
+ @Test
+ fun showCommunalWhileDocked() =
+ kosmos.runTest {
+ fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+ fakeUserRepository.setSelectedUserInfo(mainUser)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 1, mainUser.id)
+
+ batteryRepository.fake.setDevicePluggedIn(true)
+ fakeDockManager.setIsDocked(false)
+
+ val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
+ assertThat(shouldShowCommunal).isFalse()
+
+ fakeDockManager.setIsDocked(true)
+ fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
+ assertThat(shouldShowCommunal).isTrue()
+ }
+
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
.thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt
index e4916b1a7e46..310bf6486413 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt
@@ -21,19 +21,17 @@ import android.app.admin.devicePolicyManager
import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserManager
-import android.os.userManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
-import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
@@ -48,34 +46,20 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class CommunalSettingsInteractorTest : SysuiTestCase() {
- private lateinit var userManager: UserManager
- private lateinit var userRepository: FakeUserRepository
- private lateinit var userTracker: FakeUserTracker
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- private lateinit var underTest: CommunalSettingsInteractor
+ private val Kosmos.underTest by Kosmos.Fixture { communalSettingsInteractor }
@Before
fun setUp() {
- userManager = kosmos.userManager
- userRepository = kosmos.fakeUserRepository
- userTracker = kosmos.fakeUserTracker
-
val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
- userRepository.setUserInfos(userInfos)
- userTracker.set(
- userInfos = userInfos,
- selectedUserIndex = 0,
- )
-
- underTest = kosmos.communalSettingsInteractor
+ kosmos.fakeUserRepository.setUserInfos(userInfos)
+ kosmos.fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
}
@Test
fun filterUsers_dontFilteredUsersWhenAllAreAllowed() =
- testScope.runTest {
+ kosmos.runTest {
// If no users have any keyguard features disabled...
val disallowedUser by
collectLastValue(underTest.workProfileUserDisallowedByDevicePolicy)
@@ -85,11 +69,11 @@ class CommunalSettingsInteractorTest : SysuiTestCase() {
@Test
fun filterUsers_filterWorkProfileUserWhenDisallowed() =
- testScope.runTest {
+ kosmos.runTest {
// If the work profile user has keyguard widgets disabled...
setKeyguardFeaturesDisabled(
USER_INFO_WORK,
- DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
+ DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL,
)
// ...then the disallowed user match the work profile
val disallowedUser by
@@ -102,7 +86,7 @@ class CommunalSettingsInteractorTest : SysuiTestCase() {
whenever(
kosmos.devicePolicyManager.getKeyguardDisabledFeatures(
anyOrNull(),
- ArgumentMatchers.eq(user.id)
+ ArgumentMatchers.eq(user.id),
)
)
.thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalLockIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalLockIconViewModelTest.kt
new file mode 100644
index 000000000000..c535831a27e7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalLockIconViewModelTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2025 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.communal.view.viewmodel
+
+import android.platform.test.flag.junit.FlagsParameterization
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalLockIconViewModel
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceTimeBy
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalLockIconViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
+ CommunalLockIconViewModel(
+ context = context,
+ configurationInteractor = configurationInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ keyguardInteractor = keyguardInteractor,
+ keyguardViewController = { statusBarKeyguardViewManager },
+ deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+ accessibilityInteractor = accessibilityInteractor,
+ )
+ }
+
+ @Test
+ fun isLongPressEnabled_unlocked() =
+ kosmos.runTest {
+ val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+ setLockscreenDismissible()
+ assertThat(isLongPressEnabled).isTrue()
+ }
+
+ @Test
+ fun isLongPressEnabled_lock() =
+ kosmos.runTest {
+ val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+ if (!SceneContainerFlag.isEnabled) {
+ fakeKeyguardRepository.setKeyguardDismissible(false)
+ }
+ assertThat(isLongPressEnabled).isFalse()
+ }
+
+ @Test
+ fun iconType_locked() =
+ kosmos.runTest {
+ val viewAttributes by collectLastValue(underTest.viewAttributes)
+ if (!SceneContainerFlag.isEnabled) {
+ fakeKeyguardRepository.setKeyguardDismissible(false)
+ }
+ assertThat(viewAttributes?.type).isEqualTo(DeviceEntryIconView.IconType.LOCK)
+ }
+
+ @Test
+ fun iconType_unlocked() =
+ kosmos.runTest {
+ val viewAttributes by collectLastValue(underTest.viewAttributes)
+ setLockscreenDismissible()
+ assertThat(viewAttributes?.type).isEqualTo(DeviceEntryIconView.IconType.UNLOCK)
+ }
+
+ private suspend fun Kosmos.setLockscreenDismissible() {
+ if (SceneContainerFlag.isEnabled) {
+ // Need to set up a collection for the authentication to be propagated.
+ backgroundScope.launch { kosmos.deviceUnlockedInteractor.deviceUnlockStatus.collect {} }
+ assertThat(
+ kosmos.authenticationInteractor.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN
+ )
+ )
+ .isEqualTo(AuthenticationResult.SUCCEEDED)
+ } else {
+ fakeKeyguardRepository.setKeyguardDismissible(true)
+ }
+ testScope.advanceTimeBy(
+ DeviceEntryIconViewModel.UNLOCKED_DELAY_MS * 2
+ ) // wait for unlocked delay
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
index 6c955bf1818d..5fd480f90ac9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -176,14 +176,14 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
}
@Test
- fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() =
+ fun nonPowerButtonFPS_coExFaceFailure_vibrateError() =
testScope.runTest {
val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
enrollFace()
runCurrent()
faceFailure()
- assertThat(playErrorHaptic).isNull()
+ assertThat(playErrorHaptic).isNotNull()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 7478464772a4..57ac90648f33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -117,8 +117,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
mMediaDevices.add(mMediaDevice1);
mMediaDevices.add(mMediaDevice2);
- mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1));
- mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2));
+ mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1, true));
+ mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2, false));
mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
@@ -779,4 +779,120 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
mViewHolder.getDrawableId(false /* isInputDevice */, false /* isMutedVolumeIcon */))
.isEqualTo(R.drawable.media_output_icon_volume);
}
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void multipleSelectedDevices_verifySessionView() {
+ initializeSession();
+
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_SESSION_NAME);
+ assertThat(mViewHolder.mSeekBar.getVolume()).isEqualTo(TEST_CURRENT_VOLUME);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void multipleSelectedDevices_verifyCollapsedView() {
+ initializeSession();
+
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mItemLayout.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void multipleSelectedDevices_expandIconClicked_verifyInitialView() {
+ initializeSession();
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ mViewHolder.mEndTouchArea.performClick();
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void multipleSelectedDevices_expandIconClicked_verifyCollapsedView() {
+ initializeSession();
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ mViewHolder.mEndTouchArea.performClick();
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void deviceCanNotBeDeselected_verifyView() {
+ List<MediaDevice> selectedDevices = new ArrayList<>();
+ selectedDevices.add(mMediaDevice1);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectedDevices);
+ when(mMediaSwitchingController.getSelectedMediaDevice()).thenReturn(selectedDevices);
+ when(mMediaSwitchingController.getDeselectableMediaDevice()).thenReturn(new ArrayList<>());
+
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ }
+
+ private void initializeSession() {
+ when(mMediaSwitchingController.getSessionVolumeMax()).thenReturn(TEST_MAX_VOLUME);
+ when(mMediaSwitchingController.getSessionVolume()).thenReturn(TEST_CURRENT_VOLUME);
+ when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME);
+
+ List<MediaDevice> selectedDevices = new ArrayList<>();
+ selectedDevices.add(mMediaDevice1);
+ selectedDevices.add(mMediaDevice2);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectedDevices);
+ when(mMediaSwitchingController.getSelectedMediaDevice()).thenReturn(selectedDevices);
+ when(mMediaSwitchingController.getDeselectableMediaDevice()).thenReturn(selectedDevices);
+
+ mMediaOutputAdapter.updateItems();
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
index ff00bfb540c6..63942072f3a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediaprojection.data.repository
import android.hardware.display.displayManager
+import android.media.projection.MediaProjectionEvent
import android.media.projection.MediaProjectionInfo
import android.media.projection.StopReason
import android.os.Binder
@@ -32,8 +33,11 @@ import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHI
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
@@ -42,7 +46,7 @@ import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionMana
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -55,7 +59,7 @@ import org.mockito.kotlin.whenever
@SmallTest
class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
- private val kosmos = taskSwitcherKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
@@ -345,4 +349,40 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
verify(fakeMediaProjectionManager.mediaProjectionManager)
.stopActiveProjection(StopReason.STOP_QS_TILE)
}
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun projectionStartedDuringCallAndActivePostCallEvent_flagEnabled_emitsUnit() =
+ kosmos.runTest {
+ val projectionStartedDuringCallAndActivePostCallEvent by
+ collectLastValue(repo.projectionStartedDuringCallAndActivePostCallEvent)
+
+ fakeMediaProjectionManager.dispatchEvent(
+ PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL_EVENT
+ )
+
+ assertThat(projectionStartedDuringCallAndActivePostCallEvent).isEqualTo(Unit)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun projectionStartedDuringCallAndActivePostCallEvent_flagDisabled_doesNotEmit() =
+ testScope.runTest {
+ val projectionStartedDuringCallAndActivePostCallEvent by
+ collectLastValue(repo.projectionStartedDuringCallAndActivePostCallEvent)
+
+ fakeMediaProjectionManager.dispatchEvent(
+ PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL_EVENT
+ )
+
+ assertThat(projectionStartedDuringCallAndActivePostCallEvent).isNull()
+ }
+
+ companion object {
+ private val PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL_EVENT =
+ MediaProjectionEvent(
+ MediaProjectionEvent.PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL,
+ /* timestampMillis= */ 100L,
+ )
+ }
}
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 ef70305a3f47..af30e435da73 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
@@ -1149,7 +1149,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
@Test
@DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
- fun skipsFaceErrorHaptics_nonSfps_coEx() =
+ fun playsFaceErrorHaptics_nonSfps_coEx() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
@@ -1161,14 +1161,15 @@ class SceneContainerStartableTest : SysuiTestCase() {
underTest.start()
updateFaceAuthStatus(isSuccess = false)
- assertThat(playErrorHaptic).isNull()
- verify(vibratorHelper, never()).vibrateAuthError(anyString())
+ assertThat(playErrorHaptic).isNotNull()
+ assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+ verify(vibratorHelper).vibrateAuthError(anyString())
verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
}
@Test
@EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
- fun skipsMSDLFaceErrorHaptics_nonSfps_coEx() =
+ fun playsMSDLFaceErrorHaptics_nonSfps_coEx() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
@@ -1180,9 +1181,10 @@ class SceneContainerStartableTest : SysuiTestCase() {
underTest.start()
updateFaceAuthStatus(isSuccess = false)
- assertThat(playErrorHaptic).isNull()
- assertThat(msdlPlayer.latestTokenPlayed).isNull()
- assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ assertThat(playErrorHaptic).isNotNull()
+ assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 2020d0dcb041..3d3178793a09 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -460,17 +460,17 @@ public class CommandQueueTest extends SysuiTestCase {
}
@Test
- public void testOnDisplayReady() {
- mCommandQueue.onDisplayReady(DEFAULT_DISPLAY);
+ public void testonDisplayAddSystemDecorations() {
+ mCommandQueue.onDisplayAddSystemDecorations(DEFAULT_DISPLAY);
waitForIdleSync();
- verify(mCallbacks).onDisplayReady(eq(DEFAULT_DISPLAY));
+ verify(mCallbacks).onDisplayAddSystemDecorations(eq(DEFAULT_DISPLAY));
}
@Test
- public void testOnDisplayReadyForSecondaryDisplay() {
- mCommandQueue.onDisplayReady(SECONDARY_DISPLAY);
+ public void testonDisplayAddSystemDecorationsForSecondaryDisplay() {
+ mCommandQueue.onDisplayAddSystemDecorations(SECONDARY_DISPLAY);
waitForIdleSync();
- verify(mCallbacks).onDisplayReady(eq(SECONDARY_DISPLAY));
+ verify(mCallbacks).onDisplayAddSystemDecorations(eq(SECONDARY_DISPLAY));
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 0a0564994e69..a79f4085ec6d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -45,6 +45,7 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
import java.util.function.Consumer
import org.junit.Before
import org.junit.Rule
@@ -66,7 +67,6 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
-import java.util.Optional
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -150,24 +150,23 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Test
fun setupListeners() {
- verify(dumpManager).registerCriticalDumpable(
- anyString(), eq(notificationShadeDepthController)
- )
+ verify(dumpManager)
+ .registerCriticalDumpable(anyString(), eq(notificationShadeDepthController))
}
@Test
fun onPanelExpansionChanged_apliesBlur_ifShade() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@Test
fun onPanelExpansionChanged_animatesBlurIn_ifShade() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 0.01f, expanded = false, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 0.01f, expanded = false, tracking = false)
+ )
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@@ -176,27 +175,27 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
onPanelExpansionChanged_animatesBlurIn_ifShade()
clearInvocations(shadeAnimation)
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 0f, expanded = false, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 0f, expanded = false, tracking = false)
+ )
verify(shadeAnimation).animateTo(eq(0))
}
@Test
fun onPanelExpansionChanged_animatesBlurOut_ifFlick() {
- val event =
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false)
+ val event = ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
onPanelExpansionChanged_apliesBlur_ifShade()
clearInvocations(shadeAnimation)
notificationShadeDepthController.onPanelExpansionChanged(event)
verify(shadeAnimation, never()).animateTo(anyInt())
notificationShadeDepthController.onPanelExpansionChanged(
- event.copy(fraction = 0.9f, tracking = true))
+ event.copy(fraction = 0.9f, tracking = true)
+ )
verify(shadeAnimation, never()).animateTo(anyInt())
notificationShadeDepthController.onPanelExpansionChanged(
- event.copy(fraction = 0.8f, tracking = false))
+ event.copy(fraction = 0.8f, tracking = false)
+ )
verify(shadeAnimation).animateTo(eq(0))
}
@@ -205,16 +204,14 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
onPanelExpansionChanged_animatesBlurOut_ifFlick()
clearInvocations(shadeAnimation)
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 0.6f, expanded = true, tracking = true))
+ ShadeExpansionChangeEvent(fraction = 0.6f, expanded = true, tracking = true)
+ )
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@Test
fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() {
- val event =
- ShadeExpansionChangeEvent(
- fraction = 0.5f, expanded = true, tracking = true)
+ val event = ShadeExpansionChangeEvent(fraction = 0.5f, expanded = true, tracking = true)
notificationShadeDepthController.panelPullDownMinFraction = 0.5f
notificationShadeDepthController.onPanelExpansionChanged(event)
assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0f)
@@ -241,8 +238,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
statusBarState = StatusBarState.KEYGUARD
notificationShadeDepthController.qsPanelExpansion = 1f
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
}
@@ -252,8 +249,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
statusBarState = StatusBarState.KEYGUARD
notificationShadeDepthController.qsPanelExpansion = 0.25f
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController)
.setNotificationShadeZoom(eq(ShadeInterpolation.getNotificationScrimAlpha(0.25f)))
@@ -264,8 +261,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
enableSplitShade()
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController).setNotificationShadeZoom(0f)
@@ -276,8 +273,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
disableSplitShade()
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController).setNotificationShadeZoom(floatThat { it > 0 })
@@ -354,8 +351,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Test
fun updateBlurCallback_setsBlur_whenExpanded() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
@@ -364,8 +361,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Test
fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = true
notificationShadeDepthController.updateBlurCallback.doFrame(0)
@@ -373,7 +370,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+ @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP, Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
fun ignoreShadeBlurUntilHidden_schedulesFrame() {
notificationShadeDepthController.blursDisabledForAppLaunch = true
verify(blurUtils).prepareBlur(any(), anyInt())
@@ -391,8 +388,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Test
fun ignoreBlurForUnlock_ignores() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = false
@@ -408,8 +405,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Test
fun ignoreBlurForUnlock_doesNotIgnore() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = false
@@ -435,14 +432,14 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+ @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP, Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
fun brightnessMirror_hidesShadeBlur() {
// Brightness mirror is fully visible
`when`(brightnessSpring.ratio).thenReturn(1f)
// And shade is blurred
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
index dea3d1f68ce5..0dc2759d9a4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
@@ -22,21 +22,26 @@ import android.content.packageManager
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.doAnswer
import org.mockito.kotlin.any
@@ -44,8 +49,9 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@SmallTest
+@RunWith(AndroidJUnit4::class)
class MediaProjectionChipInteractorTest : SysuiTestCase() {
- private val kosmos = Kosmos().also { it.testCase = this }
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
@@ -57,6 +63,26 @@ class MediaProjectionChipInteractorTest : SysuiTestCase() {
private val underTest = kosmos.mediaProjectionChipInteractor
@Test
+ fun projectionStartedDuringCallAndActivePostCallEvent_eventEmitted_isUnit() =
+ kosmos.runTest {
+ val latest by
+ collectLastValue(underTest.projectionStartedDuringCallAndActivePostCallEvent)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ assertThat(latest).isEqualTo(Unit)
+ }
+
+ @Test
+ fun projectionStartedDuringCallAndActivePostCallEvent_noEventEmitted_isNull() =
+ kosmos.runTest {
+ val latest by
+ collectLastValue(underTest.projectionStartedDuringCallAndActivePostCallEvent)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
fun projection_notProjectingState_isNotProjecting() =
testScope.runTest {
val latest by collectLastValue(underTest.projection)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 36fc5aa16407..5a66888e4da4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -31,9 +31,10 @@ import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
@@ -41,6 +42,7 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.CAST_TO_OTHER_DEVICES_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndGenericShareToAppDialogDelegate
import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareScreenToAppDialogDelegate
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
@@ -51,6 +53,7 @@ import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
@@ -58,6 +61,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mockito.times
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
@@ -68,7 +72,7 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShareToAppChipViewModelTest : SysuiTestCase() {
- private val kosmos = Kosmos().also { it.testCase = this }
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
private val systemClock = kosmos.fakeSystemClock
@@ -89,9 +93,11 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest = kosmos.shareToAppChipViewModel
+ private val mockDialog = mock<SystemUIDialog>()
@Before
fun setUp() {
+ underTest.start()
setUpPackageManagerForMediaProjection(kosmos)
whenever(kosmos.mockSystemUIDialogFactory.create(any<EndShareScreenToAppDialogDelegate>()))
@@ -101,6 +107,196 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun chip_flagEnabled_projectionStartedDuringCallAndActivePostCallEventEmitted_chipHidden() =
+ kosmos.runTest {
+ val latestChip by collectLastValue(underTest.chip)
+
+ // Set mediaProjectionState to Projecting
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ // Verify the chip is initially shown
+ assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify the chip is hidden
+ assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun chip_flagDisabled_projectionStartedDuringCallAndActivePostCallEventEmitted_chipRemainsVisible() =
+ kosmos.runTest {
+ val latestChip by collectLastValue(underTest.chip)
+
+ // Set mediaProjectionState to Projecting
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ // Verify the chip is initially shown
+ assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Chip is still shown
+ assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_flagEnabled_initialState_isHidden() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.stopDialogToShow)
+
+ assertThat(latest).isEqualTo(MediaProjectionStopDialogModel.Hidden)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_flagDisabled_projectionStartedDuringCallAndActivePostCallEventEmitted_dialogRemainsHidden() =
+ kosmos.runTest {
+ val latestStopDialogModel by collectLastValue(underTest.stopDialogToShow)
+
+ // Set mediaProjectionRepo state to Projecting
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that no dialog is shown
+ assertThat(latestStopDialogModel).isEqualTo(MediaProjectionStopDialogModel.Hidden)
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_notProjectingState_flagEnabled_remainsHidden() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.stopDialogToShow)
+
+ // Set the state to not projecting
+ mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that the dialog remains hidden
+ assertThat(latest).isEqualTo(MediaProjectionStopDialogModel.Hidden)
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END,
+ FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP,
+ )
+ fun stopDialog_projectingAudio_flagEnabled_eventEmitted_showsGenericStopDialog() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.stopDialogToShow)
+
+ // Set the state to projecting audio
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that the generic dialog is shown
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+ val dialogDelegate = (latest as MediaProjectionStopDialogModel.Shown).dialogDelegate
+ assertThat(dialogDelegate).isInstanceOf(EndGenericShareToAppDialogDelegate::class.java)
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_projectingEntireScreen_flagEnabled_eventEmitted_showsShareScreenToAppStopDialog() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.stopDialogToShow)
+
+ // Set the state to projecting the entire screen
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Hidden::class.java)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that the dialog is shown
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+ val dialogDelegate = (latest as MediaProjectionStopDialogModel.Shown).dialogDelegate
+ assertThat(dialogDelegate).isInstanceOf(EndShareScreenToAppDialogDelegate::class.java)
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_projectingEntireScreen_eventEmitted_hasCancelBehaviour() =
+ kosmos.runTest {
+ val latestDialogModel by collectLastValue(underTest.stopDialogToShow)
+
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that the dialog is shown
+ assertThat(latestDialogModel)
+ .isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+
+ val dialogModel = latestDialogModel as MediaProjectionStopDialogModel.Shown
+
+ whenever(dialogModel.dialogDelegate.createDialog()).thenReturn(mockDialog)
+
+ dialogModel.createAndShowDialog()
+
+ // Verify dialog is shown
+ verify(mockDialog).show()
+
+ // Verify dialog is hidden when dialog is cancelled
+ argumentCaptor<DialogInterface.OnCancelListener>().apply {
+ verify(mockDialog).setOnCancelListener(capture())
+ firstValue.onCancel(mockDialog)
+ }
+ assertThat(underTest.stopDialogToShow.value)
+ .isEqualTo(MediaProjectionStopDialogModel.Hidden)
+
+ verify(mockDialog, times(1)).setOnCancelListener(any())
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_projectingEntireScreen_eventEmitted_hasDismissBehaviour() =
+ kosmos.runTest {
+ val latestDialogModel by collectLastValue(underTest.stopDialogToShow)
+
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that the dialog is shown
+ assertThat(latestDialogModel)
+ .isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+
+ val dialogModel = latestDialogModel as MediaProjectionStopDialogModel.Shown
+
+ whenever(dialogModel.dialogDelegate.createDialog()).thenReturn(mockDialog)
+
+ // Simulate showing the dialog
+ dialogModel.createAndShowDialog()
+
+ // Verify dialog is shown
+ verify(mockDialog).show()
+
+ // Verify dialog is hidden when dialog is dismissed
+ argumentCaptor<DialogInterface.OnDismissListener>().apply {
+ verify(mockDialog).setOnDismissListener(capture())
+ firstValue.onDismiss(mockDialog)
+ }
+ assertThat(underTest.stopDialogToShow.value)
+ .isEqualTo(MediaProjectionStopDialogModel.Hidden)
+
+ verify(mockDialog, times(1)).setOnDismissListener(any())
+ }
+
+ @Test
fun chip_notProjectingState_isHidden() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index a4b6a841d61b..7a33cbe761c1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -289,7 +289,11 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
fun primaryChip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() =
testScope.runTest {
screenRecordState.value = ScreenRecordModel.Recording
- mediaProjectionState.value = MediaProjectionState.NotProjecting
+ mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(
+ NORMAL_PACKAGE,
+ hostDeviceName = "Recording Display",
+ )
callRepo.setOngoingCallState(OngoingCallModel.NoCall)
val latest by collectLastValue(underTest.primaryChip)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
index 39c42f183481..28b2ee8dde06 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -269,6 +269,36 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
}
@Test
+ fun testOpenAndCloseGutsWithoutSave() {
+ val guts = spy(NotificationGuts(mContext))
+ whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
+ handler.post(((invocation.arguments[0] as Runnable)))
+ null
+ }
+
+ // Test doesn't support animation since the guts view is not attached.
+ doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any())
+
+ val realRow = createTestNotificationRow()
+ val menuItem = createTestMenuItem(realRow)
+
+ val row = spy(realRow)
+ whenever(row.windowToken).thenReturn(Binder())
+ whenever(row.guts).thenReturn(guts)
+
+ assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+ executor.runAllReady()
+ verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
+
+ gutsManager.closeAndUndoGuts()
+
+ verify(guts).closeControls(anyInt(), anyInt(), eq(false), eq(false))
+ verify(row, times(1)).setGutsView(any<MenuItem>())
+ executor.runAllReady()
+ verify(headsUpManager).setGutsShown(realRow.entry, false)
+ }
+
+ @Test
fun testLockscreenShadeVisible_visible_gutsNotClosed() =
testScope.runTest {
// First, start out lockscreen or shade as not visible
@@ -377,52 +407,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
}
@Test
- fun testChangeDensityOrFontScale() {
- val guts = spy(NotificationGuts(mContext))
- whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
- handler.post(((invocation.arguments[0] as Runnable)))
- null
- }
-
- // Test doesn't support animation since the guts view is not attached.
- doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
-
- val realRow = createTestNotificationRow()
- val menuItem = createTestMenuItem(realRow)
-
- val row = spy(realRow)
-
- whenever(row.windowToken).thenReturn(Binder())
- whenever(row.guts).thenReturn(guts)
- doNothing().whenever(row).ensureGutsInflated()
-
- val realEntry = realRow.entry
- val entry = spy(realEntry)
-
- whenever(entry.row).thenReturn(row)
- whenever(entry.guts).thenReturn(guts)
-
- assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
- executor.runAllReady()
- verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
-
- // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
- verify(row).setGutsView(any<MenuItem>())
-
- row.onDensityOrFontScaleChanged()
- gutsManager.onDensityOrFontScaleChanged(entry)
-
- executor.runAllReady()
-
- gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
-
- verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean())
-
- // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
- verify(row, times(2)).setGutsView(any<MenuItem>())
- }
-
- @Test
fun testAppOpsSettingsIntent_camera() {
val row = createTestNotificationRow()
val ops = ArraySet<Int>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
index 6435e8203d3d..af67a04d2f2a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
@@ -16,29 +16,44 @@
package com.android.systemui.statusbar.notification.row;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableResources;
-import android.util.KeyValueListParser;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
import androidx.test.annotation.UiThreadTest;
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.animation.AnimatorTestRule;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.res.R;
+import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -46,8 +61,12 @@ import java.util.ArrayList;
public class NotificationSnoozeTest extends SysuiTestCase {
private static final int RES_DEFAULT = 2;
private static final int[] RES_OPTIONS = {1, 2, 3};
- private NotificationSnooze mNotificationSnooze;
- private KeyValueListParser mMockParser;
+ private final NotificationSwipeActionHelper mSnoozeListener = mock(
+ NotificationSwipeActionHelper.class);
+ private NotificationSnooze mUnderTest;
+
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
@Before
public void setUp() throws Exception {
@@ -56,62 +75,117 @@ public class NotificationSnoozeTest extends SysuiTestCase {
TestableResources resources = mContext.getOrCreateTestableResources();
resources.addOverride(R.integer.config_notification_snooze_time_default, RES_DEFAULT);
resources.addOverride(R.array.config_notification_snooze_times, RES_OPTIONS);
- mNotificationSnooze = new NotificationSnooze(mContext, null);
- mMockParser = mock(KeyValueListParser.class);
+
+ mUnderTest = new NotificationSnooze(mContext, null);
+ mUnderTest.setSnoozeListener(mSnoozeListener);
+ mUnderTest.mExpandButton = mock(ImageView.class);
+ mUnderTest.mSnoozeView = mock(View.class);
+ mUnderTest.mSelectedOptionText = mock(TextView.class);
+ mUnderTest.mDivider = mock(View.class);
+ mUnderTest.mSnoozeOptionContainer = mock(ViewGroup.class);
+ mUnderTest.mSnoozeOptions = mock(List.class);
+ }
+
+ @After
+ public void tearDown() {
+ // Make sure all animations are finished
+ mAnimatorTestRule.advanceTimeBy(1000L);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NOTIFICATION_UNDO_GUTS_ON_CONFIG_CHANGED)
+ public void closeControls_withoutSave_performsUndo() {
+ ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions();
+ mUnderTest.mSelectedOption = options.getFirst();
+ mUnderTest.showSnoozeOptions(true);
+
+ assertThat(
+ mUnderTest.handleCloseControls(/* save = */ false, /* force = */ false)).isFalse();
+
+ assertThat(mUnderTest.mSelectedOption).isNull();
+ assertThat(mUnderTest.isExpanded()).isFalse();
+ verify(mSnoozeListener, times(0)).snooze(any(), any());
+ }
+
+ @Test
+ public void closeControls_whenExpanded_collapsesOptions() {
+ ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions();
+ mUnderTest.mSelectedOption = options.getFirst();
+ mUnderTest.showSnoozeOptions(true);
+
+ assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ false)).isTrue();
+
+ assertThat(mUnderTest.mSelectedOption).isNotNull();
+ assertThat(mUnderTest.isExpanded()).isFalse();
+ }
+
+ @Test
+ public void closeControls_whenCollapsed_commitsChanges() {
+ ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions();
+ mUnderTest.mSelectedOption = options.getFirst();
+
+ assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ false)).isTrue();
+
+ verify(mSnoozeListener).snooze(any(), any());
+ }
+
+ @Test
+ public void closeControls_withForce_returnsFalse() {
+ assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ true)).isFalse();
}
@Test
- public void testGetOptionsWithNoConfig() throws Exception {
- ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ public void testGetOptionsWithNoConfig() {
+ ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions();
assertEquals(3, result.size());
assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order
assertEquals(2, result.get(1).getMinutesToSnoozeFor());
assertEquals(3, result.get(2).getMinutesToSnoozeFor());
- assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+ assertEquals(2, mUnderTest.getDefaultOption().getMinutesToSnoozeFor());
}
@Test
- public void testGetOptionsWithInvalidConfig() throws Exception {
+ public void testGetOptionsWithInvalidConfig() {
Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
"this is garbage");
- ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions();
assertEquals(3, result.size());
assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order
assertEquals(2, result.get(1).getMinutesToSnoozeFor());
assertEquals(3, result.get(2).getMinutesToSnoozeFor());
- assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+ assertEquals(2, mUnderTest.getDefaultOption().getMinutesToSnoozeFor());
}
@Test
- public void testGetOptionsWithValidDefault() throws Exception {
+ public void testGetOptionsWithValidDefault() {
Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
"default=10,options_array=4:5:6:7");
- ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
- assertNotNull(mNotificationSnooze.getDefaultOption()); // pick one
+ ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions();
+ assertNotNull(mUnderTest.getDefaultOption()); // pick one
}
@Test
- public void testGetOptionsWithValidConfig() throws Exception {
+ public void testGetOptionsWithValidConfig() {
Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
"default=6,options_array=4:5:6:7");
- ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions();
assertEquals(4, result.size());
assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order
assertEquals(5, result.get(1).getMinutesToSnoozeFor());
assertEquals(6, result.get(2).getMinutesToSnoozeFor());
assertEquals(7, result.get(3).getMinutesToSnoozeFor());
- assertEquals(6, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+ assertEquals(6, mUnderTest.getDefaultOption().getMinutesToSnoozeFor());
}
@Test
- public void testGetOptionsWithLongConfig() throws Exception {
+ public void testGetOptionsWithLongConfig() {
Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
"default=6,options_array=4:5:6:7:8:9:10:11:12:13:14:15:16:17");
- ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+ ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions();
assertTrue(result.size() > 3);
assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order
assertEquals(5, result.get(1).getMinutesToSnoozeFor());
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index a1c910d48cef..0223484dae41 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -20,6 +20,7 @@ import android.graphics.Color
import android.graphics.Rect
import android.view.View
import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
@@ -46,6 +47,9 @@ class FakeHomeStatusBarViewModel(
override val statusBarPopupChips = MutableStateFlow(emptyList<PopupChipModel.Shown>())
+ override val mediaProjectionStopDialogDueToCallEndedState =
+ MutableStateFlow(MediaProjectionStopDialogModel.Hidden)
+
override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
override val shouldHomeStatusBarBeVisible = MutableStateFlow(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index e74d009bb909..46f625fd9ba8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -45,8 +45,8 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.collectValues
import com.android.systemui.kosmos.runTest
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.assertLogsWtf
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
@@ -58,7 +58,9 @@ import com.android.systemui.screenrecord.data.repository.screenRecordRepository
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip
@@ -89,7 +91,7 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -98,9 +100,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class HomeStatusBarViewModelImplTest : SysuiTestCase() {
- private val kosmos by lazy {
- testKosmos().also { it.testDispatcher = UnconfinedTestDispatcher() }
- }
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val Kosmos.underTest by Kosmos.Fixture { kosmos.homeStatusBarViewModel }
@Before
@@ -112,6 +112,55 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
fun addDisplays() = runBlocking { kosmos.displayRepository.fake.addDisplay(DEFAULT_DISPLAY) }
@Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun mediaProjectionStopDialogDueToCallEndedState_initiallyHidden() =
+ kosmos.runTest {
+ shareToAppChipViewModel.start()
+ val latest by collectLastValue(underTest.mediaProjectionStopDialogDueToCallEndedState)
+
+ // Verify that the stop dialog is initially hidden
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Hidden::class.java)
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun mediaProjectionStopDialogDueToCallEndedState_flagEnabled_mediaIsProjecting_projectionStartedDuringCallAndActivePostCallEventEmitted_isShown() =
+ kosmos.runTest {
+ shareToAppChipViewModel.start()
+
+ val latest by
+ collectLastValue(
+ homeStatusBarViewModel.mediaProjectionStopDialogDueToCallEndedState
+ )
+
+ fakeMediaProjectionRepository.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun mediaProjectionStopDialogDueToCallEndedState_flagDisabled_mediaIsProjecting_projectionStartedDuringCallAndActivePostCallEventEmitted_isHidden() =
+ kosmos.runTest {
+ shareToAppChipViewModel.start()
+
+ val latest by
+ collectLastValue(
+ homeStatusBarViewModel.mediaProjectionStopDialogDueToCallEndedState
+ )
+
+ fakeMediaProjectionRepository.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Hidden::class.java)
+ }
+
+ @Test
fun isTransitioningFromLockscreenToOccluded_started_isTrue() =
kosmos.runTest {
val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index 09be93de9f3e..ea91b7a9d6e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.unfold
import android.content.Context
import android.content.res.Resources
import android.hardware.devicestate.DeviceStateManager
+import android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
@@ -27,16 +28,20 @@ import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImp
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.defaultDeviceState
import com.android.systemui.deviceStateManager
-import com.android.systemui.display.data.repository.DeviceStateRepository
import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.FOLDED
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.HALF_FOLDED
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.UNFOLDED
+import com.android.systemui.display.data.repository.fakeDeviceStateRepository
import com.android.systemui.foldedDeviceStateList
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.shared.model.ScreenPowerState
-import com.android.systemui.power.shared.model.WakeSleepReason
-import com.android.systemui.power.shared.model.WakefulnessModel
-import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState
+import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_OFF
+import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_CLOSED
@@ -45,7 +50,7 @@ import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLate
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import com.android.systemui.unfoldedDeviceState
-import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
@@ -77,14 +82,15 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
private lateinit var displaySwitchLatencyTracker: DisplaySwitchLatencyTracker
@Captor private lateinit var loggerArgumentCaptor: ArgumentCaptor<DisplaySwitchLatencyEvent>
+ private val kosmos = Kosmos()
private val mockContext = mock<Context>()
private val resources = mock<Resources>()
- private val foldStateRepository = mock<DeviceStateRepository>()
- private val powerInteractor = mock<PowerInteractor>()
- private val animationStatusRepository = mock<AnimationStatusRepository>()
+ private val foldStateRepository = kosmos.fakeDeviceStateRepository
+ private val powerInteractor = PowerInteractorFactory.create().powerInteractor
+ private val animationStatusRepository = kosmos.fakeAnimationStatusRepository
private val keyguardInteractor = mock<KeyguardInteractor>()
private val displaySwitchLatencyLogger = mock<DisplaySwitchLatencyLogger>()
- private val kosmos = Kosmos()
+
private val deviceStateManager = kosmos.deviceStateManager
private val closedDeviceState = kosmos.foldedDeviceStateList.first()
private val openDeviceState = kosmos.unfoldedDeviceState
@@ -94,12 +100,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
private val testDispatcher: TestDispatcher = StandardTestDispatcher()
private val testScope: TestScope = TestScope(testDispatcher)
- private val isAsleep = MutableStateFlow(false)
private val isAodAvailable = MutableStateFlow(false)
- private val deviceState = MutableStateFlow(DeviceState.UNFOLDED)
- private val screenPowerState = MutableStateFlow(ScreenPowerState.SCREEN_ON)
- private val areAnimationEnabled = MutableStateFlow(true)
- private val lastWakefulnessEvent = MutableStateFlow(WakefulnessModel())
private val systemClock = FakeSystemClock()
private val configurationController = FakeConfigurationController()
private val configurationRepository =
@@ -126,13 +127,10 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
.thenReturn(listOf(closedDeviceState, openDeviceState))
whenever(resources.getIntArray(R.array.config_foldedDeviceStates))
.thenReturn(nonEmptyClosedDeviceStatesArray)
- whenever(foldStateRepository.state).thenReturn(deviceState)
- whenever(powerInteractor.isAsleep).thenReturn(isAsleep)
- whenever(animationStatusRepository.areAnimationsEnabled()).thenReturn(areAnimationEnabled)
- whenever(powerInteractor.screenPowerState).thenReturn(screenPowerState)
whenever(keyguardInteractor.isAodAvailable).thenReturn(isAodAvailable)
- whenever(powerInteractor.detailedWakefulness).thenReturn(lastWakefulnessEvent)
-
+ animationStatusRepository.onAnimationStatusChanged(true)
+ powerInteractor.setAwakeForTest()
+ powerInteractor.setScreenPowerState(SCREEN_ON)
displaySwitchLatencyTracker =
DisplaySwitchLatencyTracker(
mockContext,
@@ -152,21 +150,19 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
@Test
fun unfold_logsLatencyTillTransitionStarted() {
testScope.runTest {
- areAnimationEnabled.emit(true)
-
displaySwitchLatencyTracker.start()
- deviceState.emit(DeviceState.FOLDED)
- screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ setDeviceState(FOLDED)
+ powerInteractor.setScreenPowerState(SCREEN_OFF)
systemClock.advanceTime(50)
runCurrent()
- deviceState.emit(DeviceState.HALF_FOLDED)
+ setDeviceState(HALF_FOLDED)
runCurrent()
systemClock.advanceTime(50)
- screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ powerInteractor.setScreenPowerState(SCREEN_ON)
systemClock.advanceTime(200)
unfoldTransitionProgressProvider.onTransitionStarted()
runCurrent()
- deviceState.emit(DeviceState.UNFOLDED)
+ setDeviceState(UNFOLDED)
verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
val loggedEvent = loggerArgumentCaptor.value
@@ -202,23 +198,22 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
systemClock,
deviceStateManager,
)
- areAnimationEnabled.emit(true)
displaySwitchLatencyTracker.start()
- deviceState.emit(DeviceState.FOLDED)
- screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ setDeviceState(FOLDED)
+ powerInteractor.setScreenPowerState(SCREEN_OFF)
systemClock.advanceTime(50)
runCurrent()
- deviceState.emit(DeviceState.HALF_FOLDED)
+ setDeviceState(HALF_FOLDED)
systemClock.advanceTime(50)
runCurrent()
- screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ powerInteractor.setScreenPowerState(SCREEN_ON)
systemClock.advanceTime(50)
runCurrent()
systemClock.advanceTime(200)
unfoldTransitionProgressProvider.onTransitionStarted()
runCurrent()
- deviceState.emit(DeviceState.UNFOLDED)
+ setDeviceState(UNFOLDED)
verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
val loggedEvent = loggerArgumentCaptor.value
@@ -235,23 +230,23 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
@Test
fun unfold_animationDisabled_logsLatencyTillScreenTurnedOn() {
testScope.runTest {
- areAnimationEnabled.emit(false)
+ animationStatusRepository.onAnimationStatusChanged(false)
displaySwitchLatencyTracker.start()
- deviceState.emit(DeviceState.FOLDED)
- screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ setDeviceState(FOLDED)
+ powerInteractor.setScreenPowerState(SCREEN_OFF)
systemClock.advanceTime(50)
runCurrent()
- deviceState.emit(DeviceState.HALF_FOLDED)
+ setDeviceState(HALF_FOLDED)
systemClock.advanceTime(50)
runCurrent()
- screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ powerInteractor.setScreenPowerState(SCREEN_ON)
systemClock.advanceTime(50)
runCurrent()
unfoldTransitionProgressProvider.onTransitionStarted()
systemClock.advanceTime(200)
runCurrent()
- deviceState.emit(DeviceState.UNFOLDED)
+ setDeviceState(UNFOLDED)
verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
val loggedEvent = loggerArgumentCaptor.value
@@ -268,19 +263,18 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
@Test
fun foldWhileStayingAwake_logsLatency() {
testScope.runTest {
- areAnimationEnabled.emit(true)
- deviceState.emit(DeviceState.UNFOLDED)
- screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ setDeviceState(UNFOLDED)
+ powerInteractor.setScreenPowerState(SCREEN_ON)
displaySwitchLatencyTracker.start()
- deviceState.emit(DeviceState.HALF_FOLDED)
+ setDeviceState(HALF_FOLDED)
systemClock.advanceTime(50)
runCurrent()
- deviceState.emit(DeviceState.FOLDED)
- screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ setDeviceState(FOLDED)
+ powerInteractor.setScreenPowerState(SCREEN_OFF)
runCurrent()
systemClock.advanceTime(200)
- screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ powerInteractor.setScreenPowerState(SCREEN_ON)
runCurrent()
verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
@@ -298,25 +292,19 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
@Test
fun foldToAod_capturesToStateAsAod() {
testScope.runTest {
- areAnimationEnabled.emit(true)
- deviceState.emit(DeviceState.UNFOLDED)
+ setDeviceState(UNFOLDED)
isAodAvailable.emit(true)
displaySwitchLatencyTracker.start()
- deviceState.emit(DeviceState.HALF_FOLDED)
+ setDeviceState(HALF_FOLDED)
systemClock.advanceTime(50)
runCurrent()
- deviceState.emit(DeviceState.FOLDED)
- lastWakefulnessEvent.emit(
- WakefulnessModel(
- internalWakefulnessState = WakefulnessState.ASLEEP,
- lastSleepReason = WakeSleepReason.FOLD,
- )
- )
- screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ setDeviceState(FOLDED)
+ powerInteractor.setAsleepForTest(sleepReason = GO_TO_SLEEP_REASON_DEVICE_FOLD)
+ powerInteractor.setScreenPowerState(SCREEN_OFF)
runCurrent()
systemClock.advanceTime(200)
- screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ powerInteractor.setScreenPowerState(SCREEN_ON)
runCurrent()
verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
@@ -335,22 +323,21 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
@Test
fun fold_notAFoldable_shouldNotLogLatency() {
testScope.runTest {
- areAnimationEnabled.emit(true)
- deviceState.emit(DeviceState.UNFOLDED)
+ setDeviceState(UNFOLDED)
whenever(resources.getIntArray(R.array.config_foldedDeviceStates))
.thenReturn(IntArray(0))
whenever(deviceStateManager.supportedDeviceStates)
.thenReturn(listOf(defaultDeviceState))
displaySwitchLatencyTracker.start()
- deviceState.emit(DeviceState.HALF_FOLDED)
+ setDeviceState(HALF_FOLDED)
systemClock.advanceTime(50)
runCurrent()
- deviceState.emit(DeviceState.FOLDED)
- screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ setDeviceState(FOLDED)
+ powerInteractor.setScreenPowerState(SCREEN_OFF)
runCurrent()
systemClock.advanceTime(200)
- screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ powerInteractor.setScreenPowerState(SCREEN_ON)
runCurrent()
verify(displaySwitchLatencyLogger, never()).log(any())
@@ -360,22 +347,16 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
@Test
fun foldToScreenOff_capturesToStateAsScreenOff() {
testScope.runTest {
- areAnimationEnabled.emit(true)
- deviceState.emit(DeviceState.UNFOLDED)
+ setDeviceState(UNFOLDED)
isAodAvailable.emit(false)
displaySwitchLatencyTracker.start()
- deviceState.emit(DeviceState.HALF_FOLDED)
+ setDeviceState(HALF_FOLDED)
systemClock.advanceTime(50)
runCurrent()
- deviceState.emit(DeviceState.FOLDED)
- lastWakefulnessEvent.emit(
- WakefulnessModel(
- internalWakefulnessState = WakefulnessState.ASLEEP,
- lastSleepReason = WakeSleepReason.FOLD,
- )
- )
- screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ setDeviceState(FOLDED)
+ powerInteractor.setAsleepForTest(sleepReason = GO_TO_SLEEP_REASON_DEVICE_FOLD)
+ powerInteractor.setScreenPowerState(SCREEN_OFF)
runCurrent()
verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
@@ -390,4 +371,8 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
}
}
+
+ private suspend fun setDeviceState(state: DeviceState) {
+ foldStateRepository.emit(state)
+ }
}
diff --git a/packages/SystemUI/res/drawable/media_output_item_expand_group.xml b/packages/SystemUI/res/drawable/media_output_item_expand_group.xml
new file mode 100644
index 000000000000..833843d9633a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_item_expand_group.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,15.4 L6,9.4l1.4,-1.4 4.6,4.6 4.6,-4.6 1.4,1.4 -6,6Z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid.xml b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
index 8c34cd4165e0..8fd10fb3ddb8 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
@@ -29,6 +29,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@*android:dimen/notification_2025_title_text_size"
android:paddingEnd="4dp"
/>
<TextView
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
index a338e4c70cfa..35f2ef901bdd 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
@@ -54,6 +54,7 @@
android:singleLine="true"
android:paddingEnd="4dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@*android:dimen/notification_2025_title_text_size"
/>
<TextView
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 84c859ccd5a9..fc9635bb6e45 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -588,6 +588,12 @@
<!-- Content description of the cast label showing what we are connected to. [CHAR LIMIT=NONE] -->
<string name="accessibility_cast_name">Connected to <xliff:g id="cast" example="TV">%s</xliff:g>.</string>
+ <!-- Content description of the button to expand the group of devices. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_expand_group">Expand group.</string>
+
+ <!-- Content description of the button to open the application . [CHAR LIMIT=NONE] -->
+ <string name="accessibility_open_application">Open application.</string>
+
<!-- Content description of an item with no signal and no connection for accessibility (not shown on the screen) [CHAR LIMIT=NONE] -->
<string name="accessibility_not_connected">Not connected.</string>
<!-- Content description of the roaming data connection type. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
index b43ffc530289..10b930381c44 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
@@ -146,9 +146,9 @@ oneway interface ILauncherProxy {
void onUnbind(IRemoteCallback reply) = 35;
/**
- * Sent when {@link TaskbarDelegate#onDisplayReady} is called.
+ * Sent when {@link TaskbarDelegate#onDisplayAddSystemDecorations} is called.
*/
- void onDisplayReady(int displayId) = 36;
+ void onDisplayAddSystemDecorations(int displayId) = 36;
/**
* Sent when {@link TaskbarDelegate#onDisplayRemoved} is called.
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index e76f38c8c75c..9507b0483a06 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -62,15 +62,14 @@ public abstract class ClockRegistryModule {
scope,
mainDispatcher,
bgDispatcher,
- com.android.systemui.Flags.lockscreenCustomClocks()
+ com.android.systemui.shared.Flags.lockscreenCustomClocks()
|| featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
/* handleAllUsers= */ true,
new DefaultClockProvider(
context,
layoutInflater,
resources,
-
- com.android.systemui.Flags.clockReactiveVariants()
+ com.android.systemui.shared.Flags.clockReactiveVariants()
),
context.getString(R.string.lockscreen_clock_id_fallback),
clockBuffers,
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 1c994731c393..126471234fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -32,6 +32,9 @@ import com.android.systemui.authentication.shared.model.AuthenticationWipeModel.
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -66,6 +69,7 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val repository: AuthenticationRepository,
private val selectedUserInteractor: SelectedUserInteractor,
+ @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
) {
/**
* The currently-configured authentication method. This determines how the authentication
@@ -85,7 +89,11 @@ constructor(
* `true` even when the lockscreen is showing and still needs to be dismissed by the user to
* proceed.
*/
- val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod
+ val authenticationMethod: Flow<AuthenticationMethodModel> =
+ repository.authenticationMethod.logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ initialValue = AuthenticationMethodModel.None,
+ )
/**
* Whether the auto confirm feature is enabled for the currently-selected user.
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
index 4e45fcc25fb8..744fd7e94ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
@@ -16,6 +16,9 @@
package com.android.systemui.authentication.shared.model
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
/** Enumerates all known authentication methods. */
sealed class AuthenticationMethodModel(
/**
@@ -24,8 +27,8 @@ sealed class AuthenticationMethodModel(
* "Secure" authentication methods require authentication to unlock the device. Non-secure auth
* methods simply require user dismissal.
*/
- open val isSecure: Boolean,
-) {
+ open val isSecure: Boolean
+) : Diffable<AuthenticationMethodModel> {
/**
* Device doesn't use a secure authentication method. Either there is no lockscreen or the lock
* screen can be swiped away when displayed.
@@ -39,4 +42,8 @@ sealed class AuthenticationMethodModel(
data object Pattern : AuthenticationMethodModel(isSecure = true)
data object Sim : AuthenticationMethodModel(isSecure = true)
+
+ override fun logDiffs(prevVal: AuthenticationMethodModel, row: TableRowLogger) {
+ row.logChange(columnName = "authenticationMethod", value = toString())
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index 19238804fb12..79e66a89cd6b 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -16,6 +16,8 @@
package com.android.systemui.common.data
+import com.android.systemui.common.data.repository.BatteryRepository
+import com.android.systemui.common.data.repository.BatteryRepositoryImpl
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl
import dagger.Binds
@@ -27,4 +29,6 @@ abstract class CommonDataLayerModule {
abstract fun bindPackageChangeRepository(
impl: PackageChangeRepositoryImpl
): PackageChangeRepository
+
+ @Binds abstract fun bindBatteryRepository(impl: BatteryRepositoryImpl): BatteryRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt
new file mode 100644
index 000000000000..63b051339d4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.kotlin.isDevicePluggedIn
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
+
+interface BatteryRepository {
+ val isDevicePluggedIn: Flow<Boolean>
+}
+
+@SysUISingleton
+class BatteryRepositoryImpl
+@Inject
+constructor(@Background bgScope: CoroutineScope, batteryController: BatteryController) :
+ BatteryRepository {
+
+ /** Returns {@code true} if the device is currently plugged in or wireless charging. */
+ override val isDevicePluggedIn: Flow<Boolean> =
+ batteryController
+ .isDevicePluggedIn()
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), batteryController.isPluggedIn)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt
new file mode 100644
index 000000000000..987776d14b2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.domain.interactor
+
+import com.android.systemui.common.data.repository.BatteryRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class BatteryInteractor @Inject constructor(batteryRepository: BatteryRepository) {
+ val isDevicePluggedIn = batteryRepository.isDevicePluggedIn
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 53122c56ed2c..abd101693b43 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -34,6 +34,7 @@ import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_I
import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule.Companion.DEFAULT_BACKGROUND_TYPE
import com.android.systemui.communal.shared.model.CommunalBackgroundType
+import com.android.systemui.communal.shared.model.WhenToDream
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -60,6 +61,12 @@ interface CommunalSettingsRepository {
fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean>
/**
+ * Returns a [WhenToDream] for the specified user, indicating what state the device should be in
+ * to trigger dreams.
+ */
+ fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream>
+
+ /**
* Returns true if any glanceable hub functionality should be enabled via configs and flags.
*
* This should be used for preventing basic glanceable hub functionality from running on devices
@@ -157,6 +164,49 @@ constructor(
}
.flowOn(bgDispatcher)
+ override fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream> =
+ secureSettings
+ .observerFlow(
+ userId = user.id,
+ names =
+ arrayOf(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ ),
+ )
+ .emitOnStart()
+ .map {
+ if (
+ secureSettings.getIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 0,
+ user.id,
+ ) == 1
+ ) {
+ WhenToDream.WHILE_CHARGING
+ } else if (
+ secureSettings.getIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ 0,
+ user.id,
+ ) == 1
+ ) {
+ WhenToDream.WHILE_DOCKED
+ } else if (
+ secureSettings.getIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+ 0,
+ user.id,
+ ) == 1
+ ) {
+ WhenToDream.WHILE_POSTURED
+ } else {
+ WhenToDream.NEVER
+ }
+ }
+ .flowOn(bgDispatcher)
+
override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
broadcastDispatcher
.broadcastFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 465a8e3eccca..237a19cb31c4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -30,11 +30,13 @@ import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.Flags.communalResponsiveGrid
import com.android.systemui.Flags.glanceableHubBlurredBackground
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.domain.interactor.BatteryInteractor
import com.android.systemui.communal.data.repository.CommunalMediaRepository
import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
+import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.FULL
@@ -43,11 +45,14 @@ import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.EditModeState
+import com.android.systemui.communal.shared.model.WhenToDream
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.retrieveIsDocked
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -67,6 +72,7 @@ import com.android.systemui.statusbar.phone.ManagedProfileController
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.kotlin.isDevicePluggedIn
import javax.inject.Inject
import kotlin.time.Duration.Companion.minutes
import kotlinx.coroutines.CoroutineDispatcher
@@ -86,6 +92,7 @@ import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -117,6 +124,9 @@ constructor(
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
private val managedProfileController: ManagedProfileController,
+ private val batteryInteractor: BatteryInteractor,
+ private val dockManager: DockManager,
+ private val posturingInteractor: PosturingInteractor,
) {
private val logger = Logger(logBuffer, "CommunalInteractor")
@@ -172,6 +182,37 @@ constructor(
replay = 1,
)
+ /**
+ * Whether communal hub should be shown automatically, depending on the user's [WhenToDream]
+ * state.
+ */
+ val shouldShowCommunal: StateFlow<Boolean> =
+ allOf(
+ isCommunalAvailable,
+ communalSettingsInteractor.whenToDream
+ .flatMapLatest { whenToDream ->
+ when (whenToDream) {
+ WhenToDream.NEVER -> flowOf(false)
+
+ WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
+
+ WhenToDream.WHILE_DOCKED ->
+ allOf(
+ batteryInteractor.isDevicePluggedIn,
+ dockManager.retrieveIsDocked(),
+ )
+
+ WhenToDream.WHILE_POSTURED ->
+ allOf(
+ batteryInteractor.isDevicePluggedIn,
+ posturingInteractor.postured,
+ )
+ }
+ }
+ .flowOn(bgDispatcher),
+ )
+ .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
+
private val _isDisclaimerDismissed = MutableStateFlow(false)
val isDisclaimerDismissed: Flow<Boolean> = _isDisclaimerDismissed.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index 1738f37b7f0c..a0b1261df346 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -21,6 +21,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.communal.data.model.CommunalEnabledState
import com.android.systemui.communal.data.repository.CommunalSettingsRepository
import com.android.systemui.communal.shared.model.CommunalBackgroundType
+import com.android.systemui.communal.shared.model.WhenToDream
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.dagger.CommunalTableLog
@@ -73,6 +74,12 @@ constructor(
repository.getScreensaverEnabledState(user)
}
+ /** When to dream for the currently selected user. */
+ val whenToDream: Flow<WhenToDream> =
+ userInteractor.selectedUserInfo.flatMapLatest { user ->
+ repository.getWhenToDreamState(user)
+ }
+
/**
* Returns true if any glanceable hub functionality should be enabled via configs and flags.
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt
new file mode 100644
index 000000000000..0d4eb60c5240
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 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.communal.shared.model
+
+enum class WhenToDream {
+ NEVER,
+ WHILE_CHARGING,
+ WHILE_DOCKED,
+ WHILE_POSTURED,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalLockIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalLockIconViewBinder.kt
new file mode 100644
index 000000000000..b1407da78816
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalLockIconViewBinder.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.ui.binder
+
+import android.annotation.SuppressLint
+import android.content.res.ColorStateList
+import android.util.Log
+import android.util.StateSet
+import android.view.HapticFeedbackConstants
+import android.view.View
+import androidx.core.view.isInvisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.common.ui.view.LongPressHandlingView
+import com.android.systemui.communal.ui.viewmodel.CommunalLockIconViewModel
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.kotlin.DisposableHandles
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+
+object CommunalLockIconViewBinder {
+ private const val TAG = "CommunalLockIconViewBinder"
+
+ /**
+ * Updates UI for:
+ * - device entry containing view (parent view for the below views)
+ * - long-press handling view (transparent, no UI)
+ * - foreground icon view (lock/unlock)
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ @JvmStatic
+ fun bind(
+ applicationScope: CoroutineScope,
+ view: DeviceEntryIconView,
+ viewModel: CommunalLockIconViewModel,
+ falsingManager: FalsingManager,
+ vibratorHelper: VibratorHelper,
+ ): DisposableHandle {
+ val disposables = DisposableHandles()
+ val longPressHandlingView = view.longPressHandlingView
+ val fgIconView = view.iconView
+ val bgView = view.bgView
+ longPressHandlingView.listener =
+ object : LongPressHandlingView.Listener {
+ override fun onLongPressDetected(
+ view: View,
+ x: Int,
+ y: Int,
+ isA11yAction: Boolean,
+ ) {
+ if (
+ !isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+ ) {
+ Log.d(
+ TAG,
+ "Long press rejected because it is not a11yAction " +
+ "and it is a falseLongTap",
+ )
+ return
+ }
+ vibratorHelper.performHapticFeedback(view, HapticFeedbackConstants.CONFIRM)
+ applicationScope.launch {
+ view.clearFocus()
+ view.clearAccessibilityFocus()
+ viewModel.onUserInteraction()
+ }
+ }
+ }
+
+ longPressHandlingView.isInvisible = false
+ view.isClickable = true
+ longPressHandlingView.longPressDuration = {
+ view.resources.getInteger(R.integer.config_lockIconLongPress).toLong()
+ }
+ bgView.visibility = View.GONE
+
+ disposables +=
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch("$TAG#viewModel.isLongPressEnabled") {
+ viewModel.isLongPressEnabled.collect { isEnabled ->
+ longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
+ }
+ }
+ launch("$TAG#viewModel.accessibilityDelegateHint") {
+ viewModel.accessibilityDelegateHint.collect { hint ->
+ view.accessibilityHintType = hint
+ if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
+ view.setOnClickListener {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.CONFIRM,
+ )
+ applicationScope.launch {
+ view.clearFocus()
+ view.clearAccessibilityFocus()
+ viewModel.onUserInteraction()
+ }
+ }
+ } else {
+ view.setOnClickListener(null)
+ }
+ }
+ }
+ }
+ }
+
+ disposables +=
+ fgIconView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // Start with an empty state
+ fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
+ launch("$TAG#fpIconView.viewModel") {
+ viewModel.viewAttributes.collect { attributes ->
+ if (attributes.type.contentDescriptionResId != -1) {
+ fgIconView.contentDescription =
+ fgIconView.resources.getString(
+ attributes.type.contentDescriptionResId
+ )
+ }
+ fgIconView.imageTintList = ColorStateList.valueOf(attributes.tint)
+ fgIconView.setPadding(
+ attributes.padding,
+ attributes.padding,
+ attributes.padding,
+ attributes.padding,
+ )
+ // Set image state at the end after updating other view state. This
+ // method forces the ImageView to recompute the bounds of the drawable.
+ fgIconView.setImageState(
+ view.getIconState(attributes.type, false),
+ /* merge */ false,
+ )
+ // Invalidate, just in case the padding changes just after icon changes
+ fgIconView.invalidate()
+ }
+ }
+ }
+ }
+ return disposables
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt
new file mode 100644
index 000000000000..19eeabd98c88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2025 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.communal.ui.viewmodel
+
+import android.content.Context
+import com.android.keyguard.KeyguardViewController
+import com.android.settingslib.Utils
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntrySourceInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.toAccessibilityHintType
+import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.util.kotlin.emitOnStart
+import dagger.Lazy
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/**
+ * Simpler implementation of [DeviceEntryIconViewModel] for use in glanceable hub, where fingerprint
+ * is not supported.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalLockIconViewModel
+@Inject
+constructor(
+ @ShadeDisplayAware val context: Context,
+ @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ keyguardInteractor: KeyguardInteractor,
+ private val keyguardViewController: Lazy<KeyguardViewController>,
+ private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+ accessibilityInteractor: AccessibilityInteractor,
+) {
+
+ private val isUnlocked: Flow<Boolean> =
+ if (SceneContainerFlag.isEnabled) {
+ deviceEntryInteractor.isUnlocked
+ } else {
+ keyguardInteractor.isKeyguardDismissible
+ }
+ .flatMapLatest { isUnlocked ->
+ if (!isUnlocked) {
+ flowOf(false)
+ } else {
+ flow {
+ // delay in case device ends up transitioning away from the lock screen;
+ // we don't want to animate to the unlocked icon and just let the
+ // icon fade with the transition to GONE
+ delay(DeviceEntryIconViewModel.UNLOCKED_DELAY_MS)
+ emit(true)
+ }
+ }
+ }
+
+ private val iconType: Flow<DeviceEntryIconView.IconType> =
+ isUnlocked.map { unlocked ->
+ if (unlocked) {
+ DeviceEntryIconView.IconType.UNLOCK
+ } else {
+ DeviceEntryIconView.IconType.LOCK
+ }
+ }
+
+ val isLongPressEnabled: Flow<Boolean> =
+ iconType.map { deviceEntryStatus ->
+ when (deviceEntryStatus) {
+ DeviceEntryIconView.IconType.UNLOCK -> true
+ DeviceEntryIconView.IconType.LOCK,
+ DeviceEntryIconView.IconType.FINGERPRINT,
+ DeviceEntryIconView.IconType.NONE -> false
+ }
+ }
+
+ val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
+ accessibilityInteractor.isEnabled.flatMapLatest { touchExplorationEnabled ->
+ if (touchExplorationEnabled) {
+ iconType.map { it.toAccessibilityHintType() }
+ } else {
+ flowOf(DeviceEntryIconView.AccessibilityHintType.NONE)
+ }
+ }
+
+ private val padding: Flow<Int> =
+ configurationInteractor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+
+ private fun getColor() =
+ Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+
+ private val color: Flow<Int> =
+ configurationInteractor.onAnyConfigurationChange
+ .emitOnStart()
+ .map { getColor() }
+ .distinctUntilChanged()
+
+ suspend fun onUserInteraction() {
+ if (SceneContainerFlag.isEnabled) {
+ deviceEntryInteractor.attemptDeviceEntry()
+ } else {
+ keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
+ }
+ deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+ }
+
+ val viewAttributes: Flow<CommunalLockIconAttributes> =
+ combine(iconType, color, padding) { iconType, color, padding ->
+ CommunalLockIconAttributes(type = iconType, tint = color, padding = padding)
+ }
+}
+
+data class CommunalLockIconAttributes(
+ val type: DeviceEntryIconView.IconType,
+ val tint: Int,
+ val padding: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
index 1e7bec257432..69da67e055fe 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
@@ -68,4 +68,10 @@ constructor(
emptyFlow()
}
}
+
+ /** Triggered if a face failure occurs regardless of the mode. */
+ val faceFailure: Flow<FailedFaceAuthenticationStatus> =
+ deviceEntryFaceAuthInteractor.authenticationStatus.filterIsInstance<
+ FailedFaceAuthenticationStatus
+ >()
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index cdd2b054711e..079d624e6fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor
import com.android.systemui.CoreStartable
import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
+import com.android.systemui.log.table.TableLogBuffer
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -81,6 +82,8 @@ interface DeviceEntryFaceAuthInteractor : CoreStartable {
/** Whether face auth is considered class 3 */
fun isFaceAuthStrong(): Boolean
+
+ suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer)
}
/**
@@ -93,17 +96,17 @@ interface DeviceEntryFaceAuthInteractor : CoreStartable {
*/
interface FaceAuthenticationListener {
/** Receive face isAuthenticated updates */
- fun onAuthenticatedChanged(isAuthenticated: Boolean)
+ fun onAuthenticatedChanged(isAuthenticated: Boolean) = Unit
/** Receive face authentication status updates */
- fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
+ fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus) = Unit
/** Receive status updates whenever face detection runs */
- fun onDetectionStatusChanged(status: FaceDetectionStatus)
+ fun onDetectionStatusChanged(status: FaceDetectionStatus) = Unit
- fun onLockoutStateChanged(isLockedOut: Boolean)
+ fun onLockoutStateChanged(isLockedOut: Boolean) = Unit
- fun onRunningStateChanged(isRunning: Boolean)
+ fun onRunningStateChanged(isRunning: Boolean) = Unit
- fun onAuthEnrollmentStateChanged(enrolled: Boolean)
+ fun onAuthEnrollmentStateChanged(enrolled: Boolean) = Unit
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index cd456a618c48..38e0503440f9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -123,7 +123,7 @@ constructor(
private val playErrorHapticForBiometricFailure: Flow<Unit> =
merge(
deviceEntryFingerprintAuthInteractor.fingerprintFailure,
- deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure,
+ deviceEntryBiometricAuthInteractor.faceFailure,
)
// map to Unit
.map {}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 4ddc98cd434f..5b6859761705 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -25,7 +25,10 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.scene.data.model.asIterable
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
import com.android.systemui.scene.domain.interactor.SceneBackInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -33,9 +36,11 @@ import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
@@ -43,6 +48,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* Hosts application business logic related to device entry.
@@ -62,6 +68,7 @@ constructor(
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val dismissCallbackRegistry: DismissCallbackRegistry,
sceneBackInteractor: SceneBackInteractor,
+ @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
) {
/**
* Whether the device is unlocked.
@@ -147,6 +154,11 @@ constructor(
) { enteredDirectly, enteredOnBackStack ->
enteredOnBackStack || enteredDirectly
}
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnName = "isDeviceEntered",
+ initialValue = false,
+ )
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
@@ -184,6 +196,11 @@ constructor(
deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) &&
!isDeviceEntered
}
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnName = "canSwipeToEnter",
+ initialValue = false,
+ )
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
@@ -271,4 +288,29 @@ constructor(
fun lockNow() {
deviceUnlockedInteractor.lockNow()
}
+
+ suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+ coroutineScope {
+ launch {
+ isDeviceEntered
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnName = "isDeviceEntered",
+ initialValue = isDeviceEntered.value,
+ )
+ .collect()
+ }
+
+ launch {
+ canSwipeToEnter
+ .map { it?.toString() ?: "" }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnName = "canSwipeToEnter",
+ initialValue = canSwipeToEnter.value?.toString() ?: "",
+ )
+ .collect()
+ }
+ }
+ }
}
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 68aef521be7b..b1be9a209a0a 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
@@ -33,8 +33,11 @@ import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
@@ -48,6 +51,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -74,6 +78,7 @@ constructor(
private val systemPropertiesHelper: SystemPropertiesHelper,
private val userAwareSecureSettingsRepository: UserAwareSecureSettingsRepository,
private val keyguardInteractor: KeyguardInteractor,
+ @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
) : ExclusiveActivatable() {
private val deviceUnlockSource =
@@ -179,17 +184,33 @@ constructor(
private val lockNowRequests = Channel<Unit>()
override suspend fun onActivated(): Nothing {
- authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
- if (!authMethod.isSecure) {
- // Device remains unlocked as long as the authentication method is not secure.
- Log.d(TAG, "remaining unlocked because auth method not secure")
- repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null)
- } else if (authMethod == AuthenticationMethodModel.Sim) {
- // Device remains locked while SIM is locked.
- Log.d(TAG, "remaining locked because SIM locked")
- repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null)
- } else {
- handleLockAndUnlockEvents()
+ coroutineScope {
+ launch {
+ authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
+ if (!authMethod.isSecure) {
+ // Device remains unlocked as long as the authentication method is not
+ // secure.
+ Log.d(TAG, "remaining unlocked because auth method not secure")
+ repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null)
+ } else if (authMethod == AuthenticationMethodModel.Sim) {
+ // Device remains locked while SIM is locked.
+ Log.d(TAG, "remaining locked because SIM locked")
+ repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null)
+ } else {
+ handleLockAndUnlockEvents()
+ }
+ }
+ }
+
+ launch {
+ deviceUnlockStatus
+ .map { it.isUnlocked }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnName = "isUnlocked",
+ initialValue = deviceUnlockStatus.value.isUnlocked,
+ )
+ .collect()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index 9b8c2b1acc33..ecc4dbc2326a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
+import com.android.systemui.log.table.TableLogBuffer
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -73,4 +74,6 @@ class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceA
override fun onWalletLaunched() = Unit
override fun onDeviceUnfolded() {}
+
+ override suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index b19b2d9ece02..4b90e1d52ea0 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -44,6 +44,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -53,13 +55,16 @@ import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import dagger.Lazy
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.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
@@ -379,6 +384,27 @@ constructor(
.launchIn(applicationScope)
}
+ override suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+ conflatedCallbackFlow {
+ val listener =
+ object : FaceAuthenticationListener {
+ override fun onAuthEnrollmentStateChanged(enrolled: Boolean) {
+ trySend(isFaceAuthEnabledAndEnrolled())
+ }
+ }
+
+ registerListener(listener)
+
+ awaitClose { unregisterListener(listener) }
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnName = "isFaceAuthEnabledAndEnrolled",
+ initialValue = isFaceAuthEnabledAndEnrolled(),
+ )
+ .collect()
+ }
+
companion object {
const val TAG = "DeviceEntryFaceAuthInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 02e1824dc7dd..11b7e9dfe319 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -20,6 +20,7 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
@@ -51,6 +52,7 @@ constructor(
private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
private val communalInteractor: CommunalInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val userTracker: UserTracker,
dumpManager: DumpManager,
@@ -58,8 +60,12 @@ constructor(
fun startTransitionFromDream() {
val showGlanceableHub =
- communalInteractor.isCommunalEnabled.value &&
- !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ communalInteractor.shouldShowCommunal.value
+ } else {
+ communalInteractor.isCommunalEnabled.value &&
+ !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
+ }
fromDreamingTransitionInteractor.startToLockscreenOrGlanceableHubTransition(
showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 0054dd772659..6395bb736a34 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -1 +1,1101 @@
-/* * 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.keyboard.shortcut.ui.composable import android.graphics.drawable.Icon import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.focusable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRowScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets 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.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState 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.automirrored.filled.OpenInNew import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.DeleteOutline import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Tune import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationDrawerItemColors import androidx.compose.material3.NavigationDrawerItemDefaults import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider 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.focus.FocusDirection import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.semantics.isTraversalGroup import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.Hyphens import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastFirstOrNull import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEachIndexed import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.painter.rememberDrawablePainter import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.keyboard.shortcut.ui.model.IconSource import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState import com.android.systemui.res.R import kotlinx.coroutines.delay import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel @Composable fun ShortcutHelper( onSearchQueryChanged: (String) -> Unit, onKeyboardSettingsClicked: () -> Unit, modifier: Modifier = Modifier, shortcutsUiState: ShortcutsUiState, useSinglePane: @Composable () -> Boolean = { shouldUseSinglePane() }, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, ) { when (shortcutsUiState) { is ShortcutsUiState.Active -> { ActiveShortcutHelper( shortcutsUiState, useSinglePane, onSearchQueryChanged, modifier, onKeyboardSettingsClicked, onCustomizationRequested, ) } else -> { // No-op for now. } } } @Composable private fun ActiveShortcutHelper( shortcutsUiState: ShortcutsUiState.Active, useSinglePane: @Composable () -> Boolean, onSearchQueryChanged: (String) -> Unit, modifier: Modifier, onKeyboardSettingsClicked: () -> Unit, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, ) { var selectedCategoryType by remember(shortcutsUiState.defaultSelectedCategory) { mutableStateOf(shortcutsUiState.defaultSelectedCategory) } if (useSinglePane()) { ShortcutHelperSinglePane( shortcutsUiState.searchQuery, onSearchQueryChanged, shortcutsUiState.shortcutCategories, selectedCategoryType, onCategorySelected = { selectedCategoryType = it }, onKeyboardSettingsClicked, modifier, ) } else { ShortcutHelperTwoPane( shortcutsUiState.searchQuery, onSearchQueryChanged, modifier, shortcutsUiState.shortcutCategories, selectedCategoryType, onCategorySelected = { selectedCategoryType = it }, onKeyboardSettingsClicked, shortcutsUiState.isShortcutCustomizerFlagEnabled, onCustomizationRequested, shortcutsUiState.shouldShowResetButton, ) } } @Composable private fun shouldUseSinglePane() = hasCompactWindowSize() @Composable private fun ShortcutHelperSinglePane( searchQuery: String, onSearchQueryChanged: (String) -> Unit, categories: List<ShortcutCategoryUi>, selectedCategoryType: ShortcutCategoryType?, onCategorySelected: (ShortcutCategoryType?) -> Unit, onKeyboardSettingsClicked: () -> Unit, modifier: Modifier = Modifier, ) { Column( modifier = modifier .fillMaxSize() .verticalScroll(rememberScrollState()) .padding(start = 16.dp, end = 16.dp, top = 26.dp) ) { TitleBar() Spacer(modifier = Modifier.height(6.dp)) ShortcutsSearchBar(onSearchQueryChanged) Spacer(modifier = Modifier.height(16.dp)) if (categories.isEmpty()) { Box(modifier = Modifier.weight(1f)) { NoSearchResultsText(horizontalPadding = 16.dp, fillHeight = true) } } else { CategoriesPanelSinglePane( searchQuery, categories, selectedCategoryType, onCategorySelected, ) Spacer(modifier = Modifier.weight(1f)) } KeyboardSettings( horizontalPadding = 16.dp, verticalPadding = 32.dp, onClick = onKeyboardSettingsClicked, ) } } @Composable private fun CategoriesPanelSinglePane( searchQuery: String, categories: List<ShortcutCategoryUi>, selectedCategoryType: ShortcutCategoryType?, onCategorySelected: (ShortcutCategoryType?) -> Unit, ) { Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { categories.fastForEachIndexed { index, category -> val isExpanded = selectedCategoryType == category.type val itemShape = if (categories.size == 1) { ShortcutHelper.Shapes.singlePaneSingleCategory } else if (index == 0) { ShortcutHelper.Shapes.singlePaneFirstCategory } else if (index == categories.lastIndex) { ShortcutHelper.Shapes.singlePaneLastCategory } else { ShortcutHelper.Shapes.singlePaneCategory } CategoryItemSinglePane( searchQuery = searchQuery, category = category, isExpanded = isExpanded, onClick = { onCategorySelected( if (isExpanded) { null } else { category.type } ) }, shape = itemShape, ) } } } @Composable private fun CategoryItemSinglePane( searchQuery: String, category: ShortcutCategoryUi, isExpanded: Boolean, onClick: () -> Unit, shape: Shape, ) { Surface(color = MaterialTheme.colorScheme.surfaceBright, shape = shape, onClick = onClick) { Column { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp), ) { ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.iconSource) Spacer(modifier = Modifier.width(16.dp)) Text(category.label) Spacer(modifier = Modifier.weight(1f)) RotatingExpandCollapseIcon(isExpanded) } AnimatedVisibility(visible = isExpanded) { ShortcutCategoryDetailsSinglePane(searchQuery, category) } } } } @Composable fun ShortcutCategoryIcon( source: IconSource, modifier: Modifier = Modifier, contentDescription: String? = null, tint: Color = LocalContentColor.current, ) { if (source.imageVector != null) { Icon(source.imageVector, contentDescription, modifier, tint) } else if (source.painter != null) { Image(source.painter, contentDescription, modifier) } } @Composable private fun RotatingExpandCollapseIcon(isExpanded: Boolean) { val expandIconRotationDegrees by animateFloatAsState( targetValue = if (isExpanded) { 180f } else { 0f }, label = "Expand icon rotation animation", ) Icon( modifier = Modifier.background( color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = CircleShape, ) .graphicsLayer { rotationZ = expandIconRotationDegrees }, imageVector = Icons.Default.ExpandMore, contentDescription = if (isExpanded) { stringResource(R.string.shortcut_helper_content_description_collapse_icon) } else { stringResource(R.string.shortcut_helper_content_description_expand_icon) }, tint = MaterialTheme.colorScheme.onSurface, ) } @Composable private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategoryUi) { Column(Modifier.padding(horizontal = 16.dp)) { category.subCategories.fastForEach { subCategory -> ShortcutSubCategorySinglePane(searchQuery, subCategory) } } } @Composable private fun ShortcutSubCategorySinglePane(searchQuery: String, subCategory: ShortcutSubCategory) { // This @Composable is expected to be in a Column. SubCategoryTitle(subCategory.label) subCategory.shortcuts.fastForEachIndexed { index, shortcut -> if (index > 0) { HorizontalDivider(color = MaterialTheme.colorScheme.surfaceContainerHigh) } Shortcut(Modifier.padding(vertical = 24.dp), searchQuery, shortcut) } } @Composable private fun ShortcutHelperTwoPane( searchQuery: String, onSearchQueryChanged: (String) -> Unit, modifier: Modifier = Modifier, categories: List<ShortcutCategoryUi>, selectedCategoryType: ShortcutCategoryType?, onCategorySelected: (ShortcutCategoryType?) -> Unit, onKeyboardSettingsClicked: () -> Unit, isShortcutCustomizerFlagEnabled: Boolean, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, shouldShowResetButton: Boolean, ) { val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType } var isCustomizing by remember { mutableStateOf(false) } Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) { Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { // Keep title centered whether customize button is visible or not. Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) { Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { TitleBar(isCustomizing) } if (isShortcutCustomizerFlagEnabled) { CustomizationButtonsContainer( isCustomizing = isCustomizing, onToggleCustomizationMode = { isCustomizing = !isCustomizing }, onReset = { onCustomizationRequested(ShortcutCustomizationRequestInfo.Reset) }, shouldShowResetButton = shouldShowResetButton, ) } else { Spacer(modifier = Modifier.width(if (isCustomizing) 69.dp else 133.dp)) } } } Spacer(modifier = Modifier.height(12.dp)) Row(Modifier.fillMaxWidth()) { StartSidePanel( onSearchQueryChanged = onSearchQueryChanged, modifier = Modifier.width(240.dp).semantics { isTraversalGroup = true }, categories = categories, onKeyboardSettingsClicked = onKeyboardSettingsClicked, selectedCategory = selectedCategoryType, onCategoryClicked = { onCategorySelected(it.type) }, ) Spacer(modifier = Modifier.width(24.dp)) EndSidePanel( searchQuery, Modifier.fillMaxSize().padding(top = 8.dp).semantics { isTraversalGroup = true }, selectedCategory, isCustomizing = isCustomizing, onCustomizationRequested = onCustomizationRequested, ) } } } @Composable private fun CustomizationButtonsContainer( isCustomizing: Boolean, shouldShowResetButton: Boolean, onToggleCustomizationMode: () -> Unit, onReset: () -> Unit, ) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { if (isCustomizing) { if (shouldShowResetButton) { ResetButton(onClick = onReset) } DoneButton(onClick = onToggleCustomizationMode) } else { CustomizeButton(onClick = onToggleCustomizationMode) } } } @Composable private fun ResetButton(onClick: () -> Unit) { ShortcutHelperButton( modifier = Modifier.heightIn(40.dp), onClick = onClick, color = Color.Transparent, iconSource = IconSource(imageVector = Icons.Default.Refresh), text = stringResource(id = R.string.shortcut_helper_reset_button_text), contentColor = MaterialTheme.colorScheme.primary, border = BorderStroke(color = MaterialTheme.colorScheme.outlineVariant, width = 1.dp), ) } @Composable private fun CustomizeButton(onClick: () -> Unit) { ShortcutHelperButton( modifier = Modifier.heightIn(40.dp), onClick = onClick, color = MaterialTheme.colorScheme.secondaryContainer, iconSource = IconSource(imageVector = Icons.Default.Tune), text = stringResource(id = R.string.shortcut_helper_customize_button_text), contentColor = MaterialTheme.colorScheme.onSecondaryContainer, ) } @Composable private fun DoneButton(onClick: () -> Unit) { ShortcutHelperButton( modifier = Modifier.heightIn(40.dp), onClick = onClick, color = MaterialTheme.colorScheme.primary, text = stringResource(R.string.shortcut_helper_done_button_text), contentColor = MaterialTheme.colorScheme.onPrimary, ) } @Composable private fun EndSidePanel( searchQuery: String, modifier: Modifier, category: ShortcutCategoryUi?, isCustomizing: Boolean, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, ) { val listState = rememberLazyListState() LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) } if (category == null) { NoSearchResultsText(horizontalPadding = 24.dp, fillHeight = false) return } LazyColumn(modifier = modifier, state = listState) { items(category.subCategories) { subcategory -> SubCategoryContainerDualPane( searchQuery = searchQuery, subCategory = subcategory, isCustomizing = isCustomizing and category.type.includeInCustomization, onCustomizationRequested = { requestInfo -> when (requestInfo) { is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add -> onCustomizationRequested(requestInfo.copy(categoryType = category.type)) is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete -> onCustomizationRequested(requestInfo.copy(categoryType = category.type)) ShortcutCustomizationRequestInfo.Reset -> onCustomizationRequested(requestInfo) } }, ) Spacer(modifier = Modifier.height(8.dp)) } } } @Composable private fun NoSearchResultsText(horizontalPadding: Dp, fillHeight: Boolean) { var modifier = Modifier.fillMaxWidth() if (fillHeight) { modifier = modifier.fillMaxHeight() } Text( stringResource(R.string.shortcut_helper_no_search_results), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface, modifier = modifier .padding(vertical = 8.dp) .background(MaterialTheme.colorScheme.surfaceBright, RoundedCornerShape(28.dp)) .padding(horizontal = horizontalPadding, vertical = 24.dp), ) } @Composable private fun SubCategoryContainerDualPane( searchQuery: String, subCategory: ShortcutSubCategory, isCustomizing: Boolean, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit, ) { Surface( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(28.dp), color = MaterialTheme.colorScheme.surfaceBright, ) { Column(Modifier.padding(16.dp)) { SubCategoryTitle(subCategory.label) Spacer(Modifier.height(8.dp)) subCategory.shortcuts.fastForEachIndexed { index, shortcut -> if (index > 0) { HorizontalDivider( modifier = Modifier.padding(horizontal = 8.dp), color = MaterialTheme.colorScheme.surfaceContainerHigh, ) } Shortcut( modifier = Modifier.padding(vertical = 8.dp), searchQuery = searchQuery, shortcut = shortcut, isCustomizing = isCustomizing && shortcut.isCustomizable, onCustomizationRequested = { requestInfo -> when (requestInfo) { is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add -> onCustomizationRequested( requestInfo.copy(subCategoryLabel = subCategory.label) ) is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete -> onCustomizationRequested( requestInfo.copy(subCategoryLabel = subCategory.label) ) ShortcutCustomizationRequestInfo.Reset -> onCustomizationRequested(requestInfo) } }, ) } } } } @Composable private fun SubCategoryTitle(title: String) { Text( title, style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, ) } @Composable private fun Shortcut( modifier: Modifier, searchQuery: String, shortcut: ShortcutModel, isCustomizing: Boolean = false, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, ) { val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() val focusColor = MaterialTheme.colorScheme.secondary Row( modifier .thenIf(isFocused) { Modifier.border(width = 3.dp, color = focusColor, shape = RoundedCornerShape(16.dp)) } .focusable(interactionSource = interactionSource) .padding(8.dp) .semantics(mergeDescendants = true) { contentDescription = shortcut.contentDescription } ) { Row( modifier = Modifier.width(128.dp).align(Alignment.CenterVertically).weight(0.333f).semantics { hideFromAccessibility() }, horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, ) { if (shortcut.icon != null) { ShortcutIcon( shortcut.icon, modifier = Modifier.size(24.dp).semantics { hideFromAccessibility() }, ) } ShortcutDescriptionText( searchQuery = searchQuery, shortcut = shortcut, modifier = Modifier.semantics { hideFromAccessibility() }, ) } Spacer(modifier = Modifier.width(24.dp).semantics { hideFromAccessibility() }) ShortcutKeyCombinations( modifier = Modifier.weight(.666f).semantics { hideFromAccessibility() }, shortcut = shortcut, isCustomizing = isCustomizing, onAddShortcutRequested = { onCustomizationRequested( ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add( label = shortcut.label, shortcutCommand = shortcut.commands.first(), ) ) }, onDeleteShortcutRequested = { onCustomizationRequested( ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete( label = shortcut.label, shortcutCommand = shortcut.commands.first(), ) ) }, ) } } @Composable fun ShortcutIcon( icon: ShortcutIcon, modifier: Modifier = Modifier, contentDescription: String? = null, ) { val context = LocalContext.current val drawable = remember(icon.packageName, icon.resourceId) { Icon.createWithResource(icon.packageName, icon.resourceId).loadDrawable(context) } ?: return Image( painter = rememberDrawablePainter(drawable), contentDescription = contentDescription, modifier = modifier, ) } @OptIn(ExperimentalLayoutApi::class) @Composable private fun ShortcutKeyCombinations( modifier: Modifier = Modifier, shortcut: ShortcutModel, isCustomizing: Boolean = false, onAddShortcutRequested: () -> Unit = {}, onDeleteShortcutRequested: () -> Unit = {}, ) { FlowRow( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), itemVerticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.End, ) { shortcut.commands.forEachIndexed { index, command -> if (index > 0) { ShortcutOrSeparator(spacing = 16.dp) } ShortcutCommandContainer(showBackground = command.isCustom) { ShortcutCommand(command) } } if (isCustomizing) { Spacer(modifier = Modifier.width(16.dp)) if (shortcut.containsCustomShortcutCommands) { DeleteShortcutButton(onDeleteShortcutRequested) } else { AddShortcutButton(onAddShortcutRequested) } } } } @Composable private fun AddShortcutButton(onClick: () -> Unit) { ShortcutHelperButton( modifier = Modifier.size(32.dp), onClick = onClick, color = Color.Transparent, iconSource = IconSource(imageVector = Icons.Default.Add), contentColor = MaterialTheme.colorScheme.primary, contentPaddingVertical = 0.dp, contentPaddingHorizontal = 0.dp, contentDescription = stringResource(R.string.shortcut_helper_add_shortcut_button_label), shape = CircleShape, border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outline) ) } @Composable private fun DeleteShortcutButton(onClick: () -> Unit) { ShortcutHelperButton( modifier = Modifier.size(32.dp), onClick = onClick, color = Color.Transparent, iconSource = IconSource(imageVector = Icons.Default.DeleteOutline), contentColor = MaterialTheme.colorScheme.primary, contentPaddingVertical = 0.dp, contentPaddingHorizontal = 0.dp, contentDescription = stringResource(R.string.shortcut_helper_delete_shortcut_button_label), shape = CircleShape, border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outline) ) } @Composable private fun ShortcutCommandContainer(showBackground: Boolean, content: @Composable () -> Unit) { if (showBackground) { Box( modifier = Modifier.wrapContentSize() .background( color = MaterialTheme.colorScheme.outlineVariant, shape = RoundedCornerShape(16.dp), ) .padding(4.dp) ) { content() } } else { content() } } @Composable private fun ShortcutCommand(command: ShortcutCommand) { Row { command.keys.forEachIndexed { keyIndex, key -> if (keyIndex > 0) { Spacer(Modifier.width(4.dp)) } ShortcutKeyContainer { if (key is ShortcutKey.Text) { ShortcutTextKey(key) } else if (key is ShortcutKey.Icon) { ShortcutIconKey(key) } } } } } @Composable private fun ShortcutKeyContainer(shortcutKeyContent: @Composable BoxScope.() -> Unit) { Box( modifier = Modifier.height(36.dp) .background( color = MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(12.dp), ) ) { shortcutKeyContent() } } @Composable private fun BoxScope.ShortcutTextKey(key: ShortcutKey.Text) { Text( text = key.value, modifier = Modifier.align(Alignment.Center).padding(horizontal = 12.dp).semantics { hideFromAccessibility() }, style = MaterialTheme.typography.titleSmall, ) } @Composable private fun BoxScope.ShortcutIconKey(key: ShortcutKey.Icon) { Icon( painter = when (key) { is ShortcutKey.Icon.ResIdIcon -> painterResource(key.drawableResId) is ShortcutKey.Icon.DrawableIcon -> rememberDrawablePainter(drawable = key.drawable) }, contentDescription = null, modifier = Modifier.align(Alignment.Center).padding(6.dp), ) } @OptIn(ExperimentalLayoutApi::class) @Composable private fun FlowRowScope.ShortcutOrSeparator(spacing: Dp) { Spacer(Modifier.width(spacing)) Text( text = stringResource(R.string.shortcut_helper_key_combinations_or_separator), modifier = Modifier.align(Alignment.CenterVertically).semantics { hideFromAccessibility() }, style = MaterialTheme.typography.titleSmall, ) Spacer(Modifier.width(spacing)) } @Composable private fun ShortcutDescriptionText( searchQuery: String, shortcut: ShortcutModel, modifier: Modifier = Modifier, ) { Text( modifier = modifier, text = textWithHighlightedSearchQuery(shortcut.label, searchQuery), style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.onSurface, ) } @Composable private fun textWithHighlightedSearchQuery(text: String, searchValue: String) = buildAnnotatedString { val searchIndex = text.lowercase().indexOf(searchValue.trim().lowercase()) val postSearchIndex = searchIndex + searchValue.trim().length if (searchIndex > 0) { val preSearchText = text.substring(0, searchIndex) append(preSearchText) } if (searchIndex >= 0) { val searchText = text.substring(searchIndex, postSearchIndex) withStyle(style = SpanStyle(background = MaterialTheme.colorScheme.primaryContainer)) { append(searchText) } if (postSearchIndex < text.length) { val postSearchText = text.substring(postSearchIndex) append(postSearchText) } } else { append(text) } } @Composable private fun StartSidePanel( onSearchQueryChanged: (String) -> Unit, modifier: Modifier, categories: List<ShortcutCategoryUi>, onKeyboardSettingsClicked: () -> Unit, selectedCategory: ShortcutCategoryType?, onCategoryClicked: (ShortcutCategoryUi) -> Unit, ) { CompositionLocalProvider( // Restrict system font scale increases up to a max so categories display correctly. LocalDensity provides Density( density = LocalDensity.current.density, fontScale = LocalDensity.current.fontScale.coerceIn(1f, 1.5f), ) ) { Column(modifier) { ShortcutsSearchBar(onSearchQueryChanged) Spacer(modifier = Modifier.heightIn(8.dp)) CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked) Spacer(modifier = Modifier.weight(1f)) KeyboardSettings( horizontalPadding = 24.dp, verticalPadding = 24.dp, onKeyboardSettingsClicked, ) } } } @Composable private fun CategoriesPanelTwoPane( categories: List<ShortcutCategoryUi>, selectedCategory: ShortcutCategoryType?, onCategoryClicked: (ShortcutCategoryUi) -> Unit, ) { Column { categories.fastForEach { CategoryItemTwoPane( label = it.label, iconSource = it.iconSource, selected = selectedCategory == it.type, onClick = { onCategoryClicked(it) }, ) } } } @Composable private fun CategoryItemTwoPane( label: String, iconSource: IconSource, selected: Boolean, onClick: () -> Unit, colors: NavigationDrawerItemColors = NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent), ) { SelectableShortcutSurface( selected = selected, onClick = onClick, modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 64.dp).fillMaxWidth(), shape = RoundedCornerShape(28.dp), color = colors.containerColor(selected).value, interactionsConfig = InteractionsConfig( hoverOverlayColor = MaterialTheme.colorScheme.onSurface, hoverOverlayAlpha = 0.11f, pressedOverlayColor = MaterialTheme.colorScheme.onSurface, pressedOverlayAlpha = 0.15f, focusOutlineColor = MaterialTheme.colorScheme.secondary, focusOutlineStrokeWidth = 3.dp, focusOutlinePadding = 2.dp, surfaceCornerRadius = 28.dp, focusOutlineCornerRadius = 33.dp, ), ) { Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) { ShortcutCategoryIcon( modifier = Modifier.size(24.dp), source = iconSource, contentDescription = null, tint = colors.iconColor(selected).value, ) Spacer(Modifier.width(12.dp)) Box(Modifier.weight(1f)) { Text( fontSize = 18.sp, color = colors.textColor(selected).value, style = MaterialTheme.typography.titleSmall.copy(hyphens = Hyphens.Auto), text = label, ) } } } } @Composable @OptIn(ExperimentalMaterial3Api::class) private fun TitleBar(isCustomizing: Boolean = false) { val text = if (isCustomizing) { stringResource(R.string.shortcut_helper_customize_mode_title) } else { stringResource(R.string.shortcut_helper_title) } CenterAlignedTopAppBar( colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent), title = { Text( text = text, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.headlineSmall, ) }, windowInsets = WindowInsets(top = 0.dp, bottom = 0.dp, left = 0.dp, right = 0.dp), expandedHeight = 64.dp, ) } @Composable @OptIn(ExperimentalMaterial3Api::class) private fun ShortcutsSearchBar(onQueryChange: (String) -> Unit) { // Using an "internal query" to make sure the SearchBar is immediately updated, otherwise // the cursor moves to the wrong position sometimes, when waiting for the query to come back // from the ViewModel. var queryInternal by remember { mutableStateOf("") } val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current LaunchedEffect(Unit) { // TODO(b/272065229): Added minor delay so TalkBack can take focus of search box by default, // remove when default a11y focus is fixed. delay(50) focusRequester.requestFocus() } SearchBar( modifier = Modifier.fillMaxWidth().focusRequester(focusRequester).onKeyEvent { if (it.key == Key.DirectionDown) { focusManager.moveFocus(FocusDirection.Down) return@onKeyEvent true } else { return@onKeyEvent false } }, colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright), query = queryInternal, active = false, onActiveChange = {}, onQueryChange = { queryInternal = it onQueryChange(it) }, onSearch = {}, leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) }, windowInsets = WindowInsets(top = 0.dp, bottom = 0.dp, left = 0.dp, right = 0.dp), content = {}, ) } @Composable private fun KeyboardSettings(horizontalPadding: Dp, verticalPadding: Dp, onClick: () -> Unit) { ClickableShortcutSurface( onClick = onClick, shape = RoundedCornerShape(24.dp), color = Color.Transparent, modifier = Modifier.semantics { role = Role.Button }.fillMaxWidth().padding(horizontal = 12.dp), interactionsConfig = InteractionsConfig( hoverOverlayColor = MaterialTheme.colorScheme.onSurface, hoverOverlayAlpha = 0.11f, pressedOverlayColor = MaterialTheme.colorScheme.onSurface, pressedOverlayAlpha = 0.15f, focusOutlineColor = MaterialTheme.colorScheme.secondary, focusOutlinePadding = 8.dp, focusOutlineStrokeWidth = 3.dp, surfaceCornerRadius = 24.dp, focusOutlineCornerRadius = 28.dp, hoverPadding = 8.dp, ), ) { Row(verticalAlignment = Alignment.CenterVertically) { Text( text = stringResource(id = R.string.shortcut_helper_keyboard_settings_buttons_label), color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 16.sp, style = MaterialTheme.typography.titleSmall, ) Spacer(modifier = Modifier.weight(1f)) Icon( imageVector = Icons.AutoMirrored.Default.OpenInNew, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp), ) } } } object ShortcutHelper { object Shapes { val singlePaneFirstCategory = RoundedCornerShape( topStart = Dimensions.SinglePaneCategoryCornerRadius, topEnd = Dimensions.SinglePaneCategoryCornerRadius, ) val singlePaneLastCategory = RoundedCornerShape( bottomStart = Dimensions.SinglePaneCategoryCornerRadius, bottomEnd = Dimensions.SinglePaneCategoryCornerRadius, ) val singlePaneSingleCategory = RoundedCornerShape(size = Dimensions.SinglePaneCategoryCornerRadius) val singlePaneCategory = RectangleShape } object Dimensions { val SinglePaneCategoryCornerRadius = 28.dp } internal const val TAG = "ShortcutHelperUI" } \ No newline at end of file
+/*
+ * 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.keyboard.shortcut.ui.composable
+
+import android.graphics.drawable.Icon
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsFocusedAsState
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.FlowRowScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+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.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+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.automirrored.filled.OpenInNew
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.DeleteOutline
+import androidx.compose.material.icons.filled.ExpandMore
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material.icons.filled.Tune
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.NavigationDrawerItemColors
+import androidx.compose.material3.NavigationDrawerItemDefaults
+import androidx.compose.material3.SearchBar
+import androidx.compose.material3.SearchBarDefaults
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+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.focus.FocusDirection
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.hideFromAccessibility
+import androidx.compose.ui.semantics.isTraversalGroup
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.style.Hyphens
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.fastFirstOrNull
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import com.android.compose.modifiers.thenIf
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
+import com.android.systemui.res.R
+import kotlinx.coroutines.delay
+
+@Composable
+fun ShortcutHelper(
+ onSearchQueryChanged: (String) -> Unit,
+ onKeyboardSettingsClicked: () -> Unit,
+ modifier: Modifier = Modifier,
+ shortcutsUiState: ShortcutsUiState,
+ useSinglePane: @Composable () -> Boolean = { shouldUseSinglePane() },
+ onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
+) {
+ when (shortcutsUiState) {
+ is ShortcutsUiState.Active -> {
+ ActiveShortcutHelper(
+ shortcutsUiState,
+ useSinglePane,
+ onSearchQueryChanged,
+ modifier,
+ onKeyboardSettingsClicked,
+ onCustomizationRequested,
+ )
+ }
+
+ else -> {
+ // No-op for now.
+ }
+ }
+}
+
+@Composable
+private fun ActiveShortcutHelper(
+ shortcutsUiState: ShortcutsUiState.Active,
+ useSinglePane: @Composable () -> Boolean,
+ onSearchQueryChanged: (String) -> Unit,
+ modifier: Modifier,
+ onKeyboardSettingsClicked: () -> Unit,
+ onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
+) {
+ var selectedCategoryType by
+ remember(shortcutsUiState.defaultSelectedCategory) {
+ mutableStateOf(shortcutsUiState.defaultSelectedCategory)
+ }
+ if (useSinglePane()) {
+ ShortcutHelperSinglePane(
+ shortcutsUiState.searchQuery,
+ onSearchQueryChanged,
+ shortcutsUiState.shortcutCategories,
+ selectedCategoryType,
+ onCategorySelected = { selectedCategoryType = it },
+ onKeyboardSettingsClicked,
+ modifier,
+ )
+ } else {
+ ShortcutHelperTwoPane(
+ shortcutsUiState.searchQuery,
+ onSearchQueryChanged,
+ modifier,
+ shortcutsUiState.shortcutCategories,
+ selectedCategoryType,
+ onCategorySelected = { selectedCategoryType = it },
+ onKeyboardSettingsClicked,
+ shortcutsUiState.isShortcutCustomizerFlagEnabled,
+ onCustomizationRequested,
+ shortcutsUiState.shouldShowResetButton,
+ )
+ }
+}
+
+@Composable private fun shouldUseSinglePane() = hasCompactWindowSize()
+
+@Composable
+private fun ShortcutHelperSinglePane(
+ searchQuery: String,
+ onSearchQueryChanged: (String) -> Unit,
+ categories: List<ShortcutCategoryUi>,
+ selectedCategoryType: ShortcutCategoryType?,
+ onCategorySelected: (ShortcutCategoryType?) -> Unit,
+ onKeyboardSettingsClicked: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier =
+ modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(start = 16.dp, end = 16.dp, top = 26.dp)
+ ) {
+ TitleBar()
+ Spacer(modifier = Modifier.height(6.dp))
+ ShortcutsSearchBar(onSearchQueryChanged)
+ Spacer(modifier = Modifier.height(16.dp))
+ if (categories.isEmpty()) {
+ Box(modifier = Modifier.weight(1f)) {
+ NoSearchResultsText(horizontalPadding = 16.dp, fillHeight = true)
+ }
+ } else {
+ CategoriesPanelSinglePane(
+ searchQuery,
+ categories,
+ selectedCategoryType,
+ onCategorySelected,
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ KeyboardSettings(
+ horizontalPadding = 16.dp,
+ verticalPadding = 32.dp,
+ onClick = onKeyboardSettingsClicked,
+ )
+ }
+}
+
+@Composable
+private fun CategoriesPanelSinglePane(
+ searchQuery: String,
+ categories: List<ShortcutCategoryUi>,
+ selectedCategoryType: ShortcutCategoryType?,
+ onCategorySelected: (ShortcutCategoryType?) -> Unit,
+) {
+ Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+ categories.fastForEachIndexed { index, category ->
+ val isExpanded = selectedCategoryType == category.type
+ val itemShape =
+ if (categories.size == 1) {
+ ShortcutHelper.Shapes.singlePaneSingleCategory
+ } else if (index == 0) {
+ ShortcutHelper.Shapes.singlePaneFirstCategory
+ } else if (index == categories.lastIndex) {
+ ShortcutHelper.Shapes.singlePaneLastCategory
+ } else {
+ ShortcutHelper.Shapes.singlePaneCategory
+ }
+ CategoryItemSinglePane(
+ searchQuery = searchQuery,
+ category = category,
+ isExpanded = isExpanded,
+ onClick = {
+ onCategorySelected(
+ if (isExpanded) {
+ null
+ } else {
+ category.type
+ }
+ )
+ },
+ shape = itemShape,
+ )
+ }
+ }
+}
+
+@Composable
+private fun CategoryItemSinglePane(
+ searchQuery: String,
+ category: ShortcutCategoryUi,
+ isExpanded: Boolean,
+ onClick: () -> Unit,
+ shape: Shape,
+) {
+ Surface(color = MaterialTheme.colorScheme.surfaceBright, shape = shape, onClick = onClick) {
+ Column {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp),
+ ) {
+ ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.iconSource)
+ Spacer(modifier = Modifier.width(16.dp))
+ Text(category.label)
+ Spacer(modifier = Modifier.weight(1f))
+ RotatingExpandCollapseIcon(isExpanded)
+ }
+ AnimatedVisibility(visible = isExpanded) {
+ ShortcutCategoryDetailsSinglePane(searchQuery, category)
+ }
+ }
+ }
+}
+
+@Composable
+fun ShortcutCategoryIcon(
+ source: IconSource,
+ modifier: Modifier = Modifier,
+ contentDescription: String? = null,
+ tint: Color = LocalContentColor.current,
+) {
+ if (source.imageVector != null) {
+ Icon(source.imageVector, contentDescription, modifier, tint)
+ } else if (source.painter != null) {
+ Image(source.painter, contentDescription, modifier)
+ }
+}
+
+@Composable
+private fun RotatingExpandCollapseIcon(isExpanded: Boolean) {
+ val expandIconRotationDegrees by
+ animateFloatAsState(
+ targetValue =
+ if (isExpanded) {
+ 180f
+ } else {
+ 0f
+ },
+ label = "Expand icon rotation animation",
+ )
+ Icon(
+ modifier =
+ Modifier.background(
+ color = MaterialTheme.colorScheme.surfaceContainerHigh,
+ shape = CircleShape,
+ )
+ .graphicsLayer { rotationZ = expandIconRotationDegrees },
+ imageVector = Icons.Default.ExpandMore,
+ contentDescription =
+ if (isExpanded) {
+ stringResource(R.string.shortcut_helper_content_description_collapse_icon)
+ } else {
+ stringResource(R.string.shortcut_helper_content_description_expand_icon)
+ },
+ tint = MaterialTheme.colorScheme.onSurface,
+ )
+}
+
+@Composable
+private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategoryUi) {
+ Column(Modifier.padding(horizontal = 16.dp)) {
+ category.subCategories.fastForEach { subCategory ->
+ ShortcutSubCategorySinglePane(searchQuery, subCategory)
+ }
+ }
+}
+
+@Composable
+private fun ShortcutSubCategorySinglePane(searchQuery: String, subCategory: ShortcutSubCategory) {
+ // This @Composable is expected to be in a Column.
+ SubCategoryTitle(subCategory.label)
+ subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
+ if (index > 0) {
+ HorizontalDivider(color = MaterialTheme.colorScheme.surfaceContainerHigh)
+ }
+ Shortcut(Modifier.padding(vertical = 24.dp), searchQuery, shortcut)
+ }
+}
+
+@Composable
+private fun ShortcutHelperTwoPane(
+ searchQuery: String,
+ onSearchQueryChanged: (String) -> Unit,
+ modifier: Modifier = Modifier,
+ categories: List<ShortcutCategoryUi>,
+ selectedCategoryType: ShortcutCategoryType?,
+ onCategorySelected: (ShortcutCategoryType?) -> Unit,
+ onKeyboardSettingsClicked: () -> Unit,
+ isShortcutCustomizerFlagEnabled: Boolean,
+ onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
+ shouldShowResetButton: Boolean,
+) {
+ val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
+ var isCustomizing by remember { mutableStateOf(false) }
+
+ Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) {
+ Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
+ // Keep title centered whether customize button is visible or not.
+ Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
+ Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
+ TitleBar(isCustomizing)
+ }
+ if (isShortcutCustomizerFlagEnabled) {
+ CustomizationButtonsContainer(
+ isCustomizing = isCustomizing,
+ onToggleCustomizationMode = { isCustomizing = !isCustomizing },
+ onReset = {
+ onCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+ },
+ shouldShowResetButton = shouldShowResetButton,
+ )
+ } else {
+ Spacer(modifier = Modifier.width(if (isCustomizing) 69.dp else 133.dp))
+ }
+ }
+ }
+ Spacer(modifier = Modifier.height(12.dp))
+ Row(Modifier.fillMaxWidth()) {
+ StartSidePanel(
+ onSearchQueryChanged = onSearchQueryChanged,
+ modifier = Modifier.width(240.dp).semantics { isTraversalGroup = true },
+ categories = categories,
+ onKeyboardSettingsClicked = onKeyboardSettingsClicked,
+ selectedCategory = selectedCategoryType,
+ onCategoryClicked = { onCategorySelected(it.type) },
+ )
+ Spacer(modifier = Modifier.width(24.dp))
+ EndSidePanel(
+ searchQuery,
+ Modifier.fillMaxSize().padding(top = 8.dp).semantics { isTraversalGroup = true },
+ selectedCategory,
+ isCustomizing = isCustomizing,
+ onCustomizationRequested = onCustomizationRequested,
+ )
+ }
+ }
+}
+
+@Composable
+private fun CustomizationButtonsContainer(
+ isCustomizing: Boolean,
+ shouldShowResetButton: Boolean,
+ onToggleCustomizationMode: () -> Unit,
+ onReset: () -> Unit,
+) {
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ if (isCustomizing) {
+ if (shouldShowResetButton) {
+ ResetButton(onClick = onReset)
+ }
+ DoneButton(onClick = onToggleCustomizationMode)
+ } else {
+ CustomizeButton(onClick = onToggleCustomizationMode)
+ }
+ }
+}
+
+@Composable
+private fun ResetButton(onClick: () -> Unit) {
+ ShortcutHelperButton(
+ modifier = Modifier.heightIn(40.dp),
+ onClick = onClick,
+ color = Color.Transparent,
+ iconSource = IconSource(imageVector = Icons.Default.Refresh),
+ text = stringResource(id = R.string.shortcut_helper_reset_button_text),
+ contentColor = MaterialTheme.colorScheme.primary,
+ border = BorderStroke(color = MaterialTheme.colorScheme.outlineVariant, width = 1.dp),
+ )
+}
+
+@Composable
+private fun CustomizeButton(onClick: () -> Unit) {
+ ShortcutHelperButton(
+ modifier = Modifier.heightIn(40.dp),
+ onClick = onClick,
+ color = MaterialTheme.colorScheme.secondaryContainer,
+ iconSource = IconSource(imageVector = Icons.Default.Tune),
+ text = stringResource(id = R.string.shortcut_helper_customize_button_text),
+ contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
+ )
+}
+
+@Composable
+private fun DoneButton(onClick: () -> Unit) {
+ ShortcutHelperButton(
+ modifier = Modifier.heightIn(40.dp),
+ onClick = onClick,
+ color = MaterialTheme.colorScheme.primary,
+ text = stringResource(R.string.shortcut_helper_done_button_text),
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ )
+}
+
+@Composable
+private fun EndSidePanel(
+ searchQuery: String,
+ modifier: Modifier,
+ category: ShortcutCategoryUi?,
+ isCustomizing: Boolean,
+ onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
+) {
+ val listState = rememberLazyListState()
+ LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
+ if (category == null) {
+ NoSearchResultsText(horizontalPadding = 24.dp, fillHeight = false)
+ return
+ }
+ LazyColumn(modifier = modifier, state = listState) {
+ items(category.subCategories) { subcategory ->
+ SubCategoryContainerDualPane(
+ searchQuery = searchQuery,
+ subCategory = subcategory,
+ isCustomizing = isCustomizing and category.type.includeInCustomization,
+ onCustomizationRequested = { requestInfo ->
+ when (requestInfo) {
+ is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add ->
+ onCustomizationRequested(requestInfo.copy(categoryType = category.type))
+
+ is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete ->
+ onCustomizationRequested(requestInfo.copy(categoryType = category.type))
+
+ ShortcutCustomizationRequestInfo.Reset ->
+ onCustomizationRequested(requestInfo)
+ }
+ },
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+ }
+}
+
+@Composable
+private fun NoSearchResultsText(horizontalPadding: Dp, fillHeight: Boolean) {
+ var modifier = Modifier.fillMaxWidth()
+ if (fillHeight) {
+ modifier = modifier.fillMaxHeight()
+ }
+ Text(
+ stringResource(R.string.shortcut_helper_no_search_results),
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier =
+ modifier
+ .padding(vertical = 8.dp)
+ .background(MaterialTheme.colorScheme.surfaceBright, RoundedCornerShape(28.dp))
+ .padding(horizontal = horizontalPadding, vertical = 24.dp),
+ )
+}
+
+@Composable
+private fun SubCategoryContainerDualPane(
+ searchQuery: String,
+ subCategory: ShortcutSubCategory,
+ isCustomizing: Boolean,
+ onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit,
+) {
+ Surface(
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(28.dp),
+ color = MaterialTheme.colorScheme.surfaceBright,
+ ) {
+ Column(Modifier.padding(16.dp)) {
+ SubCategoryTitle(subCategory.label)
+ Spacer(Modifier.height(8.dp))
+ subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
+ if (index > 0) {
+ HorizontalDivider(
+ modifier = Modifier.padding(horizontal = 8.dp),
+ color = MaterialTheme.colorScheme.surfaceContainerHigh,
+ )
+ }
+ Shortcut(
+ modifier = Modifier.padding(vertical = 8.dp),
+ searchQuery = searchQuery,
+ shortcut = shortcut,
+ isCustomizing = isCustomizing && shortcut.isCustomizable,
+ onCustomizationRequested = { requestInfo ->
+ when (requestInfo) {
+ is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add ->
+ onCustomizationRequested(
+ requestInfo.copy(subCategoryLabel = subCategory.label)
+ )
+
+ is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete ->
+ onCustomizationRequested(
+ requestInfo.copy(subCategoryLabel = subCategory.label)
+ )
+
+ ShortcutCustomizationRequestInfo.Reset ->
+ onCustomizationRequested(requestInfo)
+ }
+ },
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun SubCategoryTitle(title: String) {
+ Text(
+ title,
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.primary,
+ )
+}
+
+@Composable
+private fun Shortcut(
+ modifier: Modifier,
+ searchQuery: String,
+ shortcut: ShortcutModel,
+ isCustomizing: Boolean = false,
+ onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
+) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val isFocused by interactionSource.collectIsFocusedAsState()
+ val focusColor = MaterialTheme.colorScheme.secondary
+ Row(
+ modifier
+ .thenIf(isFocused) {
+ Modifier.border(width = 3.dp, color = focusColor, shape = RoundedCornerShape(16.dp))
+ }
+ .focusable(interactionSource = interactionSource)
+ .padding(8.dp)
+ .semantics(mergeDescendants = true) { contentDescription = shortcut.contentDescription }
+ ) {
+ Row(
+ modifier =
+ Modifier.width(128.dp).align(Alignment.CenterVertically).weight(0.333f).semantics {
+ hideFromAccessibility()
+ },
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ if (shortcut.icon != null) {
+ ShortcutIcon(
+ shortcut.icon,
+ modifier = Modifier.size(24.dp).semantics { hideFromAccessibility() },
+ )
+ }
+ ShortcutDescriptionText(
+ searchQuery = searchQuery,
+ shortcut = shortcut,
+ modifier = Modifier.semantics { hideFromAccessibility() },
+ )
+ }
+ Spacer(modifier = Modifier.width(24.dp).semantics { hideFromAccessibility() })
+ ShortcutKeyCombinations(
+ modifier = Modifier.weight(.666f).semantics { hideFromAccessibility() },
+ shortcut = shortcut,
+ isCustomizing = isCustomizing,
+ onAddShortcutRequested = {
+ onCustomizationRequested(
+ ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add(
+ label = shortcut.label,
+ shortcutCommand = shortcut.commands.first(),
+ )
+ )
+ },
+ onDeleteShortcutRequested = {
+ onCustomizationRequested(
+ ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete(
+ label = shortcut.label,
+ shortcutCommand = shortcut.commands.first(),
+ )
+ )
+ },
+ )
+ }
+}
+
+@Composable
+fun ShortcutIcon(
+ icon: ShortcutIcon,
+ modifier: Modifier = Modifier,
+ contentDescription: String? = null,
+) {
+ val context = LocalContext.current
+ val drawable =
+ remember(icon.packageName, icon.resourceId) {
+ Icon.createWithResource(icon.packageName, icon.resourceId).loadDrawable(context)
+ } ?: return
+ Image(
+ painter = rememberDrawablePainter(drawable),
+ contentDescription = contentDescription,
+ modifier = modifier,
+ )
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+private fun ShortcutKeyCombinations(
+ modifier: Modifier = Modifier,
+ shortcut: ShortcutModel,
+ isCustomizing: Boolean = false,
+ onAddShortcutRequested: () -> Unit = {},
+ onDeleteShortcutRequested: () -> Unit = {},
+) {
+ FlowRow(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ itemVerticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.End,
+ ) {
+ shortcut.commands.forEachIndexed { index, command ->
+ if (index > 0) {
+ ShortcutOrSeparator(spacing = 16.dp)
+ }
+ ShortcutCommandContainer(showBackground = command.isCustom) { ShortcutCommand(command) }
+ }
+
+ if (isCustomizing) Spacer(modifier = Modifier.width(16.dp))
+
+ AnimatedVisibility(visible = isCustomizing) {
+ if (shortcut.containsCustomShortcutCommands) {
+ DeleteShortcutButton(onDeleteShortcutRequested)
+ } else {
+ AddShortcutButton(onAddShortcutRequested)
+ }
+ }
+ }
+}
+
+@Composable
+private fun AddShortcutButton(onClick: () -> Unit) {
+ ShortcutHelperButton(
+ modifier = Modifier.size(32.dp),
+ onClick = onClick,
+ color = Color.Transparent,
+ iconSource = IconSource(imageVector = Icons.Default.Add),
+ contentColor = MaterialTheme.colorScheme.primary,
+ contentPaddingVertical = 0.dp,
+ contentPaddingHorizontal = 0.dp,
+ contentDescription = stringResource(R.string.shortcut_helper_add_shortcut_button_label),
+ shape = CircleShape,
+ border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outline),
+ )
+}
+
+@Composable
+private fun DeleteShortcutButton(onClick: () -> Unit) {
+ ShortcutHelperButton(
+ modifier = Modifier.size(32.dp),
+ onClick = onClick,
+ color = Color.Transparent,
+ iconSource = IconSource(imageVector = Icons.Default.DeleteOutline),
+ contentColor = MaterialTheme.colorScheme.primary,
+ contentPaddingVertical = 0.dp,
+ contentPaddingHorizontal = 0.dp,
+ contentDescription = stringResource(R.string.shortcut_helper_delete_shortcut_button_label),
+ shape = CircleShape,
+ border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outline),
+ )
+}
+
+@Composable
+private fun ShortcutCommandContainer(showBackground: Boolean, content: @Composable () -> Unit) {
+ if (showBackground) {
+ Box(
+ modifier =
+ Modifier.wrapContentSize()
+ .background(
+ color = MaterialTheme.colorScheme.outlineVariant,
+ shape = RoundedCornerShape(16.dp),
+ )
+ .padding(4.dp)
+ ) {
+ content()
+ }
+ } else {
+ content()
+ }
+}
+
+@Composable
+private fun ShortcutCommand(command: ShortcutCommand) {
+ Row {
+ command.keys.forEachIndexed { keyIndex, key ->
+ if (keyIndex > 0) {
+ Spacer(Modifier.width(4.dp))
+ }
+ ShortcutKeyContainer {
+ if (key is ShortcutKey.Text) {
+ ShortcutTextKey(key)
+ } else if (key is ShortcutKey.Icon) {
+ ShortcutIconKey(key)
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun ShortcutKeyContainer(shortcutKeyContent: @Composable BoxScope.() -> Unit) {
+ Box(
+ modifier =
+ Modifier.height(36.dp)
+ .background(
+ color = MaterialTheme.colorScheme.surfaceContainer,
+ shape = RoundedCornerShape(12.dp),
+ )
+ ) {
+ shortcutKeyContent()
+ }
+}
+
+@Composable
+private fun BoxScope.ShortcutTextKey(key: ShortcutKey.Text) {
+ Text(
+ text = key.value,
+ modifier =
+ Modifier.align(Alignment.Center).padding(horizontal = 12.dp).semantics {
+ hideFromAccessibility()
+ },
+ style = MaterialTheme.typography.titleSmall,
+ )
+}
+
+@Composable
+private fun BoxScope.ShortcutIconKey(key: ShortcutKey.Icon) {
+ Icon(
+ painter =
+ when (key) {
+ is ShortcutKey.Icon.ResIdIcon -> painterResource(key.drawableResId)
+ is ShortcutKey.Icon.DrawableIcon -> rememberDrawablePainter(drawable = key.drawable)
+ },
+ contentDescription = null,
+ modifier = Modifier.align(Alignment.Center).padding(6.dp),
+ )
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+private fun FlowRowScope.ShortcutOrSeparator(spacing: Dp) {
+ Spacer(Modifier.width(spacing))
+ Text(
+ text = stringResource(R.string.shortcut_helper_key_combinations_or_separator),
+ modifier = Modifier.align(Alignment.CenterVertically).semantics { hideFromAccessibility() },
+ style = MaterialTheme.typography.titleSmall,
+ )
+ Spacer(Modifier.width(spacing))
+}
+
+@Composable
+private fun ShortcutDescriptionText(
+ searchQuery: String,
+ shortcut: ShortcutModel,
+ modifier: Modifier = Modifier,
+) {
+ Text(
+ modifier = modifier,
+ text = textWithHighlightedSearchQuery(shortcut.label, searchQuery),
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+}
+
+@Composable
+private fun textWithHighlightedSearchQuery(text: String, searchValue: String) =
+ buildAnnotatedString {
+ val searchIndex = text.lowercase().indexOf(searchValue.trim().lowercase())
+ val postSearchIndex = searchIndex + searchValue.trim().length
+
+ if (searchIndex > 0) {
+ val preSearchText = text.substring(0, searchIndex)
+ append(preSearchText)
+ }
+ if (searchIndex >= 0) {
+ val searchText = text.substring(searchIndex, postSearchIndex)
+ withStyle(style = SpanStyle(background = MaterialTheme.colorScheme.primaryContainer)) {
+ append(searchText)
+ }
+ if (postSearchIndex < text.length) {
+ val postSearchText = text.substring(postSearchIndex)
+ append(postSearchText)
+ }
+ } else {
+ append(text)
+ }
+ }
+
+@Composable
+private fun StartSidePanel(
+ onSearchQueryChanged: (String) -> Unit,
+ modifier: Modifier,
+ categories: List<ShortcutCategoryUi>,
+ onKeyboardSettingsClicked: () -> Unit,
+ selectedCategory: ShortcutCategoryType?,
+ onCategoryClicked: (ShortcutCategoryUi) -> Unit,
+) {
+ CompositionLocalProvider(
+ // Restrict system font scale increases up to a max so categories display correctly.
+ LocalDensity provides
+ Density(
+ density = LocalDensity.current.density,
+ fontScale = LocalDensity.current.fontScale.coerceIn(1f, 1.5f),
+ )
+ ) {
+ Column(modifier) {
+ ShortcutsSearchBar(onSearchQueryChanged)
+ Spacer(modifier = Modifier.heightIn(8.dp))
+ CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
+ Spacer(modifier = Modifier.weight(1f))
+ KeyboardSettings(
+ horizontalPadding = 24.dp,
+ verticalPadding = 24.dp,
+ onKeyboardSettingsClicked,
+ )
+ }
+ }
+}
+
+@Composable
+private fun CategoriesPanelTwoPane(
+ categories: List<ShortcutCategoryUi>,
+ selectedCategory: ShortcutCategoryType?,
+ onCategoryClicked: (ShortcutCategoryUi) -> Unit,
+) {
+ Column {
+ categories.fastForEach {
+ CategoryItemTwoPane(
+ label = it.label,
+ iconSource = it.iconSource,
+ selected = selectedCategory == it.type,
+ onClick = { onCategoryClicked(it) },
+ )
+ }
+ }
+}
+
+@Composable
+private fun CategoryItemTwoPane(
+ label: String,
+ iconSource: IconSource,
+ selected: Boolean,
+ onClick: () -> Unit,
+ colors: NavigationDrawerItemColors =
+ NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent),
+) {
+ SelectableShortcutSurface(
+ selected = selected,
+ onClick = onClick,
+ modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 64.dp).fillMaxWidth(),
+ shape = RoundedCornerShape(28.dp),
+ color = colors.containerColor(selected).value,
+ interactionsConfig =
+ InteractionsConfig(
+ hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+ hoverOverlayAlpha = 0.11f,
+ pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
+ pressedOverlayAlpha = 0.15f,
+ focusOutlineColor = MaterialTheme.colorScheme.secondary,
+ focusOutlineStrokeWidth = 3.dp,
+ focusOutlinePadding = 2.dp,
+ surfaceCornerRadius = 28.dp,
+ focusOutlineCornerRadius = 33.dp,
+ ),
+ ) {
+ Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) {
+ ShortcutCategoryIcon(
+ modifier = Modifier.size(24.dp),
+ source = iconSource,
+ contentDescription = null,
+ tint = colors.iconColor(selected).value,
+ )
+ Spacer(Modifier.width(12.dp))
+ Box(Modifier.weight(1f)) {
+ Text(
+ fontSize = 18.sp,
+ color = colors.textColor(selected).value,
+ style = MaterialTheme.typography.titleSmall.copy(hyphens = Hyphens.Auto),
+ text = label,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+private fun TitleBar(isCustomizing: Boolean = false) {
+ val text =
+ if (isCustomizing) {
+ stringResource(R.string.shortcut_helper_customize_mode_title)
+ } else {
+ stringResource(R.string.shortcut_helper_title)
+ }
+ CenterAlignedTopAppBar(
+ colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
+ title = {
+ Text(
+ text = text,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.headlineSmall,
+ )
+ },
+ windowInsets = WindowInsets(top = 0.dp, bottom = 0.dp, left = 0.dp, right = 0.dp),
+ expandedHeight = 64.dp,
+ )
+}
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+private fun ShortcutsSearchBar(onQueryChange: (String) -> Unit) {
+ // Using an "internal query" to make sure the SearchBar is immediately updated, otherwise
+ // the cursor moves to the wrong position sometimes, when waiting for the query to come back
+ // from the ViewModel.
+ var queryInternal by remember { mutableStateOf("") }
+ val focusRequester = remember { FocusRequester() }
+ val focusManager = LocalFocusManager.current
+ LaunchedEffect(Unit) {
+ // TODO(b/272065229): Added minor delay so TalkBack can take focus of search box by default,
+ // remove when default a11y focus is fixed.
+ delay(50)
+ focusRequester.requestFocus()
+ }
+ SearchBar(
+ modifier =
+ Modifier.fillMaxWidth().focusRequester(focusRequester).onKeyEvent {
+ if (it.key == Key.DirectionDown) {
+ focusManager.moveFocus(FocusDirection.Down)
+ return@onKeyEvent true
+ } else {
+ return@onKeyEvent false
+ }
+ },
+ colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright),
+ query = queryInternal,
+ active = false,
+ onActiveChange = {},
+ onQueryChange = {
+ queryInternal = it
+ onQueryChange(it)
+ },
+ onSearch = {},
+ leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
+ placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) },
+ windowInsets = WindowInsets(top = 0.dp, bottom = 0.dp, left = 0.dp, right = 0.dp),
+ content = {},
+ )
+}
+
+@Composable
+private fun KeyboardSettings(horizontalPadding: Dp, verticalPadding: Dp, onClick: () -> Unit) {
+ ClickableShortcutSurface(
+ onClick = onClick,
+ shape = RoundedCornerShape(24.dp),
+ color = Color.Transparent,
+ modifier =
+ Modifier.semantics { role = Role.Button }.fillMaxWidth().padding(horizontal = 12.dp),
+ interactionsConfig =
+ InteractionsConfig(
+ hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+ hoverOverlayAlpha = 0.11f,
+ pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
+ pressedOverlayAlpha = 0.15f,
+ focusOutlineColor = MaterialTheme.colorScheme.secondary,
+ focusOutlinePadding = 8.dp,
+ focusOutlineStrokeWidth = 3.dp,
+ surfaceCornerRadius = 24.dp,
+ focusOutlineCornerRadius = 28.dp,
+ hoverPadding = 8.dp,
+ ),
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ text =
+ stringResource(id = R.string.shortcut_helper_keyboard_settings_buttons_label),
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ fontSize = 16.sp,
+ style = MaterialTheme.typography.titleSmall,
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ Icon(
+ imageVector = Icons.AutoMirrored.Default.OpenInNew,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.size(24.dp),
+ )
+ }
+ }
+}
+
+object ShortcutHelper {
+
+ object Shapes {
+ val singlePaneFirstCategory =
+ RoundedCornerShape(
+ topStart = Dimensions.SinglePaneCategoryCornerRadius,
+ topEnd = Dimensions.SinglePaneCategoryCornerRadius,
+ )
+ val singlePaneLastCategory =
+ RoundedCornerShape(
+ bottomStart = Dimensions.SinglePaneCategoryCornerRadius,
+ bottomEnd = Dimensions.SinglePaneCategoryCornerRadius,
+ )
+ val singlePaneSingleCategory =
+ RoundedCornerShape(size = Dimensions.SinglePaneCategoryCornerRadius)
+ val singlePaneCategory = RectangleShape
+ }
+
+ object Dimensions {
+ val SinglePaneCategoryCornerRadius = 28.dp
+ }
+
+ internal const val TAG = "ShortcutHelperUI"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 8c6037107c5a..cf712f111034 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -19,8 +19,12 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import android.util.MathUtils
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -39,6 +43,10 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -47,11 +55,6 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlin.time.Duration.Companion.milliseconds
-import kotlin.time.Duration.Companion.seconds
-import java.util.UUID
-import javax.inject.Inject
-import com.android.app.tracing.coroutines.launchTraced as launch
@SysUISingleton
class FromLockscreenTransitionInteractor
@@ -68,6 +71,8 @@ constructor(
powerInteractor: PowerInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
private val communalSettingsInteractor: CommunalSettingsInteractor,
+ private val communalInteractor: CommunalInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
private val swipeToDismissInteractor: SwipeToDismissInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
@@ -94,6 +99,9 @@ constructor(
if (!communalSceneKtfRefactor()) {
listenForLockscreenToGlanceableHub()
}
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ listenForLockscreenToGlanceableHubV2()
+ }
}
/**
@@ -268,9 +276,7 @@ constructor(
it.transitionState == TransitionState.CANCELED &&
it.to == KeyguardState.PRIMARY_BOUNCER
}
- .collect {
- transitionId = null
- }
+ .collect { transitionId = null }
}
}
@@ -370,6 +376,19 @@ constructor(
}
}
+ private fun listenForLockscreenToGlanceableHubV2() {
+ scope.launch {
+ communalInteractor.shouldShowCommunal
+ .filterRelevantKeyguardStateAnd { shouldShow -> shouldShow }
+ .collect {
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "lockscreen to communal",
+ )
+ }
+ }
+ }
+
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 42cbd7d39248..a1f288edcdd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -24,6 +24,8 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.sample
@@ -32,6 +34,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -166,4 +169,14 @@ constructor(
isKeyguardEnabled.value && lockPatternUtils.isLockScreenDisabled(userId)
}
}
+
+ suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+ isKeyguardEnabled
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnName = "isKeyguardEnabled",
+ initialValue = isKeyguardEnabled.value,
+ )
+ .collect()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 75178f0ffef0..3739d17da6c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -42,6 +42,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -60,6 +62,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.debounce
@@ -533,6 +536,16 @@ constructor(
repository.setNotificationStackAbsoluteBottom(bottom)
}
+ suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+ isDozing
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnName = "isDozing",
+ initialValue = isDozing.value,
+ )
+ .collect()
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index de5088c3521c..898b68d0f4b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -475,7 +475,7 @@ constructor(
KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED,
value =
- com.android.systemui.Flags.lockscreenCustomClocks() ||
+ com.android.systemui.shared.Flags.lockscreenCustomClocks() ||
featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
),
KeyguardPickerFlag(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 8429c23d2018..0b116ded42da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -15,9 +15,11 @@
*/
package com.android.systemui.keyguard.domain.interactor
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.logging.ScrimLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_DURATION
import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
import com.android.systemui.keyguard.shared.model.Edge
@@ -31,12 +33,13 @@ import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.sample
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.flowOn
@SysUISingleton
class LightRevealScrimInteractor
@@ -47,6 +50,7 @@ constructor(
@Application private val scope: CoroutineScope,
private val scrimLogger: ScrimLogger,
private val powerInteractor: Lazy<PowerInteractor>,
+ @Background backgroundDispatcher: CoroutineDispatcher,
) {
init {
listenForStartedKeyguardTransitionStep()
@@ -113,6 +117,7 @@ constructor(
repository.maxAlpha
}
}
+ .flowOn(backgroundDispatcher)
val revealAmount =
repository.revealAmount.filter {
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 70a52afeb8c2..017fe169ca88 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
@@ -56,6 +56,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.view.layout.sections.AodPromotedNotificationSection
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -184,6 +185,7 @@ object KeyguardRootViewBinder {
viewModel.translationY.collect { y ->
childViews[burnInLayerId]?.translationY = y
childViews[largeClockId]?.translationY = y
+ childViews[aodPromotedNotificationId]?.translationY = y
childViews[aodNotificationIconContainerId]?.translationY = y
}
}
@@ -195,6 +197,7 @@ object KeyguardRootViewBinder {
state.isToOrFrom(KeyguardState.AOD) -> {
// Large Clock is not translated in the x direction
childViews[burnInLayerId]?.translationX = px
+ childViews[aodPromotedNotificationId]?.translationX = px
childViews[aodNotificationIconContainerId]?.translationX = px
}
state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
@@ -291,11 +294,17 @@ object KeyguardRootViewBinder {
blueprintViewModel.refreshBlueprint()
}
childViews[aodNotificationIconContainerId]
- ?.setAodNotifIconContainerIsVisible(
- isVisible,
- iconsAppearTranslationPx.value,
- screenOffAnimationController,
- )
+ ?.setAodNotifIconContainerIsVisible(isVisible)
+ }
+ }
+
+ launch {
+ viewModel.isNotifIconContainerVisible.collect { isVisible ->
+ if (isVisible.value) {
+ blueprintViewModel.refreshBlueprint()
+ }
+ childViews[aodPromotedNotificationId]
+ ?.setAodNotifIconContainerIsVisible(isVisible)
}
}
@@ -524,11 +533,7 @@ object KeyguardRootViewBinder {
}
}
- private fun View.setAodNotifIconContainerIsVisible(
- isVisible: AnimatedValue<Boolean>,
- iconsAppearTranslationPx: Int,
- screenOffAnimationController: ScreenOffAnimationController,
- ) {
+ private fun View.setAodNotifIconContainerIsVisible(isVisible: AnimatedValue<Boolean>) {
animate().cancel()
val animatorListener =
object : AnimatorListenerAdapter() {
@@ -563,6 +568,7 @@ object KeyguardRootViewBinder {
}
private val burnInLayerId = R.id.burn_in_layer
+ private val aodPromotedNotificationId = AodPromotedNotificationSection.viewId
private val aodNotificationIconContainerId = R.id.aod_notification_icon_container
private val largeClockId = customR.id.lockscreen_clock_view_large
private val smallClockId = customR.id.lockscreen_clock_view
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt
index 454ba9af5745..d2808627163e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt
@@ -19,6 +19,7 @@
package com.android.systemui.keyguard.ui.view
import android.graphics.Rect
+import android.os.DeadObjectException
import android.util.Log
import android.view.View
import com.android.systemui.dagger.SysUISingleton
@@ -192,7 +193,12 @@ constructor(
launcherAnimationController?.let {
manualUnlockAmount = amount
- it.setUnlockAmount(amount, forceIfAnimating)
+
+ try {
+ it.setUnlockAmount(amount, forceIfAnimating)
+ } catch (e: DeadObjectException) {
+ Log.e(TAG, "DeadObjectException in setUnlockAmount($amount, $forceIfAnimating)", e)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 84fdc6e3a433..13cd5839e1c8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -262,16 +262,6 @@ constructor(
deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
}
- private fun DeviceEntryIconView.IconType.toAccessibilityHintType():
- DeviceEntryIconView.AccessibilityHintType {
- return when (this) {
- DeviceEntryIconView.IconType.FINGERPRINT,
- DeviceEntryIconView.IconType.LOCK -> DeviceEntryIconView.AccessibilityHintType.BOUNCER
- DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER
- DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE
- }
- }
-
companion object {
const val UNLOCKED_DELAY_MS = 50L
}
@@ -282,3 +272,13 @@ data class BurnInOffsets(
val y: Int, // current y burn in offset based on the aodTransitionAmount
val progress: Float, // current progress based on the aodTransitionAmount
)
+
+fun DeviceEntryIconView.IconType.toAccessibilityHintType():
+ DeviceEntryIconView.AccessibilityHintType {
+ return when (this) {
+ DeviceEntryIconView.IconType.FINGERPRINT,
+ DeviceEntryIconView.IconType.LOCK -> DeviceEntryIconView.AccessibilityHintType.BOUNCER
+ DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER
+ DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index f0c924f99033..11a509a4fa61 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -307,6 +307,16 @@ constructor(
BurnInScaleViewModel(scale = it.scale, scaleClockOnly = it.scaleClockOnly)
}
+ val isAodPromotedNotifVisible: StateFlow<Boolean> =
+ keyguardTransitionInteractor
+ .transitionValue(AOD)
+ .map { it == 1f }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
/** Is the notification icon container visible? */
val isNotifIconContainerVisible: StateFlow<AnimatedValue<Boolean>> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java
index 4496b258bde4..7b1c62e2a0e5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java
@@ -36,6 +36,7 @@ public class MediaItem {
private final String mTitle;
@MediaItemType
private final int mMediaItemType;
+ private final boolean mIsFirstDeviceInGroup;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -54,7 +55,18 @@ public class MediaItem {
* name.
*/
public static MediaItem createDeviceMediaItem(@NonNull MediaDevice device) {
- return new MediaItem(device, device.getName(), MediaItemType.TYPE_DEVICE);
+ return new MediaItem(device, device.getName(), MediaItemType.TYPE_DEVICE, false);
+ }
+
+ /**
+ * Returns a new {@link MediaItemType#TYPE_DEVICE} {@link MediaItem} with its {@link
+ * #getMediaDevice() media device} set to {@code device} and its title set to {@code device}'s
+ * name.
+ */
+ public static MediaItem createDeviceMediaItem(
+ @NonNull MediaDevice device, boolean isFirstDeviceInGroup) {
+ return new MediaItem(
+ device, device.getName(), MediaItemType.TYPE_DEVICE, isFirstDeviceInGroup);
}
/**
@@ -63,7 +75,10 @@ public class MediaItem {
*/
public static MediaItem createPairNewDeviceMediaItem() {
return new MediaItem(
- /* device */ null, /* title */ null, MediaItemType.TYPE_PAIR_NEW_DEVICE);
+ /* device */ null,
+ /* title */ null,
+ MediaItemType.TYPE_PAIR_NEW_DEVICE,
+ /* mIsFirstDeviceInGroup */ false);
}
/**
@@ -71,14 +86,22 @@ public class MediaItem {
* title and a {@code null} {@link #getMediaDevice() media device}.
*/
public static MediaItem createGroupDividerMediaItem(@Nullable String title) {
- return new MediaItem(/* device */ null, title, MediaItemType.TYPE_GROUP_DIVIDER);
+ return new MediaItem(
+ /* device */ null,
+ title,
+ MediaItemType.TYPE_GROUP_DIVIDER,
+ /* misFirstDeviceInGroup */ false);
}
private MediaItem(
- @Nullable MediaDevice device, @Nullable String title, @MediaItemType int type) {
+ @Nullable MediaDevice device,
+ @Nullable String title,
+ @MediaItemType int type,
+ boolean isFirstDeviceInGroup) {
this.mMediaDeviceOptional = Optional.ofNullable(device);
this.mTitle = title;
this.mMediaItemType = type;
+ this.mIsFirstDeviceInGroup = isFirstDeviceInGroup;
}
public Optional<MediaDevice> getMediaDevice() {
@@ -106,4 +129,8 @@ public class MediaItem {
public int getMediaItemType() {
return mMediaItemType;
}
+
+ public boolean isFirstDeviceInGroup() {
+ return mIsFirstDeviceInGroup;
+ }
}
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 53f3b3a7a59d..52b3c3ecacc6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -21,6 +21,7 @@ import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECT
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
import android.annotation.DrawableRes;
+import android.annotation.StringRes;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -38,6 +39,7 @@ import androidx.core.widget.CompoundButtonCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.media.flags.Flags;
import com.android.settingslib.media.LocalMediaManager.MediaDeviceState;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.res.R;
@@ -55,6 +57,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
private static final float DEVICE_DISCONNECTED_ALPHA = 0.5f;
private static final float DEVICE_CONNECTED_ALPHA = 1f;
protected List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
+ private boolean mShouldGroupSelectedMediaItems = Flags.enableOutputSwitcherSessionGrouping();
public MediaOutputAdapter(MediaSwitchingController controller) {
super(controller);
@@ -65,6 +68,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
public void updateItems() {
mMediaItemList.clear();
mMediaItemList.addAll(mController.getMediaItemList());
+ if (mShouldGroupSelectedMediaItems) {
+ if (mController.getSelectedMediaDevice().size() == 1) {
+ // Don't group devices if initially there isn't more than one selected.
+ mShouldGroupSelectedMediaItems = false;
+ }
+ }
notifyDataSetChanged();
}
@@ -101,7 +110,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
break;
case MediaItem.MediaItemType.TYPE_DEVICE:
((MediaDeviceViewHolder) viewHolder).onBind(
- currentMediaItem.getMediaDevice().get(),
+ currentMediaItem,
position);
break;
default:
@@ -141,8 +150,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
super(view);
}
- @Override
- void onBind(MediaDevice device, int position) {
+ void onBind(MediaItem mediaItem, int position) {
+ MediaDevice device = mediaItem.getMediaDevice().get();
super.onBind(device, position);
boolean isMutingExpectedDeviceExist = mController.hasMutingExpectedDevice();
final boolean currentlyConnected = isCurrentlyConnected(device);
@@ -150,6 +159,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
if (mCurrentActivePosition == position) {
mCurrentActivePosition = -1;
}
+ mItemLayout.setVisibility(View.VISIBLE);
mStatusIcon.setVisibility(View.GONE);
enableFocusPropertyForView(mContainerLayout);
@@ -174,6 +184,30 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
updateFullItemClickListener(v -> onItemClick(v, device));
setSingleLineLayout(device.getName());
initFakeActiveDevice(device);
+ } else if (mShouldGroupSelectedMediaItems
+ && mController.getSelectedMediaDevice().size() > 1
+ && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
+ if (!mediaItem.isFirstDeviceInGroup()) {
+ mItemLayout.setVisibility(View.GONE);
+ mEndTouchArea.setVisibility(View.GONE);
+ } else {
+ String sessionName = mController.getSessionName().toString();
+ updateUnmutedVolumeIcon(null);
+ updateEndClickAreaWithIcon(
+ v -> {
+ mShouldGroupSelectedMediaItems = false;
+ notifyDataSetChanged();
+ },
+ R.drawable.media_output_item_expand_group,
+ R.string.accessibility_expand_group);
+ disableFocusPropertyForView(mContainerLayout);
+ setUpContentDescriptionForView(mSeekBar, mContext.getString(
+ R.string.accessibility_cast_name, sessionName));
+ setSingleLineLayout(sessionName, true /* showSeekBar */,
+ false /* showProgressBar */, false /* showCheckBox */,
+ true /* showEndTouchArea */);
+ initGroupSeekbar(isCurrentSeekbarInvisible);
+ }
} else if (device.hasSubtext()) {
boolean isActiveWithOngoingSession =
(device.hasOngoingSession() && (currentlyConnected || isDeviceIncluded(
@@ -237,6 +271,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
// selected device in group
boolean isDeviceDeselectable = isDeviceIncluded(
mController.getDeselectableMediaDevice(), device);
+ boolean showEndArea = !Flags.enableOutputSwitcherSessionGrouping()
+ || isDeviceDeselectable;
updateUnmutedVolumeIcon(device);
updateGroupableCheckBox(true, isDeviceDeselectable, device);
updateEndClickArea(device, isDeviceDeselectable);
@@ -244,7 +280,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
setUpContentDescriptionForView(mSeekBar, device);
setSingleLineLayout(device.getName(), true /* showSeekBar */,
false /* showProgressBar */, true /* showCheckBox */,
- true /* showEndTouchArea */);
+ showEndArea /* showEndTouchArea */);
initSeekbar(device, isCurrentSeekbarInvisible);
} else if (!mController.hasAdjustVolumeUserRestriction()
&& currentlyConnected) {
@@ -335,19 +371,29 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
private void updateEndClickAreaAsSessionEditing(MediaDevice device, @DrawableRes int id) {
- mEndClickIcon.setOnClickListener(null);
- mEndTouchArea.setOnClickListener(null);
+ updateEndClickAreaWithIcon(
+ v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v),
+ id,
+ R.string.accessibility_open_application);
+ }
+
+ private void updateEndClickAreaWithIcon(View.OnClickListener clickListener,
+ @DrawableRes int iconDrawableId,
+ @StringRes int accessibilityStringId) {
updateEndClickAreaColor(mController.getColorSeekbarProgress());
mEndClickIcon.setImageTintList(
ColorStateList.valueOf(mController.getColorItemContent()));
- mEndClickIcon.setOnClickListener(
- v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v));
+ mEndClickIcon.setOnClickListener(clickListener);
mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick());
- Drawable drawable = mContext.getDrawable(id);
+ Drawable drawable = mContext.getDrawable(iconDrawableId);
mEndClickIcon.setImageDrawable(drawable);
if (drawable instanceof AnimatedVectorDrawable) {
((AnimatedVectorDrawable) drawable).start();
}
+ if (Flags.enableOutputSwitcherSessionGrouping()) {
+ setUpContentDescriptionForView(
+ mEndClickIcon, mContext.getString(accessibilityStringId));
+ }
}
public void updateEndClickAreaColor(int color) {
@@ -479,12 +525,17 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
private void setUpContentDescriptionForView(View view, MediaDevice device) {
- view.setContentDescription(
+ setUpContentDescriptionForView(
+ view,
mContext.getString(device.getDeviceType()
== MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
? R.string.accessibility_bluetooth_name
: R.string.accessibility_cast_name, device.getName()));
}
+
+ protected void setUpContentDescriptionForView(View view, String description) {
+ view.setContentDescription(description);
+ }
}
class MediaGroupDividerViewHolder extends RecyclerView.ViewHolder {
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 9b24c69cac30..ee2d8aa46264 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -42,6 +42,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.media.flags.Flags;
import com.android.settingslib.media.InputMediaDevice;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.utils.ThreadUtils;
@@ -211,6 +212,10 @@ public abstract class MediaOutputBaseAdapter extends
mTitleText.setText(title);
mCheckBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE);
mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
+ if (Flags.enableOutputSwitcherSessionGrouping()) {
+ mEndClickIcon.setVisibility(
+ !showCheckBox && showEndTouchArea ? View.VISIBLE : View.GONE);
+ }
ViewGroup.MarginLayoutParams params =
(ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
@@ -265,14 +270,8 @@ public abstract class MediaOutputBaseAdapter extends
mController.getActiveRadius(), 0, 0});
}
- void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
- if (!mController.isVolumeControlEnabled(device)) {
- disableSeekBar();
- } else {
- enableSeekBar(device);
- }
- mSeekBar.setMaxVolume(device.getMaxVolume());
- final int currentVolume = device.getCurrentVolume();
+ private void initializeSeekbarVolume(
+ MediaDevice device, int currentVolume, boolean isCurrentSeekbarInvisible) {
if (!mIsDragging) {
if (mSeekBar.getVolume() != currentVolume && (mLatestUpdateVolume == -1
|| currentVolume == mLatestUpdateVolume)) {
@@ -307,54 +306,75 @@ public abstract class MediaOutputBaseAdapter extends
if (mIsInitVolumeFirstTime) {
mIsInitVolumeFirstTime = false;
}
- mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- boolean mStartFromMute = false;
+ }
+
+ void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
+ SeekBarVolumeControl volumeControl = new SeekBarVolumeControl() {
@Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (device == null || !fromUser) {
- return;
- }
+ public int getVolume() {
+ return device.getCurrentVolume();
+ }
+ @Override
+ public void setVolume(int volume) {
+ mController.adjustVolume(device, volume);
+ }
+
+ @Override
+ public void onMute() {
+ mController.logInteractionUnmuteDevice(device);
+ }
+ };
- final String percentageString = mContext.getResources().getString(
- R.string.media_output_dialog_volume_percentage,
- mSeekBar.getPercentage());
- mVolumeValueText.setText(percentageString);
+ if (!mController.isVolumeControlEnabled(device)) {
+ disableSeekBar();
+ } else {
+ enableSeekBar(volumeControl);
+ }
+ mSeekBar.setMaxVolume(device.getMaxVolume());
+ final int currentVolume = device.getCurrentVolume();
+ initializeSeekbarVolume(device, currentVolume, isCurrentSeekbarInvisible);
- if (mStartFromMute) {
- updateUnmutedVolumeIcon(device);
- mStartFromMute = false;
- }
- int seekBarVolume = MediaOutputSeekbar.scaleProgressToVolume(progress);
- if (seekBarVolume != device.getCurrentVolume()) {
- mLatestUpdateVolume = seekBarVolume;
- mController.adjustVolume(device, seekBarVolume);
- }
+ mSeekBar.setOnSeekBarChangeListener(new MediaSeekBarChangedListener(
+ device, volumeControl) {
+ @Override
+ public void onStopTrackingTouch(SeekBar seekbar) {
+ super.onStopTrackingTouch(seekbar);
+ mController.logInteractionAdjustVolume(device);
}
+ });
+ }
+ // Initializes the seekbar for a group of devices.
+ void initGroupSeekbar(boolean isCurrentSeekbarInvisible) {
+ SeekBarVolumeControl volumeControl = new SeekBarVolumeControl() {
@Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- mTitleIcon.setVisibility(View.INVISIBLE);
- mVolumeValueText.setVisibility(View.VISIBLE);
- int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
- seekBar.getProgress());
- mStartFromMute = (currentVolume == 0);
- mIsDragging = true;
+ public int getVolume() {
+ return mController.getSessionVolume();
}
@Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
- seekBar.getProgress());
- if (currentVolume == 0) {
- seekBar.setProgress(0);
- updateMutedVolumeIcon(device);
- } else {
- updateUnmutedVolumeIcon(device);
- }
- mTitleIcon.setVisibility(View.VISIBLE);
- mVolumeValueText.setVisibility(View.GONE);
- mController.logInteractionAdjustVolume(device);
- mIsDragging = false;
+ public void setVolume(int volume) {
+ mController.adjustSessionVolume(volume);
+ }
+
+ @Override
+ public void onMute() {}
+ };
+
+ if (!mController.isVolumeControlEnabledForSession()) {
+ disableSeekBar();
+ } else {
+ enableSeekBar(volumeControl);
+ }
+ mSeekBar.setMaxVolume(mController.getSessionVolumeMax());
+
+ final int currentVolume = mController.getSessionVolume();
+ initializeSeekbarVolume(null, currentVolume, isCurrentSeekbarInvisible);
+ mSeekBar.setOnSeekBarChangeListener(new MediaSeekBarChangedListener(
+ null, volumeControl) {
+ @Override
+ protected boolean shouldHandleProgressChanged() {
+ return true;
}
});
}
@@ -385,7 +405,7 @@ public abstract class MediaOutputBaseAdapter extends
int getDrawableId(boolean isInputDevice, boolean isMutedVolumeIcon) {
// Returns the microphone icon when the flag is enabled and the device is an input
// device.
- if (com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl()
+ if (Flags.enableAudioInputDeviceRoutingAndVolumeControl()
&& isInputDevice) {
return isMutedVolumeIcon ? R.drawable.ic_mic_off : R.drawable.ic_mic_26dp;
}
@@ -452,27 +472,28 @@ public abstract class MediaOutputBaseAdapter extends
updateIconAreaClickListener(null);
}
- private void enableSeekBar(MediaDevice device) {
+ private void enableSeekBar(SeekBarVolumeControl volumeControl) {
mSeekBar.setEnabled(true);
+
mSeekBar.setOnTouchListener((v, event) -> false);
updateIconAreaClickListener((v) -> {
- if (device.getCurrentVolume() == 0) {
- mController.logInteractionUnmuteDevice(device);
+ if (volumeControl.getVolume() == 0) {
mSeekBar.setVolume(UNMUTE_DEFAULT_VOLUME);
- mController.adjustVolume(device, UNMUTE_DEFAULT_VOLUME);
- updateUnmutedVolumeIcon(device);
+ volumeControl.setVolume(UNMUTE_DEFAULT_VOLUME);
+ updateUnmutedVolumeIcon(null);
mIconAreaLayout.setOnTouchListener(((iconV, event) -> false));
} else {
- mController.logInteractionMuteDevice(device);
+ volumeControl.onMute();
mSeekBar.resetVolume();
- mController.adjustVolume(device, 0);
- updateMutedVolumeIcon(device);
+ volumeControl.setVolume(0);
+ updateMutedVolumeIcon(null);
mIconAreaLayout.setOnTouchListener(((iconV, event) -> {
mSeekBar.dispatchTouchEvent(event);
return false;
}));
}
});
+
}
protected void setUpDeviceIcon(MediaDevice device) {
@@ -488,5 +509,74 @@ public abstract class MediaOutputBaseAdapter extends
});
});
}
+
+ interface SeekBarVolumeControl {
+ int getVolume();
+ void setVolume(int volume);
+ void onMute();
+ }
+
+ private abstract class MediaSeekBarChangedListener
+ implements SeekBar.OnSeekBarChangeListener {
+ boolean mStartFromMute = false;
+ private MediaDevice mMediaDevice;
+ private SeekBarVolumeControl mVolumeControl;
+
+ MediaSeekBarChangedListener(MediaDevice device, SeekBarVolumeControl volumeControl) {
+ mMediaDevice = device;
+ mVolumeControl = volumeControl;
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (!shouldHandleProgressChanged() || !fromUser) {
+ return;
+ }
+
+ final String percentageString = mContext.getResources().getString(
+ R.string.media_output_dialog_volume_percentage,
+ mSeekBar.getPercentage());
+ mVolumeValueText.setText(percentageString);
+
+ if (mStartFromMute) {
+ updateUnmutedVolumeIcon(mMediaDevice);
+ mStartFromMute = false;
+ }
+
+ int seekBarVolume = MediaOutputSeekbar.scaleProgressToVolume(progress);
+ if (seekBarVolume != mVolumeControl.getVolume()) {
+ mLatestUpdateVolume = seekBarVolume;
+ mVolumeControl.setVolume(seekBarVolume);
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ mTitleIcon.setVisibility(View.INVISIBLE);
+ mVolumeValueText.setVisibility(View.VISIBLE);
+ int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
+ seekBar.getProgress());
+ mStartFromMute = (currentVolume == 0);
+ mIsDragging = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
+ seekBar.getProgress());
+ if (currentVolume == 0) {
+ seekBar.setProgress(0);
+ updateMutedVolumeIcon(mMediaDevice);
+ } else {
+ updateUnmutedVolumeIcon(mMediaDevice);
+ }
+ mTitleIcon.setVisibility(View.VISIBLE);
+ mVolumeValueText.setVisibility(View.GONE);
+ mIsDragging = false;
+ }
+ protected boolean shouldHandleProgressChanged() {
+ return mMediaDevice != null;
+ }
+ };
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
index 15afd22a27d8..35c872f8a203 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -760,14 +760,26 @@ public class MediaSwitchingController
if (connectedMediaDevice != null) {
selectedDevicesIds.add(connectedMediaDevice.getId());
}
+ boolean groupSelectedDevices =
+ com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping();
+ int nextSelectedItemIndex = 0;
boolean suggestedDeviceAdded = false;
boolean displayGroupAdded = false;
+ boolean selectedDeviceAdded = false;
for (MediaDevice device : devices) {
if (needToHandleMutingExpectedDevice && device.isMutingExpectedDevice()) {
finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device));
+ nextSelectedItemIndex++;
} else if (!needToHandleMutingExpectedDevice && selectedDevicesIds.contains(
device.getId())) {
- finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device));
+ if (groupSelectedDevices) {
+ finalMediaItems.add(
+ nextSelectedItemIndex++,
+ MediaItem.createDeviceMediaItem(device, !selectedDeviceAdded));
+ selectedDeviceAdded = true;
+ } else {
+ finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device));
+ }
} else {
if (device.isSuggestedDevice() && !suggestedDeviceAdded) {
addSuggestedDeviceGroupDivider(finalMediaItems);
@@ -1331,6 +1343,10 @@ public class MediaSwitchingController
return !device.isVolumeFixed();
}
+ boolean isVolumeControlEnabledForSession() {
+ return mLocalMediaManager.isMediaSessionAvailableForVolumeControl();
+ }
+
private void startActivity(Intent intent, ActivityTransitionAnimator.Controller controller) {
// Media Output dialog can be shown from the volume panel. This makes sure the panel is
// closed when navigating to another activity, so it doesn't stays on top of it
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
index ea0f63ca9721..d503fb7f94f7 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
@@ -18,6 +18,7 @@ package com.android.systemui.mediaprojection.data.repository
import android.app.ActivityManager.RunningTaskInfo
import android.hardware.display.DisplayManager
+import android.media.projection.MediaProjectionEvent
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
import android.media.projection.StopReason
@@ -43,6 +44,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -83,48 +87,59 @@ constructor(
}
}
- override val mediaProjectionState: Flow<MediaProjectionState> =
- conflatedCallbackFlow {
- val callback =
- object : MediaProjectionManager.Callback() {
- override fun onStart(info: MediaProjectionInfo?) {
- logger.log(
- TAG,
- LogLevel.DEBUG,
- {},
- { "MediaProjectionManager.Callback#onStart" },
- )
- trySendWithFailureLogging(CallbackEvent.OnStart(info), TAG)
- }
+ private val callbackEventsFlow = conflatedCallbackFlow {
+ val callback =
+ object : MediaProjectionManager.Callback() {
+ override fun onStart(info: MediaProjectionInfo?) {
+ logger.log(TAG, LogLevel.DEBUG, {}, { "Callback#onStart" })
+ trySendWithFailureLogging(CallbackEvent.OnStart(info), TAG)
+ }
- override fun onStop(info: MediaProjectionInfo?) {
- logger.log(
- TAG,
- LogLevel.DEBUG,
- {},
- { "MediaProjectionManager.Callback#onStop" },
- )
- trySendWithFailureLogging(CallbackEvent.OnStop, TAG)
- }
+ override fun onStop(info: MediaProjectionInfo?) {
+ logger.log(TAG, LogLevel.DEBUG, {}, { "Callback#onStop" })
+ trySendWithFailureLogging(CallbackEvent.OnStop, TAG)
+ }
- override fun onRecordingSessionSet(
- info: MediaProjectionInfo,
- session: ContentRecordingSession?,
- ) {
- logger.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = session.toString() },
- { "MediaProjectionManager.Callback#onSessionStarted: $str1" },
- )
- trySendWithFailureLogging(
- CallbackEvent.OnRecordingSessionSet(info, session),
- TAG,
- )
- }
+ override fun onRecordingSessionSet(
+ info: MediaProjectionInfo,
+ session: ContentRecordingSession?,
+ ) {
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = session.toString() },
+ { "Callback#onSessionSet: $str1" },
+ )
+ trySendWithFailureLogging(
+ CallbackEvent.OnRecordingSessionSet(info, session),
+ TAG,
+ )
+ }
+
+ override fun onMediaProjectionEvent(
+ event: MediaProjectionEvent,
+ info: MediaProjectionInfo?,
+ session: ContentRecordingSession?,
+ ) {
+ if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = event.toString() },
+ { "Callback#onMediaProjectionEvent : $str1" },
+ )
+ trySendWithFailureLogging(CallbackEvent.OnMediaProjectionEvent(event), TAG)
}
- mediaProjectionManager.addCallback(callback, handler)
- awaitClose { mediaProjectionManager.removeCallback(callback) }
+ }
+ }
+ mediaProjectionManager.addCallback(callback, handler)
+ awaitClose { mediaProjectionManager.removeCallback(callback) }
+ }
+
+ override val mediaProjectionState: Flow<MediaProjectionState> =
+ callbackEventsFlow
+ .filterNot {
+ it is CallbackEvent.OnMediaProjectionEvent // Exclude OnMediaProjectionEvent
}
// When we get an #onRecordingSessionSet event, we need to do some work in the
// background before emitting the right state value. But when we get an #onStop
@@ -159,6 +174,11 @@ constructor(
}
is CallbackEvent.OnStop -> MediaProjectionState.NotProjecting
is CallbackEvent.OnRecordingSessionSet -> stateForSession(it.info, it.session)
+ is CallbackEvent.OnMediaProjectionEvent ->
+ throw IllegalStateException(
+ "Unexpected OnMediaProjectionEvent in mediaProjectionState flow. It " +
+ "should have been filtered out."
+ )
}
}
.stateIn(
@@ -167,6 +187,16 @@ constructor(
initialValue = MediaProjectionState.NotProjecting,
)
+ override val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit> =
+ callbackEventsFlow
+ .filter {
+ com.android.media.projection.flags.Flags.showStopDialogPostCallEnd() &&
+ it is CallbackEvent.OnMediaProjectionEvent &&
+ it.event.eventType ==
+ MediaProjectionEvent.PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL
+ }
+ .map {}
+
private suspend fun stateForSession(
info: MediaProjectionInfo,
session: ContentRecordingSession?,
@@ -206,6 +236,8 @@ constructor(
val info: MediaProjectionInfo,
val session: ContentRecordingSession?,
) : CallbackEvent
+
+ data class OnMediaProjectionEvent(val event: MediaProjectionEvent) : CallbackEvent
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
index a01d8c2c98de..826ee589e8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
@@ -32,4 +32,10 @@ interface MediaProjectionRepository {
/** Represents the current [MediaProjectionState]. */
val mediaProjectionState: Flow<MediaProjectionState>
+
+ /**
+ * Emits each time a call ends but media projection is still active and media projection was
+ * starting during the call.
+ */
+ val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit>
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index ebda3765cf90..babb64050ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -284,7 +284,10 @@ public class NavigationBarControllerImpl implements
}
@Override
- public void onDisplayReady(int displayId) {
+ public void onDisplayAddSystemDecorations(int displayId) {
+ if (enableDisplayContentModeManagement()) {
+ mHasNavBar.put(displayId, true);
+ }
Display display = mDisplayManager.getDisplay(displayId);
mIsLargeScreen = isLargeScreen(mContext);
createNavigationBar(display, null /* savedState */, null /* result */);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 9d8943052b38..c4d847f18269 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -238,16 +238,16 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
}
@Override
- public void onDisplayReady(int displayId) {
- CommandQueue.Callbacks.super.onDisplayReady(displayId);
+ public void onDisplayAddSystemDecorations(int displayId) {
+ CommandQueue.Callbacks.super.onDisplayAddSystemDecorations(displayId);
if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mLauncherProxyService.getProxy().onDisplayReady(displayId);
+ mLauncherProxyService.getProxy().onDisplayAddSystemDecorations(displayId);
} catch (RemoteException e) {
- Log.e(TAG, "onDisplayReady() failed", e);
+ Log.e(TAG, "onDisplayAddSystemDecorations() failed", e);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index f15a7b30dce7..f8d442de0f55 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -22,6 +22,8 @@ import com.android.systemui.camera.CameraGestureHelper
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorActual
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.PowerRepository
import com.android.systemui.power.shared.model.DozeScreenStateModel
@@ -35,6 +37,7 @@ import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -228,6 +231,15 @@ constructor(
repository.updateWakefulness(powerButtonLaunchGestureTriggered = true)
}
+ suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+ detailedWakefulness
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ initialValue = detailedWakefulness.value,
+ )
+ .collect()
+ }
+
companion object {
private const val FSI_WAKE_WHY = "full_screen_intent"
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
index 0f49c94c3195..297c6af5a4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
@@ -1,6 +1,8 @@
package com.android.systemui.power.shared.model
import com.android.systemui.keyguard.KeyguardService
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
/**
* Models whether the device is awake or asleep, along with information about why we're in that
@@ -35,7 +37,7 @@ data class WakefulnessModel(
* to a subsequent power gesture.
*/
val powerButtonLaunchGestureTriggered: Boolean = false,
-) {
+) : Diffable<WakefulnessModel> {
fun isAwake() =
internalWakefulnessState == WakefulnessState.AWAKE ||
internalWakefulnessState == WakefulnessState.STARTING_TO_WAKE
@@ -58,4 +60,8 @@ data class WakefulnessModel(
return isAwake() &&
(lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
}
+
+ override fun logDiffs(prevVal: WakefulnessModel, row: TableRowLogger) {
+ row.logChange(columnName = "wakefulness", value = toString())
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 07de4662e82f..85b677b65aeb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -32,11 +32,7 @@ import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.annotation.VisibleForTesting
-import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.togetherWith
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Box
@@ -120,6 +116,7 @@ import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams
import com.android.systemui.qs.composefragment.ui.notificationScrimClip
import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings
+import com.android.systemui.qs.composefragment.ui.toEditMode
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -144,6 +141,7 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -273,36 +271,7 @@ constructor(
// by the composables.
.gesturesDisabled(viewModel.showingMirror)
) {
- val isEditing by
- viewModel.containerViewModel.editModeViewModel.isEditing
- .collectAsStateWithLifecycle()
- val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
- AnimatedContent(
- targetState = isEditing,
- transitionSpec = {
- fadeIn(animationSpecEditMode) togetherWith
- fadeOut(animationSpecEditMode)
- },
- label = "EditModeAnimatedContent",
- ) { editing ->
- if (editing) {
- val qqsPadding = viewModel.qqsHeaderHeight
- EditMode(
- viewModel = viewModel.containerViewModel.editModeViewModel,
- modifier =
- Modifier.fillMaxWidth()
- .padding(top = { qqsPadding })
- .padding(
- horizontal = {
- QuickSettingsShade.Dimensions.Padding
- .roundToPx()
- }
- ),
- )
- } else {
- CollapsableQuickSettingsSTL()
- }
- }
+ CollapsableQuickSettingsSTL()
}
}
}
@@ -324,12 +293,17 @@ constructor(
from(QuickQuickSettings, QuickSettings) {
quickQuickSettingsToQuickSettings(viewModel::animateTilesExpansion::get)
}
+ to(SceneKeys.EditMode) {
+ spec = tween(durationMillis = EDIT_MODE_TIME_MILLIS)
+ toEditMode()
+ }
},
)
LaunchedEffect(Unit) {
synchronizeQsState(
sceneState,
+ viewModel.containerViewModel.editModeViewModel.isEditing,
snapshotFlow { viewModel.expansionState }.map { it.progress },
)
}
@@ -337,12 +311,20 @@ constructor(
SceneTransitionLayout(state = sceneState, modifier = Modifier.fillMaxSize()) {
scene(QuickSettings) {
LaunchedEffect(Unit) { viewModel.onQSOpen() }
- QuickSettingsElement()
+ QuickSettingsElement(Modifier.element(QuickSettings.rootElementKey))
}
scene(QuickQuickSettings) {
LaunchedEffect(Unit) { viewModel.onQQSOpen() }
- QuickQuickSettingsElement()
+ // Cannot pass the element modifier in because the top element has a `testTag`
+ // and this would overwrite it.
+ Box(Modifier.element(QuickQuickSettings.rootElementKey)) {
+ QuickQuickSettingsElement()
+ }
+ }
+
+ scene(SceneKeys.EditMode) {
+ EditModeElement(Modifier.element(SceneKeys.EditMode.rootElementKey))
}
}
}
@@ -582,7 +564,7 @@ constructor(
}
@Composable
- private fun ContentScope.QuickQuickSettingsElement() {
+ private fun ContentScope.QuickQuickSettingsElement(modifier: Modifier = Modifier) {
val qqsPadding = viewModel.qqsHeaderHeight
val bottomPadding = viewModel.qqsBottomPadding
DisposableEffect(Unit) {
@@ -595,7 +577,7 @@ constructor(
.squishiness
.collectAsStateWithLifecycle()
- Column(modifier = Modifier.sysuiResTag(ResIdTags.quickQsPanel)) {
+ Column(modifier = modifier.sysuiResTag(ResIdTags.quickQsPanel)) {
Box(
modifier =
Modifier.fillMaxWidth()
@@ -666,12 +648,12 @@ constructor(
}
@Composable
- private fun ContentScope.QuickSettingsElement() {
+ private fun ContentScope.QuickSettingsElement(modifier: Modifier = Modifier) {
val qqsPadding = viewModel.qqsHeaderHeight
val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top)
Column(
modifier =
- Modifier.collapseExpandSemanticAction(
+ modifier.collapseExpandSemanticAction(
stringResource(id = R.string.accessibility_quick_settings_collapse)
)
) {
@@ -776,6 +758,18 @@ constructor(
}
}
+ @Composable
+ private fun EditModeElement(modifier: Modifier = Modifier) {
+ // No need for top padding, the Scaffold inside takes care of the correct insets
+ EditMode(
+ viewModel = viewModel.containerViewModel.editModeViewModel,
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .padding(horizontal = { QuickSettingsShade.Dimensions.Padding.roundToPx() }),
+ )
+ }
+
private fun Modifier.collapseExpandSemanticAction(label: String): Modifier {
return viewModel.collapseExpandAccessibilityAction?.let {
semantics {
@@ -863,6 +857,7 @@ private val instanceProvider =
object SceneKeys {
val QuickQuickSettings = SceneKey("QuickQuickSettingsScene")
val QuickSettings = SceneKey("QuickSettingsScene")
+ val EditMode = SceneKey("EditModeScene")
fun QSFragmentComposeViewModel.QSExpansionState.toIdleSceneKey(): SceneKey {
return when {
@@ -880,7 +875,11 @@ object SceneKeys {
}
}
-suspend fun synchronizeQsState(state: MutableSceneTransitionLayoutState, expansion: Flow<Float>) {
+private suspend fun synchronizeQsState(
+ state: MutableSceneTransitionLayoutState,
+ editMode: Flow<Boolean>,
+ expansion: Flow<Float>,
+) {
coroutineScope {
val animationScope = this
@@ -891,23 +890,30 @@ suspend fun synchronizeQsState(state: MutableSceneTransitionLayoutState, expansi
currentTransition = null
}
- expansion.collectLatest { progress ->
- when (progress) {
- 0f -> snapTo(QuickQuickSettings)
- 1f -> snapTo(QuickSettings)
- else -> {
- val transition = currentTransition
- if (transition != null) {
- transition.progress = progress
- return@collectLatest
- }
+ editMode.combine(expansion, ::Pair).collectLatest { (editMode, progress) ->
+ if (editMode && state.currentScene != SceneKeys.EditMode) {
+ state.setTargetScene(SceneKeys.EditMode, animationScope)?.second?.join()
+ } else if (!editMode && state.currentScene == SceneKeys.EditMode) {
+ state.setTargetScene(SceneKeys.QuickSettings, animationScope)?.second?.join()
+ }
+ if (!editMode) {
+ when (progress) {
+ 0f -> snapTo(QuickQuickSettings)
+ 1f -> snapTo(QuickSettings)
+ else -> {
+ val transition = currentTransition
+ if (transition != null) {
+ transition.progress = progress
+ return@collectLatest
+ }
- val newTransition =
- ExpansionTransition(progress).also { currentTransition = it }
- state.startTransitionImmediately(
- animationScope = animationScope,
- transition = newTransition,
- )
+ val newTransition =
+ ExpansionTransition(progress).also { currentTransition = it }
+ state.startTransitionImmediately(
+ animationScope = animationScope,
+ transition = newTransition,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/ToEditMode.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/ToEditMode.kt
new file mode 100644
index 000000000000..0c6f3ee88312
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/ToEditMode.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 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.composefragment.ui
+
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.qs.composefragment.SceneKeys
+
+fun TransitionBuilder.toEditMode() {
+ fractionRange(start = 0.5f) { fade(SceneKeys.EditMode.rootElementKey) }
+ fractionRange(end = 0.5f) {
+ fade(SceneKeys.QuickQuickSettings.rootElementKey)
+ fade(SceneKeys.QuickSettings.rootElementKey)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt
index be792df340c9..f2f237ac987e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt
@@ -16,13 +16,27 @@
package com.android.systemui.scene.domain
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.scene.domain.resolver.SceneResolverModule
import dagger.Module
+import dagger.Provides
+import javax.inject.Qualifier
-@Module(
- includes =
- [
- SceneResolverModule::class,
- ]
-)
-object SceneDomainModule
+@Module(includes = [SceneResolverModule::class])
+object SceneDomainModule {
+
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ @SceneFrameworkTableLog
+ fun provideSceneFrameworkTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("SceneFrameworkTableLog", 100)
+ }
+}
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class SceneFrameworkTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
index bebd398ac972..c9d8e0244d20 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
@@ -18,11 +18,16 @@ package com.android.systemui.scene.domain.interactor
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.scene.data.model.SceneStack
+import com.android.systemui.scene.data.model.asIterable
import com.android.systemui.scene.data.model.peek
import com.android.systemui.scene.data.model.pop
import com.android.systemui.scene.data.model.push
import com.android.systemui.scene.data.model.sceneStackOf
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.SceneContainerConfig
import javax.inject.Inject
@@ -39,6 +44,7 @@ class SceneBackInteractor
constructor(
private val logger: SceneLogger,
private val sceneContainerConfig: SceneContainerConfig,
+ @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
) {
private val _backStack = MutableStateFlow(sceneStackOf())
val backStack: StateFlow<SceneStack> = _backStack.asStateFlow()
@@ -58,6 +64,7 @@ constructor(
fun onSceneChange(from: SceneKey, to: SceneKey) {
check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" }
+ val prevVal = backStack.value
_backStack.update { stack ->
when (stackOperation(from, to, stack)) {
null -> stack
@@ -68,12 +75,21 @@ constructor(
}
}
logger.logSceneBackStack(backStack.value)
+ tableLogBuffer.logDiffs(
+ prevVal = DiffableSceneStack(prevVal),
+ newVal = DiffableSceneStack(backStack.value),
+ )
}
/** Applies the given [transform] to the back stack. */
fun updateBackStack(transform: (SceneStack) -> SceneStack) {
+ val prevVal = backStack.value
_backStack.update { stack -> transform(stack) }
logger.logSceneBackStack(backStack.value)
+ tableLogBuffer.logDiffs(
+ prevVal = DiffableSceneStack(prevVal),
+ newVal = DiffableSceneStack(backStack.value),
+ )
}
private fun stackOperation(from: SceneKey, to: SceneKey, stack: SceneStack): StackOperation? {
@@ -106,4 +122,15 @@ constructor(
private data object Push : StackOperation
private data object Pop : StackOperation
+
+ private class DiffableSceneStack(private val sceneStack: SceneStack) :
+ Diffable<DiffableSceneStack> {
+
+ override fun logDiffs(prevVal: DiffableSceneStack, row: TableRowLogger) {
+ row.logChange(
+ columnName = "backStack",
+ value = sceneStack.asIterable().joinToString { it.debugName },
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 8bc9d96c064a..9c04323f2a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -27,14 +27,19 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.domain.resolver.SceneResolver
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.pairwise
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -47,6 +52,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
/**
* Generic business logic and app state accessors for the scene framework.
@@ -562,6 +568,28 @@ constructor(
decrementActiveTransitionAnimationCount()
}
+ suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+ coroutineScope {
+ launch {
+ currentScene
+ .map { sceneKey -> DiffableSceneKey(key = sceneKey) }
+ .pairwise()
+ .collect { (prev, current) ->
+ tableLogBuffer.logDiffs(prevVal = prev, newVal = current)
+ }
+ }
+
+ launch {
+ currentOverlays
+ .map { overlayKeys -> DiffableOverlayKeys(keys = overlayKeys) }
+ .pairwise()
+ .collect { (prev, current) ->
+ tableLogBuffer.logDiffs(prevVal = prev, newVal = current)
+ }
+ }
+ }
+ }
+
private fun decrementActiveTransitionAnimationCount() {
repository.activeTransitionAnimationCount.update { current ->
(current - 1).also {
@@ -573,4 +601,20 @@ constructor(
}
}
}
+
+ private class DiffableSceneKey(private val key: SceneKey) : Diffable<DiffableSceneKey> {
+ override fun logDiffs(prevVal: DiffableSceneKey, row: TableRowLogger) {
+ row.logChange(columnName = "currentScene", value = key.debugName)
+ }
+ }
+
+ private class DiffableOverlayKeys(private val keys: Set<OverlayKey>) :
+ Diffable<DiffableOverlayKeys> {
+ override fun logDiffs(prevVal: DiffableOverlayKeys, row: TableRowLogger) {
+ row.logChange(
+ columnName = "currentOverlays",
+ value = keys.joinToString { key -> key.debugName },
+ )
+ }
+ }
}
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 8602884ec4ee..2fd584176220 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
@@ -45,6 +45,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.keyguardScenes
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.model.SceneContainerPlugin
import com.android.systemui.model.SysUiState
import com.android.systemui.model.updateFlags
@@ -54,6 +55,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.scene.data.model.asIterable
import com.android.systemui.scene.data.model.sceneStackOf
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
import com.android.systemui.scene.domain.interactor.DisabledContentInteractor
import com.android.systemui.scene.domain.interactor.SceneBackInteractor
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
@@ -145,6 +147,7 @@ constructor(
private val disabledContentInteractor: DisabledContentInteractor,
private val activityTransitionAnimator: ActivityTransitionAnimator,
private val shadeModeInteractor: ShadeModeInteractor,
+ @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
) : CoreStartable {
private val centralSurfaces: CentralSurfaces?
get() = centralSurfacesOptLazy.get().getOrNull()
@@ -154,6 +157,7 @@ constructor(
override fun start() {
if (SceneContainerFlag.isEnabled) {
sceneLogger.logFrameworkEnabled(isEnabled = true)
+ applicationScope.launch { hydrateTableLogBuffer() }
hydrateVisibility()
automaticallySwitchScenes()
hydrateSystemUiState()
@@ -224,6 +228,16 @@ constructor(
}
}
+ private suspend fun hydrateTableLogBuffer() {
+ coroutineScope {
+ launch { sceneInteractor.hydrateTableLogBuffer(tableLogBuffer) }
+ launch { keyguardEnabledInteractor.hydrateTableLogBuffer(tableLogBuffer) }
+ launch { faceUnlockInteractor.hydrateTableLogBuffer(tableLogBuffer) }
+ launch { powerInteractor.hydrateTableLogBuffer(tableLogBuffer) }
+ launch { keyguardInteractor.hydrateTableLogBuffer(tableLogBuffer) }
+ }
+ }
+
private fun resetShadeSessions() {
applicationScope.launch {
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 6844f053cd21..eae0ba66925d 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -16,6 +16,8 @@
package com.android.systemui.settings.brightness;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.Intent.EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -157,6 +159,7 @@ public class BrightnessDialog extends Activity {
}
void setBrightnessDialogViewAttributes(View container) {
+ Configuration configuration = getResources().getConfiguration();
// The brightness mirror container is INVISIBLE by default.
container.setVisibility(View.VISIBLE);
ViewGroup.MarginLayoutParams lp =
@@ -171,9 +174,16 @@ public class BrightnessDialog extends Activity {
R.dimen.notification_guts_option_vertical_padding);
lp.topMargin = verticalMargin;
+ // If in multi-window or freeform, increase the top margin so the brightness dialog
+ // doesn't get cut off.
+ final int windowingMode = configuration.windowConfiguration.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_MULTI_WINDOW
+ || windowingMode == WINDOWING_MODE_FREEFORM) {
+ lp.topMargin += 50;
+ }
+
lp.bottomMargin = verticalMargin;
- Configuration configuration = getResources().getConfiguration();
int orientation = configuration.orientation;
int windowWidth = getWindowAvailableWidth();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
index 59d812403777..01451502b859 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
@@ -19,6 +19,9 @@ package com.android.systemui.shade.domain.interactor
import android.provider.Settings
import androidx.annotation.FloatRange
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.shared.model.ShadeMode
@@ -81,8 +84,9 @@ class ShadeModeInteractorImpl
@Inject
constructor(
@Application applicationScope: CoroutineScope,
- repository: ShadeRepository,
+ private val repository: ShadeRepository,
secureSettingsRepository: SecureSettingsRepository,
+ @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
) : ShadeModeInteractor {
private val isDualShadeEnabled: Flow<Boolean> =
@@ -93,17 +97,17 @@ constructor(
override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide
+ private val shadeModeInitialValue: ShadeMode
+ get() =
+ determineShadeMode(
+ isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT,
+ isShadeLayoutWide = repository.isShadeLayoutWide.value,
+ )
+
override val shadeMode: StateFlow<ShadeMode> =
combine(isDualShadeEnabled, repository.isShadeLayoutWide, ::determineShadeMode)
- .stateIn(
- applicationScope,
- SharingStarted.Eagerly,
- initialValue =
- determineShadeMode(
- isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT,
- isShadeLayoutWide = repository.isShadeLayoutWide.value,
- ),
- )
+ .logDiffsForTable(tableLogBuffer = tableLogBuffer, initialValue = shadeModeInitialValue)
+ .stateIn(applicationScope, SharingStarted.Eagerly, initialValue = shadeModeInitialValue)
@FloatRange(from = 0.0, to = 1.0) override fun getTopEdgeSplitFraction(): Float = 0.5f
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
index a8199a402ef1..8b3ce0f69742 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
@@ -16,15 +16,18 @@
package com.android.systemui.shade.shared.model
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
/** Enumerates all known modes of operation of the shade. */
-sealed interface ShadeMode {
+sealed class ShadeMode : Diffable<ShadeMode> {
/**
* The single or "accordion" shade where the QS and notification parts are in two vertically
* stacked panels and the user can swipe up and down to expand or collapse between the two
* parts.
*/
- data object Single : ShadeMode
+ data object Single : ShadeMode()
/**
* The split shade where, on large screens and unfolded foldables, the QS and notification parts
@@ -32,14 +35,18 @@ sealed interface ShadeMode {
*
* Note: This isn't the only mode where the shade is wide.
*/
- data object Split : ShadeMode
+ data object Split : ShadeMode()
/**
* The dual shade where the QS and notification parts each have their own independently
* expandable/collapsible panel on either side of the large screen / unfolded device or sharing
* a space on a small screen or folded device.
*/
- data object Dual : ShadeMode
+ data object Dual : ShadeMode()
+
+ override fun logDiffs(prevVal: ShadeMode, row: TableRowLogger) {
+ row.logChange("shadeMode", toString())
+ }
companion object {
@JvmStatic fun dual(): Dual = Dual
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index dcea8d85e10d..1720898229a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -111,7 +111,7 @@ public class CommandQueue extends IStatusBar.Stub implements
private static final int MSG_COLLAPSE_PANELS = 4 << MSG_SHIFT;
private static final int MSG_EXPAND_SETTINGS = 5 << MSG_SHIFT;
private static final int MSG_SYSTEM_BAR_CHANGED = 6 << MSG_SHIFT;
- private static final int MSG_DISPLAY_READY = 7 << MSG_SHIFT;
+ private static final int MSG_DISPLAY_ADD_SYSTEM_DECORATIONS = 7 << MSG_SHIFT;
private static final int MSG_SHOW_IME_BUTTON = 8 << MSG_SHIFT;
private static final int MSG_TOGGLE_RECENT_APPS = 9 << MSG_SHIFT;
private static final int MSG_PRELOAD_RECENT_APPS = 10 << MSG_SHIFT;
@@ -415,9 +415,9 @@ public class CommandQueue extends IStatusBar.Stub implements
}
/**
- * @see IStatusBar#onDisplayReady(int)
+ * @see IStatusBar#onDisplayAddSystemDecorations(int)
*/
- default void onDisplayReady(int displayId) {
+ default void onDisplayAddSystemDecorations(int displayId) {
}
/**
@@ -1205,9 +1205,9 @@ public class CommandQueue extends IStatusBar.Stub implements
}
@Override
- public void onDisplayReady(int displayId) {
+ public void onDisplayAddSystemDecorations(int displayId) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_DISPLAY_READY, displayId, 0).sendToTarget();
+ mHandler.obtainMessage(MSG_DISPLAY_ADD_SYSTEM_DECORATIONS, displayId, 0).sendToTarget();
}
}
@@ -1851,9 +1851,9 @@ public class CommandQueue extends IStatusBar.Stub implements
mCallbacks.get(i).showPinningEscapeToast();
}
break;
- case MSG_DISPLAY_READY:
+ case MSG_DISPLAY_ADD_SYSTEM_DECORATIONS:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onDisplayReady(msg.arg1);
+ mCallbacks.get(i).onDisplayAddSystemDecorations(msg.arg1);
}
break;
case MSG_DISPLAY_REMOVE_SYSTEM_DECORATIONS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index 49c44798d2ea..49d69f26b538 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -32,6 +32,7 @@ import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
@@ -53,6 +54,9 @@ constructor(
private val packageManager: PackageManager,
@StatusBarChipsLog private val logger: LogBuffer,
) {
+ val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit> =
+ mediaProjectionRepository.projectionStartedDuringCallAndActivePostCallEvent
+
val projection: StateFlow<ProjectionChipModel> =
mediaProjectionRepository.mediaProjectionState
.map { state ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt
new file mode 100644
index 000000000000..b37c76232f01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2025 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.chips.mediaprojection.domain.model
+
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Represents the visibility state of a media projection stop dialog. */
+sealed interface MediaProjectionStopDialogModel {
+ /** The dialog is hidden and not visible to the user. */
+ data object Hidden : MediaProjectionStopDialogModel
+
+ /** The dialog is shown to the user. */
+ data class Shown(
+ val dialogDelegate: SystemUIDialog.Delegate,
+ private val onDismissAction: () -> Unit,
+ ) : MediaProjectionStopDialogModel {
+ /**
+ * Creates and shows the dialog. Ensures that onDismissAction callback is invoked when the
+ * dialog is canceled or dismissed.
+ */
+ fun createAndShowDialog() {
+ val dialog = dialogDelegate.createDialog()
+ dialog.setOnCancelListener { onDismissAction.invoke() }
+ dialog.setOnDismissListener { onDismissAction.invoke() }
+ dialog.show()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 6654d4a8f104..7a46fff157cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
import android.content.Context
import androidx.annotation.DrawableRes
import com.android.internal.jank.Cuj
+import com.android.systemui.CoreStartable
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
@@ -31,6 +32,7 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndGenericShareToAppDialogDelegate
@@ -41,13 +43,18 @@ import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
+import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* View model for the share-to-app chip, shown when sharing your phone screen content to another app
@@ -64,7 +71,59 @@ constructor(
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
private val dialogTransitionAnimator: DialogTransitionAnimator,
@StatusBarChipsLog private val logger: LogBuffer,
-) : OngoingActivityChipViewModel {
+) : OngoingActivityChipViewModel, CoreStartable {
+
+ private val _stopDialogToShow: MutableStateFlow<MediaProjectionStopDialogModel> =
+ MutableStateFlow(MediaProjectionStopDialogModel.Hidden)
+
+ /**
+ * Represents the current state of the media projection stop dialog. Emits
+ * [MediaProjectionStopDialogModel.Shown] when the dialog should be displayed, and
+ * [MediaProjectionStopDialogModel.Hidden] when it is dismissed.
+ */
+ val stopDialogToShow: StateFlow<MediaProjectionStopDialogModel> =
+ _stopDialogToShow.asStateFlow()
+
+ /**
+ * Emits a [MediaProjectionStopDialogModel] based on the current projection state when a
+ * projectionStartedDuringCallAndActivePostCallEvent event is emitted. If projecting, determines
+ * the appropriate dialog type to show. Otherwise, emits a hidden dialog state.
+ */
+ private val stopDialogDueToCallEndedState: StateFlow<MediaProjectionStopDialogModel> =
+ mediaProjectionChipInteractor.projectionStartedDuringCallAndActivePostCallEvent
+ .sample(mediaProjectionChipInteractor.projection) { _, currentProjection ->
+ when (currentProjection) {
+ is ProjectionChipModel.NotProjecting -> MediaProjectionStopDialogModel.Hidden
+ is ProjectionChipModel.Projecting -> {
+ when (currentProjection.receiver) {
+ ProjectionChipModel.Receiver.ShareToApp -> {
+ when (currentProjection.contentType) {
+ ProjectionChipModel.ContentType.Screen ->
+ createShareScreenToAppStopDialog(currentProjection)
+ ProjectionChipModel.ContentType.Audio ->
+ createGenericShareScreenToAppStopDialog()
+ }
+ }
+ ProjectionChipModel.Receiver.CastToOtherDevice ->
+ MediaProjectionStopDialogModel.Hidden
+ }
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), MediaProjectionStopDialogModel.Hidden)
+
+ /**
+ * Initializes background flow collector during SysUI startup for events determining the
+ * visibility of media projection stop dialogs.
+ */
+ override fun start() {
+ if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ scope.launch {
+ stopDialogDueToCallEndedState.collect { event -> _stopDialogToShow.value = event }
+ }
+ }
+ }
+
private val internalChip =
mediaProjectionChipInteractor.projection
.map { projectionModel ->
@@ -92,7 +151,25 @@ constructor(
private val chipTransitionHelper = ChipTransitionHelper(scope)
override val chip: StateFlow<OngoingActivityChipModel> =
- chipTransitionHelper.createChipFlow(internalChip)
+ combine(chipTransitionHelper.createChipFlow(internalChip), stopDialogToShow) {
+ currentChip,
+ stopDialog ->
+ if (
+ com.android.media.projection.flags.Flags.showStopDialogPostCallEnd() &&
+ stopDialog is MediaProjectionStopDialogModel.Shown
+ ) {
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ {},
+ { "Hiding the chip as stop dialog is being shown" },
+ )
+ OngoingActivityChipModel.Hidden()
+ } else {
+ currentChip
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())
/**
* Notifies this class that the user just stopped a screen recording from the dialog that's
@@ -108,6 +185,12 @@ constructor(
chipTransitionHelper.onActivityStoppedFromDialog()
}
+ /** Called when the stop dialog is dismissed or cancelled. */
+ private fun onStopDialogDismissed() {
+ logger.log(TAG, LogLevel.INFO, {}, { "The media projection stop dialog was dismissed" })
+ _stopDialogToShow.value = MediaProjectionStopDialogModel.Hidden
+ }
+
/** Stops the currently active projection. */
private fun stopProjectingFromDialog() {
logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested from dialog" })
@@ -115,6 +198,24 @@ constructor(
mediaProjectionChipInteractor.stopProjecting()
}
+ private fun createShareScreenToAppStopDialog(
+ projectionModel: ProjectionChipModel.Projecting
+ ): MediaProjectionStopDialogModel {
+ val dialogDelegate = createShareScreenToAppDialogDelegate(projectionModel)
+ return MediaProjectionStopDialogModel.Shown(
+ dialogDelegate,
+ onDismissAction = ::onStopDialogDismissed,
+ )
+ }
+
+ private fun createGenericShareScreenToAppStopDialog(): MediaProjectionStopDialogModel {
+ val dialogDelegate = createGenericShareToAppDialogDelegate()
+ return MediaProjectionStopDialogModel.Shown(
+ dialogDelegate,
+ onDismissAction = ::onStopDialogDismissed,
+ )
+ }
+
private fun createShareScreenToAppChip(
state: ProjectionChipModel.Projecting
): OngoingActivityChipModel.Shown {
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 351cdc8e7f36..b5a781ecdfb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -24,6 +24,7 @@ import com.android.systemui.SysUICutoutProviderImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.data.repository.LightBarControllerStore
@@ -140,6 +141,20 @@ interface StatusBarModule {
@Provides
@SysUISingleton
@IntoMap
+ @ClassKey(ShareToAppChipViewModel::class)
+ fun providesShareToAppChipViewModel(
+ shareToAppChipViewModelLazy: Lazy<ShareToAppChipViewModel>
+ ): CoreStartable {
+ return if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ shareToAppChipViewModelLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
@ClassKey(MultiDisplayStatusBarWindowControllerStore::class)
fun multiDisplayControllerStoreAsCoreStartable(
storeLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index 2d1eccdf1abd..a0a86710b4ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -22,6 +22,7 @@ import com.android.internal.widget.MessagingGroup
import com.android.internal.widget.MessagingMessage
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
@@ -144,7 +145,12 @@ internal constructor(
)
log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" }
traceSection("updateNotifOnUiModeChanged") {
- mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() }
+ mPipeline?.allNotifs?.forEach { entry ->
+ entry.row?.onUiModeChanged()
+ if (Flags.notificationUndoGutsOnConfigChanged()) {
+ mGutsManager.closeAndUndoGuts()
+ }
+ }
}
}
@@ -152,9 +158,15 @@ internal constructor(
colorUpdateLogger.logEvent("VCC.updateNotificationsOnDensityOrFontScaleChanged()")
mPipeline?.allNotifs?.forEach { entry ->
entry.onDensityOrFontScaleChanged()
- val exposedGuts = entry.areGutsExposed()
- if (exposedGuts) {
- mGutsManager.onDensityOrFontScaleChanged(entry)
+ if (Flags.notificationUndoGutsOnConfigChanged()) {
+ mGutsManager.closeAndUndoGuts()
+ } else {
+ // This property actually gets reset when the guts are re-inflated, so we're never
+ // actually calling onDensityOrFontScaleChanged below.
+ val exposedGuts = entry.areGutsExposed()
+ if (exposedGuts) {
+ mGutsManager.onDensityOrFontScaleChanged(entry)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index cb9bd4a3fd35..33c71d4a9c5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -27,8 +27,19 @@ import android.widget.DateTimeView
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isVisible
import com.android.app.tracing.traceSection
@@ -41,6 +52,8 @@ import com.android.internal.widget.NotificationProgressBar
import com.android.internal.widget.NotificationRowIconView
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R as systemuiR
+import com.android.systemui.statusbar.notification.promoted.AodPromotedNotificationColor.PrimaryText
+import com.android.systemui.statusbar.notification.promoted.AodPromotedNotificationColor.SecondaryText
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
@@ -59,29 +72,53 @@ fun AODPromotedNotification(viewModelFactory: AODPromotedNotificationViewModel.F
key(content.identity) {
val layoutResource = content.layoutResource ?: return
- AndroidView(
- factory = { context ->
+ val topPadding = dimensionResource(systemuiR.dimen.below_clock_padding_start_icons)
+ val sidePaddings = dimensionResource(systemuiR.dimen.notification_side_paddings)
+ val paddingValues =
+ PaddingValues(top = topPadding, start = sidePaddings, end = sidePaddings, bottom = 0.dp)
+
+ val borderStroke = BorderStroke(1.dp, SecondaryText.brush)
+
+ val borderRadius = dimensionResource(systemuiR.dimen.notification_corner_radius)
+ val borderShape = RoundedCornerShape(borderRadius)
+
+ Box(modifier = Modifier.padding(paddingValues)) {
+ AODPromotedNotificationView(
+ layoutResource = layoutResource,
+ content = content,
+ modifier = Modifier.border(borderStroke, borderShape),
+ )
+ }
+ }
+}
+
+@Composable
+fun AODPromotedNotificationView(
+ layoutResource: Int,
+ content: PromotedNotificationContentModel,
+ modifier: Modifier = Modifier,
+) {
+ AndroidView(
+ factory = { context ->
+ val view =
traceSection("$TAG.inflate") {
- LayoutInflater.from(context).inflate(layoutResource, /* root= */ null)
- }
- .apply {
- setTag(
- viewUpdaterTagId,
- traceSection("$TAG.findViews") {
- AODPromotedNotificationViewUpdater(this)
- },
- )
- }
- },
- update = { view ->
- traceSection("$TAG.update") {
- (view.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater).update(
- content
- )
+ LayoutInflater.from(context).inflate(layoutResource, /* root= */ null)
}
- },
- )
- }
+
+ val updater =
+ traceSection("$TAG.findViews") { AODPromotedNotificationViewUpdater(view) }
+
+ view.setTag(viewUpdaterTagId, updater)
+
+ view
+ },
+ update = { view ->
+ val updater = view.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater
+
+ traceSection("$TAG.update") { updater.update(content) }
+ },
+ modifier = modifier,
+ )
}
private val PromotedNotificationContentModel.layoutResource: Int?
@@ -262,12 +299,12 @@ private class AODPromotedNotificationViewUpdater(root: View) {
}
private fun updateTitle(titleView: TextView?, content: PromotedNotificationContentModel) {
- updateTextView(titleView, content.title, color = Color.PrimaryText)
+ updateTextView(titleView, content.title, color = PrimaryText)
}
private fun updateTimeAndChronometer(content: PromotedNotificationContentModel) {
- setTextViewColor(time, Color.SecondaryText)
- setTextViewColor(chronometer, Color.SecondaryText)
+ setTextViewColor(time, SecondaryText)
+ setTextViewColor(chronometer, SecondaryText)
val timeValue = content.time
@@ -309,7 +346,7 @@ private class AODPromotedNotificationViewUpdater(root: View) {
private fun updateTextView(
view: TextView?,
text: CharSequence?,
- color: Color = Color.SecondaryText,
+ color: AodPromotedNotificationColor = SecondaryText,
) {
setTextViewColor(view, color)
@@ -322,15 +359,19 @@ private class AODPromotedNotificationViewUpdater(root: View) {
}
}
- private fun setTextViewColor(view: TextView?, color: Color) {
- view?.setTextColor(color.color.toInt())
+ private fun setTextViewColor(view: TextView?, color: AodPromotedNotificationColor) {
+ view?.setTextColor(color.colorInt)
}
+}
- private enum class Color(val color: UInt) {
- Background(0x00000000u),
- PrimaryText(0xFFFFFFFFu),
- SecondaryText(0xFFCCCCCCu),
- }
+private enum class AodPromotedNotificationColor(colorUInt: UInt) {
+ Background(0x00000000u),
+ PrimaryText(0xFFFFFFFFu),
+ SecondaryText(0xFFCCCCCCu);
+
+ val colorInt = colorUInt.toInt()
+ val color = Color(colorInt)
+ val brush = SolidColor(color)
}
private val viewUpdaterTagId = systemuiR.id.aod_promoted_notification_view_updater_tag
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index b86d1d934269..75d1c7c3d51e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -287,7 +287,7 @@ public class NotificationGuts extends FrameLayout {
* @param save whether the state should be saved
* @param force whether the guts should be force-closed regardless of state.
*/
- private void closeControls(int x, int y, boolean save, boolean force) {
+ public void closeControls(int x, int y, boolean save, boolean force) {
// First try to dismiss any blocking helper.
if (getWindowToken() == null) {
if (mClosedListener != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index b1e5b22f9b1a..445cd010cd86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -48,6 +48,7 @@ import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.statusbar.IStatusBarService;
import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.CoreStartable;
+import com.android.systemui.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -223,6 +224,10 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
}
public void onDensityOrFontScaleChanged(NotificationEntry entry) {
+ if (!Flags.notificationUndoGutsOnConfigChanged()) {
+ Log.wtf(TAG, "onDensityOrFontScaleChanged should not be called if"
+ + " notificationUndoGutsOnConfigChanged is off");
+ }
setExposedGuts(entry.getGuts());
bindGuts(entry.getRow());
}
@@ -590,7 +595,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
}
/**
- * Closes guts or notification menus that might be visible and saves any changes.
+ * Closes guts or notification menus that might be visible and saves any changes if applicable
+ * (see {@link NotificationGuts.GutsContent#shouldBeSavedOnClose}).
*
* @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
* @param force true if guts should be closed regardless of state (used for snooze only).
@@ -611,6 +617,20 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
}
/**
+ * Closes all guts that might be visible without saving changes.
+ */
+ public void closeAndUndoGuts() {
+ if (mNotificationGutsExposed != null) {
+ mNotificationGutsExposed.removeCallbacks(mOpenRunnable);
+ mNotificationGutsExposed.closeControls(
+ /* x = */ -1,
+ /* y = */ -1,
+ /* save = */ false,
+ /* force = */ false);
+ }
+ }
+
+ /**
* Returns the exposed NotificationGuts or null if none are exposed.
*/
public NotificationGuts getExposedGuts() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 99a6f6a59bd0..83897f5bc3a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -51,6 +51,7 @@ import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Flags;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.res.R;
@@ -86,18 +87,26 @@ public class NotificationSnooze extends LinearLayout
private NotificationSwipeActionHelper mSnoozeListener;
private StatusBarNotification mSbn;
- private View mSnoozeView;
- private TextView mSelectedOptionText;
+ @VisibleForTesting
+ public View mSnoozeView;
+ @VisibleForTesting
+ public TextView mSelectedOptionText;
private TextView mUndoButton;
- private ImageView mExpandButton;
- private View mDivider;
- private ViewGroup mSnoozeOptionContainer;
- private List<SnoozeOption> mSnoozeOptions;
+ @VisibleForTesting
+ public ImageView mExpandButton;
+ @VisibleForTesting
+ public View mDivider;
+ @VisibleForTesting
+ public ViewGroup mSnoozeOptionContainer;
+ @VisibleForTesting
+ public List<SnoozeOption> mSnoozeOptions;
private int mCollapsedHeight;
private SnoozeOption mDefaultOption;
- private SnoozeOption mSelectedOption;
+ @VisibleForTesting
+ public SnoozeOption mSelectedOption;
private boolean mSnoozing;
- private boolean mExpanded;
+ @VisibleForTesting
+ public boolean mExpanded;
private AnimatorSet mExpandAnimation;
private KeyValueListParser mParser;
@@ -334,7 +343,8 @@ public class NotificationSnooze extends LinearLayout
}
}
- private void showSnoozeOptions(boolean show) {
+ @VisibleForTesting
+ public void showSnoozeOptions(boolean show) {
int drawableId = show ? com.android.internal.R.drawable.ic_collapse_notification
: com.android.internal.R.drawable.ic_expand_notification;
mExpandButton.setImageResource(drawableId);
@@ -381,7 +391,8 @@ public class NotificationSnooze extends LinearLayout
mExpandAnimation.start();
}
- private void setSelected(SnoozeOption option, boolean userAction) {
+ @VisibleForTesting
+ public void setSelected(SnoozeOption option, boolean userAction) {
if (option != mSelectedOption) {
mSelectedOption = option;
mSelectedOptionText.setText(option.getConfirmation());
@@ -466,7 +477,12 @@ public class NotificationSnooze extends LinearLayout
@Override
public boolean handleCloseControls(boolean save, boolean force) {
- if (mExpanded && !force) {
+ if (Flags.notificationUndoGutsOnConfigChanged() && !save) {
+ // Undo changes and let the guts handle closing the view
+ mSelectedOption = null;
+ showSnoozeOptions(false);
+ return false;
+ } else if (mExpanded && !force) {
// Collapse expanded state on outside touch
showSnoozeOptions(false);
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 7e76d77abe61..bd6906066ac8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -28,6 +28,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -115,6 +116,17 @@ constructor(
}
}
+ if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ launch {
+ viewModel.mediaProjectionStopDialogDueToCallEndedState.collect { stopDialog
+ ->
+ if (stopDialog is MediaProjectionStopDialogModel.Shown) {
+ stopDialog.createAndShowDialog()
+ }
+ }
+ }
+ }
+
if (!StatusBarNotifChips.isEnabled && !StatusBarChipsModernization.isEnabled) {
val primaryChipViewBinding =
OngoingActivityChipBinder.createBinding(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index a59d95f27c38..b116b47929d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -36,7 +36,9 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
@@ -96,6 +98,12 @@ interface HomeStatusBarViewModel {
val transitionFromLockscreenToDreamStartedEvent: Flow<Unit>
/**
+ * The current media projection stop dialog to be shown, or
+ * `MediaProjectionStopDialogModel.Hidden` if no dialog is visible.
+ */
+ val mediaProjectionStopDialogDueToCallEndedState: StateFlow<MediaProjectionStopDialogModel>
+
+ /**
* The ongoing activity chip that should be primarily shown on the left-hand side of the status
* bar. If there are multiple ongoing activity chips, this one should take priority.
*/
@@ -180,6 +188,7 @@ constructor(
sceneInteractor: SceneInteractor,
sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
shadeInteractor: ShadeInteractor,
+ shareToAppChipViewModel: ShareToAppChipViewModel,
ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel,
animations: SystemStatusEventAnimationInteractor,
@@ -206,6 +215,9 @@ constructor(
.filter { it.transitionState == TransitionState.STARTED }
.map {}
+ override val mediaProjectionStopDialogDueToCallEndedState =
+ shareToAppChipViewModel.stopDialogToShow
+
override val primaryOngoingActivityChip = ongoingActivityChipsViewModel.primaryChip
override val ongoingActivityChips = ongoingActivityChipsViewModel.chips
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 9795cda97f37..eecea9228ea3 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
@@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@@ -82,18 +83,29 @@ fun TutorialSelectionScreen(
}
),
) {
- val padding = if (hasCompactWindowSize()) 24.dp else 60.dp
+ val isCompactWindow = hasCompactWindowSize()
+ val padding = if (isCompactWindow) 24.dp else 60.dp
val configuration = LocalConfiguration.current
when (configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
- HorizontalSelectionButtons(
- onBackTutorialClicked = onBackTutorialClicked,
- onHomeTutorialClicked = onHomeTutorialClicked,
- onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
- onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
- modifier = Modifier.weight(1f).padding(padding),
- lastSelectedScreen,
- )
+ if (isCompactWindow)
+ HorizontalCompactSelectionButtons(
+ onBackTutorialClicked = onBackTutorialClicked,
+ onHomeTutorialClicked = onHomeTutorialClicked,
+ onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
+ onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
+ lastSelectedScreen,
+ modifier = Modifier.weight(1f).padding(padding),
+ )
+ else
+ HorizontalSelectionButtons(
+ onBackTutorialClicked = onBackTutorialClicked,
+ onHomeTutorialClicked = onHomeTutorialClicked,
+ onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
+ onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
+ lastSelectedScreen,
+ modifier = Modifier.weight(1f).padding(padding),
+ )
}
else -> {
VerticalSelectionButtons(
@@ -101,8 +113,8 @@ fun TutorialSelectionScreen(
onHomeTutorialClicked = onHomeTutorialClicked,
onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
- modifier = Modifier.weight(1f).padding(padding),
lastSelectedScreen,
+ modifier = Modifier.weight(1f).padding(padding),
)
}
}
@@ -120,11 +132,99 @@ private fun HorizontalSelectionButtons(
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
onSwitchAppsTutorialClicked: () -> Unit,
+ lastSelectedScreen: Screen,
modifier: Modifier = Modifier,
+) {
+ Column(modifier = modifier) {
+ TwoByTwoTutorialButtons(
+ onBackTutorialClicked,
+ onHomeTutorialClicked,
+ onRecentAppsTutorialClicked,
+ onSwitchAppsTutorialClicked,
+ lastSelectedScreen,
+ modifier = Modifier.weight(1f).fillMaxSize(),
+ )
+ }
+}
+
+@Composable
+private fun TwoByTwoTutorialButtons(
+ onBackTutorialClicked: () -> Unit,
+ onHomeTutorialClicked: () -> Unit,
+ onRecentAppsTutorialClicked: () -> Unit,
+ onSwitchAppsTutorialClicked: () -> Unit,
lastSelectedScreen: Screen,
+ modifier: Modifier = Modifier,
+) {
+ val homeFocusRequester = remember { FocusRequester() }
+ val backFocusRequester = remember { FocusRequester() }
+ val recentAppsFocusRequester = remember { FocusRequester() }
+ val switchAppsFocusRequester = remember { FocusRequester() }
+ LaunchedEffect(Unit) {
+ when (lastSelectedScreen) {
+ Screen.HOME_GESTURE -> homeFocusRequester.requestFocus()
+ Screen.BACK_GESTURE -> backFocusRequester.requestFocus()
+ Screen.RECENT_APPS_GESTURE -> recentAppsFocusRequester.requestFocus()
+ Screen.SWITCH_APPS_GESTURE -> switchAppsFocusRequester.requestFocus()
+ else -> {} // No-Op.
+ }
+ }
+ Column {
+ Row(Modifier.weight(1f)) {
+ TutorialButton(
+ text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
+ icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon),
+ iconColor = MaterialTheme.colorScheme.onPrimary,
+ onClick = onHomeTutorialClicked,
+ backgroundColor = MaterialTheme.colorScheme.primary,
+ modifier = modifier.focusRequester(homeFocusRequester).focusable().fillMaxSize(),
+ )
+ Spacer(modifier = Modifier.size(16.dp))
+ TutorialButton(
+ text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
+ icon = Icons.AutoMirrored.Outlined.ArrowBack,
+ iconColor = MaterialTheme.colorScheme.onTertiary,
+ onClick = onBackTutorialClicked,
+ backgroundColor = MaterialTheme.colorScheme.tertiary,
+ modifier = modifier.focusRequester(backFocusRequester).focusable().fillMaxSize(),
+ )
+ }
+ Spacer(modifier = Modifier.size(16.dp))
+ Row(Modifier.weight(1f)) {
+ TutorialButton(
+ text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button),
+ icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_recents_icon),
+ iconColor = MaterialTheme.colorScheme.onSecondary,
+ onClick = onRecentAppsTutorialClicked,
+ backgroundColor = MaterialTheme.colorScheme.secondary,
+ modifier =
+ modifier.focusRequester(recentAppsFocusRequester).focusable().fillMaxSize(),
+ )
+ Spacer(modifier = Modifier.size(16.dp))
+ TutorialButton(
+ text = stringResource(R.string.touchpad_tutorial_switch_apps_gesture_button),
+ icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_apps_icon),
+ iconColor = MaterialTheme.colorScheme.primary,
+ onClick = onSwitchAppsTutorialClicked,
+ backgroundColor = MaterialTheme.colorScheme.onPrimary,
+ modifier =
+ modifier.focusRequester(switchAppsFocusRequester).focusable().fillMaxSize(),
+ )
+ }
+ }
+}
+
+@Composable
+private fun HorizontalCompactSelectionButtons(
+ onBackTutorialClicked: () -> Unit,
+ onHomeTutorialClicked: () -> Unit,
+ onRecentAppsTutorialClicked: () -> Unit,
+ onSwitchAppsTutorialClicked: () -> Unit,
+ lastSelectedScreen: Screen,
+ modifier: Modifier = Modifier,
) {
Row(
- horizontalArrangement = Arrangement.spacedBy(20.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = modifier,
) {
@@ -133,8 +233,8 @@ private fun HorizontalSelectionButtons(
onHomeTutorialClicked,
onRecentAppsTutorialClicked,
onSwitchAppsTutorialClicked,
- modifier = Modifier.weight(1f).fillMaxSize(),
lastSelectedScreen,
+ modifier = Modifier.weight(1f).fillMaxSize(),
)
}
}
@@ -145,8 +245,8 @@ private fun VerticalSelectionButtons(
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
onSwitchAppsTutorialClicked: () -> Unit,
- modifier: Modifier = Modifier,
lastSelectedScreen: Screen,
+ modifier: Modifier = Modifier,
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
@@ -158,8 +258,8 @@ private fun VerticalSelectionButtons(
onHomeTutorialClicked,
onRecentAppsTutorialClicked,
onSwitchAppsTutorialClicked,
- modifier = Modifier.weight(1f).fillMaxSize(),
lastSelectedScreen,
+ modifier = Modifier.weight(1f).fillMaxSize(),
)
}
}
@@ -170,8 +270,8 @@ private fun FourTutorialButtons(
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
onSwitchAppsTutorialClicked: () -> Unit,
- modifier: Modifier = Modifier,
lastSelectedScreen: Screen,
+ modifier: Modifier = Modifier,
) {
val homeFocusRequester = remember { FocusRequester() }
val backFocusRequester = remember { FocusRequester() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index 86063acbf2e1..2715cb31ca8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -1512,6 +1512,60 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
}
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void selectedDevicesAddedInSameOrder() {
+ when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
+ doReturn(mMediaDevices)
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+ mMediaSwitchingController.getMediaItemList().clear();
+
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
+ assertThat(items.get(0).getMediaDevice().get()).isEqualTo(mMediaDevice1);
+ assertThat(items.get(1).getMediaDevice().get()).isEqualTo(mMediaDevice2);
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void selectedDevicesAddedInReverseOrder() {
+ when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
+ doReturn(mMediaDevices)
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+ mMediaSwitchingController.getMediaItemList().clear();
+
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
+ assertThat(items.get(0).getMediaDevice().get()).isEqualTo(mMediaDevice2);
+ assertThat(items.get(1).getMediaDevice().get()).isEqualTo(mMediaDevice1);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void firstSelectedDeviceIsFirstDeviceInGroupIsTrue() {
+ when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
+ doReturn(mMediaDevices)
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+ mMediaSwitchingController.getMediaItemList().clear();
+
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
+ assertThat(items.get(0).isFirstDeviceInGroup()).isTrue();
+ assertThat(items.get(1).isFirstDeviceInGroup()).isFalse();
+ }
+
private int getNumberOfConnectDeviceButtons() {
int numberOfConnectDeviceButtons = 0;
for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
index 2bd104dd375d..48b801cb06be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
@@ -20,6 +20,7 @@ import com.android.systemui.authentication.data.repository.authenticationReposit
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.user.domain.interactor.selectedUserInteractor
val Kosmos.authenticationInteractor by
@@ -29,5 +30,6 @@ val Kosmos.authenticationInteractor by
backgroundDispatcher = testDispatcher,
repository = authenticationRepository,
selectedUserInteractor = selectedUserInteractor,
+ tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt
new file mode 100644
index 000000000000..edfe8ecd0775
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.batteryRepository: BatteryRepository by Kosmos.Fixture { FakeBatteryRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt
new file mode 100644
index 000000000000..ac94335b42c3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeBatteryRepository : BatteryRepository {
+ private val _isDevicePluggedIn = MutableStateFlow(false)
+
+ override val isDevicePluggedIn: Flow<Boolean> = _isDevicePluggedIn.asStateFlow()
+
+ fun setDevicePluggedIn(isPluggedIn: Boolean) {
+ _isDevicePluggedIn.value = isPluggedIn
+ }
+}
+
+val BatteryRepository.fake
+ get() = this as FakeBatteryRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt
new file mode 100644
index 000000000000..2153955f3cc1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.domain.interactor
+
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.batteryInteractor by Kosmos.Fixture { BatteryInteractor(batteryRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 89aad4be7cc0..b0a6de1f931a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -19,10 +19,13 @@ package com.android.systemui.communal.domain.interactor
import android.content.testableContext
import android.os.userManager
import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.common.domain.interactor.batteryInteractor
import com.android.systemui.communal.data.repository.communalMediaRepository
import com.android.systemui.communal.data.repository.communalSmartspaceRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
+import com.android.systemui.communal.posturing.domain.interactor.posturingInteractor
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
+import com.android.systemui.dock.dockManager
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -64,6 +67,9 @@ val Kosmos.communalInteractor by Fixture {
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
managedProfileController = fakeManagedProfileController,
+ batteryInteractor = batteryInteractor,
+ dockManager = dockManager,
+ posturingInteractor = posturingInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 1d3fd300da06..c927b5563bba 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.keyguard.dismissCallbackRegistry
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -36,5 +37,6 @@ val Kosmos.deviceEntryInteractor by
alternateBouncerInteractor = alternateBouncerInteractor,
dismissCallbackRegistry = dismissCallbackRegistry,
sceneBackInteractor = sceneBackInteractor,
+ tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
)
}
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 e4c7df64fdc6..9e36428d119d 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
@@ -25,6 +25,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
@@ -40,6 +41,7 @@ val Kosmos.deviceUnlockedInteractor by Fixture {
systemPropertiesHelper = fakeSystemPropertiesHelper,
userAwareSecureSettingsRepository = userAwareSecureSettingsRepository,
keyguardInteractor = keyguardInteractor,
+ tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
)
.apply { activateIn(testScope) }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index b07de16be567..ff7a06c5087e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
@@ -41,5 +43,7 @@ var Kosmos.fromLockscreenTransitionInteractor by
communalSettingsInteractor = communalSettingsInteractor,
swipeToDismissInteractor = swipeToDismissInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ communalInteractor = communalInteractor,
+ communalSceneInteractor = communalSceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
index d5bdbdbaa90d..1b1fe593a2e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
@@ -20,6 +20,7 @@ import com.android.keyguard.logging.scrimLogger
import com.android.systemui.keyguard.data.lightRevealScrimRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
val Kosmos.lightRevealScrimInteractor by
@@ -30,5 +31,6 @@ val Kosmos.lightRevealScrimInteractor by
applicationCoroutineScope,
scrimLogger,
{ powerInteractor },
+ backgroundDispatcher = testDispatcher,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
index e6256a58a365..6c52d54fdd06 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
@@ -19,6 +19,8 @@ package com.android.systemui.mediaprojection.data.repository
import android.app.ActivityManager
import android.media.projection.StopReason
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMediaProjectionRepository : MediaProjectionRepository {
@@ -27,9 +29,18 @@ class FakeMediaProjectionRepository : MediaProjectionRepository {
override val mediaProjectionState: MutableStateFlow<MediaProjectionState> =
MutableStateFlow(MediaProjectionState.NotProjecting)
+ private val _projectionStartedDuringCallAndActivePostCallEvent = MutableSharedFlow<Unit>()
+
+ override val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit> =
+ _projectionStartedDuringCallAndActivePostCallEvent
+
var stopProjectingInvoked = false
override suspend fun stopProjecting(@StopReason stopReason: Int) {
stopProjectingInvoked = true
}
+
+ suspend fun emitProjectionStartedDuringCallAndActivePostCallEvent() {
+ _projectionStartedDuringCallAndActivePostCallEvent.emit(Unit)
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
index 2b6032ccafe5..9b600513b8aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
@@ -16,6 +16,7 @@
package com.android.systemui.mediaprojection.taskswitcher
+import android.media.projection.MediaProjectionEvent
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
import android.os.Binder
@@ -61,14 +62,22 @@ class FakeMediaProjectionManager {
fun dispatchOnSessionSet(
info: MediaProjectionInfo = DEFAULT_INFO,
- session: ContentRecordingSession?
+ session: ContentRecordingSession?,
) {
callbacks.forEach { it.onRecordingSessionSet(info, session) }
}
+ fun dispatchEvent(
+ event: MediaProjectionEvent,
+ info: MediaProjectionInfo? = DEFAULT_INFO,
+ session: ContentRecordingSession? = null,
+ ) {
+ callbacks.forEach { it.onMediaProjectionEvent(event, info, session) }
+ }
+
companion object {
fun createDisplaySession(): ContentRecordingSession =
- ContentRecordingSession.createDisplaySession(/* displayToMirror = */ 123)
+ ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
fun createSingleTaskSession(token: IBinder = Binder()): ContentRecordingSession =
ContentRecordingSession.createTaskSession(token)
@@ -76,10 +85,6 @@ class FakeMediaProjectionManager {
private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
private val DEFAULT_INFO =
- MediaProjectionInfo(
- DEFAULT_PACKAGE_NAME,
- DEFAULT_USER_HANDLE,
- /* launchCookie = */ null
- )
+ MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE, /* launchCookie= */ null)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt
index e46ede65bfb6..e9ba42547883 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.scene.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.shared.logger.sceneLogger
@@ -25,5 +26,6 @@ val Kosmos.sceneBackInteractor by Fixture {
SceneBackInteractor(
logger = sceneLogger,
sceneContainerConfig = sceneContainerConfig,
+ tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index d105326ec3d0..65bfafbfa9b0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -36,6 +36,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.model.sysUiState
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.disabledContentInteractor
@@ -89,5 +90,6 @@ val Kosmos.sceneContainerStartable by Fixture {
disabledContentInteractor = disabledContentInteractor,
activityTransitionAnimator = activityTransitionAnimator,
shadeModeInteractor = shadeModeInteractor,
+ tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt
index a4631f17cb37..2ba9c8094aac 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt
@@ -21,6 +21,7 @@ import android.provider.Settings
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.data.repository.shadeRepository
@@ -31,6 +32,7 @@ val Kosmos.shadeModeInteractor by Fixture {
applicationScope = applicationCoroutineScope,
repository = shadeRepository,
secureSettingsRepository = fakeSecureSettingsRepository,
+ tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index f8bf3c3fbbd9..1626904a9c19 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -25,6 +25,7 @@ import com.android.systemui.log.table.tableLogBufferFactory
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.events.domain.interactor.systemStatusEventAnimationInteractor
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModel
@@ -53,6 +54,7 @@ var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
sceneInteractor,
sceneContainerOcclusionInteractor,
shadeInteractor,
+ shareToAppChipViewModel,
ongoingActivityChipsViewModel,
statusBarPopupChipsViewModel,
systemStatusEventAnimationInteractor,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index fda57d6bb986..e422fef6c22c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -31,6 +31,7 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.Settings;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -71,9 +72,9 @@ import java.util.StringJoiner;
*/
class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation {
- private static final String TAG = AccessibilityInputFilter.class.getSimpleName();
+ private static final String TAG = "A11yInputFilter";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/**
* Flag for enabling the screen magnification feature.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8e0a7785c597..67fdca446ba4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -36,6 +36,7 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_PACKAGE_BROA
import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.hardware.input.InputSettings.isRepeatKeysFeatureFlagEnabled;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
@@ -156,6 +157,7 @@ import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.SurfaceControl;
+import android.view.ViewConfiguration;
import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
@@ -3494,6 +3496,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
somethingChanged |= readMagnificationFollowTypingLocked(userState);
somethingChanged |= readAlwaysOnMagnificationLocked(userState);
somethingChanged |= readMouseKeysEnabledLocked(userState);
+ somethingChanged |= readRepeatKeysSettingsLocked(userState);
return somethingChanged;
}
@@ -5771,6 +5774,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final Uri mUserSetupCompleteUri = Settings.Secure.getUriFor(
Settings.Secure.USER_SETUP_COMPLETE);
+ private final Uri mRepeatKeysEnabledUri = Settings.Secure.getUriFor(
+ Settings.Secure.KEY_REPEAT_ENABLED);
+
+ private final Uri mRepeatKeysTimeoutMsUri = Settings.Secure.getUriFor(
+ Settings.Secure.KEY_REPEAT_TIMEOUT_MS);
+
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -5827,6 +5836,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mNavigationModeUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mUserSetupCompleteUri, false, this, UserHandle.USER_ALL);
+ if (isRepeatKeysFeatureFlagEnabled() && Flags.enableMagnificationKeyboardControl()) {
+ contentResolver.registerContentObserver(
+ mRepeatKeysEnabledUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
+ mRepeatKeysTimeoutMsUri, false, this, UserHandle.USER_ALL);
+ }
}
@Override
@@ -5917,6 +5932,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
} else if (mNavigationModeUri.equals(uri) || mUserSetupCompleteUri.equals(uri)) {
updateShortcutsForCurrentNavigationMode();
+ } else if (mRepeatKeysEnabledUri.equals(uri)
+ || mRepeatKeysTimeoutMsUri.equals(uri)) {
+ readRepeatKeysSettingsLocked(userState);
}
}
}
@@ -6055,6 +6073,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return false;
}
+ boolean readRepeatKeysSettingsLocked(AccessibilityUserState userState) {
+ if (!isRepeatKeysFeatureFlagEnabled() || !Flags.enableMagnificationKeyboardControl()) {
+ return false;
+ }
+ final boolean isRepeatKeysEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.KEY_REPEAT_ENABLED,
+ 1, userState.mUserId) == 1;
+ final int repeatKeysTimeoutMs = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_TIMEOUT_MS,
+ ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT, userState.mUserId);
+ mMagnificationController.setRepeatKeysEnabled(isRepeatKeysEnabled);
+ mMagnificationController.setRepeatKeysTimeoutMs(repeatKeysTimeoutMs);
+
+ // No need to update any other state, so always return false.
+ return false;
+ }
+
boolean readMouseKeysEnabledLocked(AccessibilityUserState userState) {
if (!keyboardA11yMouseKeys()) {
return false;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index a3fe9ec5ea22..6cba3633b940 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -554,7 +554,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
if (motionEventInjector != null
&& mWindowManagerService.isTouchOrFaketouchDevice()) {
motionEventInjector.injectEvents(
- gestureSteps.getList(), mClient, sequence, displayId);
+ gestureSteps.getList(), mClient, sequence, displayId,
+ mAccessibilityServiceInfo.isAccessibilityTool());
} else {
try {
if (svcClientTracingEnabled()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
index 5cbd1a208ce1..b2169535d0de 100644
--- a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
+++ b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
@@ -105,12 +105,14 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
* either complete or cancelled.
*/
public void injectEvents(List<GestureStep> gestureSteps,
- IAccessibilityServiceClient serviceInterface, int sequence, int displayId) {
+ IAccessibilityServiceClient serviceInterface, int sequence, int displayId,
+ boolean fromAccessibilityTool) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = gestureSteps;
args.arg2 = serviceInterface;
args.argi1 = sequence;
args.argi2 = displayId;
+ args.argi3 = fromAccessibilityTool ? 1 : 0;
mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_INJECT_EVENTS, args));
}
@@ -132,9 +134,11 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
return;
}
cancelAnyPendingInjectedEvents();
- // Indicate that the input event is injected from accessibility, to let applications
- // distinguish it from events injected by other means.
- policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
+ if (!android.view.accessibility.Flags.preventA11yNontoolFromInjectingIntoSensitiveViews()) {
+ // Indicate that the input event is injected from accessibility, to let applications
+ // distinguish it from events injected by other means.
+ policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
+ }
sendMotionEventToNext(event, rawEvent, policyFlags);
}
@@ -159,8 +163,12 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
public boolean handleMessage(Message message) {
if (message.what == MESSAGE_INJECT_EVENTS) {
SomeArgs args = (SomeArgs) message.obj;
- injectEventsMainThread((List<GestureStep>) args.arg1,
- (IAccessibilityServiceClient) args.arg2, args.argi1, args.argi2);
+ injectEventsMainThread(
+ /*gestureSteps=*/(List<GestureStep>) args.arg1,
+ /*serviceInterface=*/(IAccessibilityServiceClient) args.arg2,
+ /*sequence=*/args.argi1,
+ /*displayId=*/args.argi2,
+ /*fromAccessibilityTool=*/args.argi3 == 1);
args.recycle();
return true;
}
@@ -169,9 +177,15 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
return false;
}
MotionEvent motionEvent = (MotionEvent) message.obj;
- sendMotionEventToNext(motionEvent, motionEvent,
- WindowManagerPolicyConstants.FLAG_PASS_TO_USER
- | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY);
+ int policyFlags = WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+ | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
+ if (android.view.accessibility.Flags.preventA11yNontoolFromInjectingIntoSensitiveViews()) {
+ boolean fromAccessibilityTool = message.arg2 == 1;
+ if (fromAccessibilityTool) {
+ policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL;
+ }
+ }
+ sendMotionEventToNext(motionEvent, motionEvent, policyFlags);
boolean isEndOfSequence = message.arg1 != 0;
if (isEndOfSequence) {
notifyService(mServiceInterfaceForCurrentGesture, mSequencesInProgress.get(0), true);
@@ -181,7 +195,8 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
}
private void injectEventsMainThread(List<GestureStep> gestureSteps,
- IAccessibilityServiceClient serviceInterface, int sequence, int displayId) {
+ IAccessibilityServiceClient serviceInterface, int sequence, int displayId,
+ boolean fromAccessibilityTool) {
if (mIsDestroyed) {
try {
serviceInterface.onPerformGestureResult(sequence, false);
@@ -228,7 +243,8 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
event.setDisplayId(displayId);
int isEndOfSequence = (i == events.size() - 1) ? 1 : 0;
Message message = mHandler.obtainMessage(
- MESSAGE_SEND_MOTION_EVENT, isEndOfSequence, 0, event);
+ MESSAGE_SEND_MOTION_EVENT, isEndOfSequence,
+ fromAccessibilityTool ? 1 : 0, event);
mLastScheduledEventTime = event.getEventTime();
mHandler.sendMessageDelayed(message, Math.max(0, event.getEventTime() - currentTime));
}
@@ -322,9 +338,16 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
long now = SystemClock.uptimeMillis();
MotionEvent cancelEvent =
obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1);
- sendMotionEventToNext(cancelEvent, cancelEvent,
- WindowManagerPolicyConstants.FLAG_PASS_TO_USER
- | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY);
+ int policyFlags = WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+ | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
+ if (android.view.accessibility.Flags
+ .preventA11yNontoolFromInjectingIntoSensitiveViews()) {
+ // ACTION_CANCEL events are internal system details for event stream state
+ // management and not used for performing new actions, so always treat them as
+ // originating from an accessibility tool.
+ policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL;
+ }
+ sendMotionEventToNext(cancelEvent, cancelEvent, policyFlags);
mOpenGesturesInProgress.put(source, false);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 486f1f449691..e757dd5a77b7 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -50,6 +50,7 @@ import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TypedValue;
import android.view.Display;
+import android.view.ViewConfiguration;
import android.view.accessibility.MagnificationAnimationCallback;
import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
@@ -122,9 +123,8 @@ public class MagnificationController implements MagnificationConnectionManager.C
private @ZoomDirection int mActiveZoomDirection = ZOOM_DIRECTION_IN;
private int mActiveZoomDisplay = Display.INVALID_DISPLAY;
- // TODO(b/355499907): Get initial repeat interval from repeat keys settings.
- @VisibleForTesting
- public static final int INITIAL_KEYBOARD_REPEAT_INTERVAL_MS = 500;
+ private int mInitialKeyboardRepeatIntervalMs =
+ ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
@VisibleForTesting
public static final int KEYBOARD_REPEAT_INTERVAL_MS = 60;
@@ -321,12 +321,6 @@ public class MagnificationController implements MagnificationConnectionManager.C
mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
mBackgroundExecutor, mAms::updateAlwaysOnMagnification);
-
- // TODO(b/355499907): Add an observer for repeat keys enabled changes,
- // rather than initializing once at startup.
- mRepeatKeysEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_ENABLED, 1,
- UserHandle.USER_CURRENT) != 0;
}
@VisibleForTesting
@@ -383,7 +377,7 @@ public class MagnificationController implements MagnificationConnectionManager.C
if (mRepeatKeysEnabled) {
mHandler.sendMessageDelayed(
PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this),
- INITIAL_KEYBOARD_REPEAT_INTERVAL_MS);
+ mInitialKeyboardRepeatIntervalMs);
}
}
@@ -404,7 +398,7 @@ public class MagnificationController implements MagnificationConnectionManager.C
if (mRepeatKeysEnabled) {
mHandler.sendMessageDelayed(
PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this),
- INITIAL_KEYBOARD_REPEAT_INTERVAL_MS);
+ mInitialKeyboardRepeatIntervalMs);
}
}
@@ -434,6 +428,19 @@ public class MagnificationController implements MagnificationConnectionManager.C
}
}
+ public void setRepeatKeysEnabled(boolean isRepeatKeysEnabled) {
+ mRepeatKeysEnabled = isRepeatKeysEnabled;
+ }
+
+ public void setRepeatKeysTimeoutMs(int repeatKeysTimeoutMs) {
+ mInitialKeyboardRepeatIntervalMs = repeatKeysTimeoutMs;
+ }
+
+ @VisibleForTesting
+ public int getInitialKeyboardRepeatIntervalMs() {
+ return mInitialKeyboardRepeatIntervalMs;
+ }
+
private void handleUserInteractionChanged(int displayId, int mode) {
if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
return;
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
index 61917676e88d..98ef974b9443 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
@@ -60,9 +60,7 @@ public interface CallerValidator {
* Validates that the caller can execute the specified app function.
*
* <p>The caller can execute if the app function's package name is the same as the caller's
- * package or the caller has either {@link Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} or
- * {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} granted. In some cases, app functions can
- * still opt-out of caller having {@link Manifest.permission#EXECUTE_APP_FUNCTIONS}.
+ * package or the caller has the {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} granted.
*
* @param callingUid The calling uid.
* @param callingPid The calling pid.
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index 69481c32baf0..fe163d77c4fc 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -18,7 +18,6 @@ package com.android.server.appfunctions;
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB;
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_NAMESPACE;
-import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS;
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction;
import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
@@ -84,12 +83,7 @@ class CallerValidatorImpl implements CallerValidator {
}
@Override
- @RequiresPermission(
- anyOf = {
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- Manifest.permission.EXECUTE_APP_FUNCTIONS
- },
- conditional = true)
+ @RequiresPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS)
public AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
int callingUid,
int callingPid,
@@ -101,17 +95,6 @@ class CallerValidatorImpl implements CallerValidator {
return AndroidFuture.completedFuture(true);
}
- boolean hasTrustedExecutionPermission =
- mContext.checkPermission(
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- callingPid,
- callingUid)
- == PackageManager.PERMISSION_GRANTED;
-
- if (hasTrustedExecutionPermission) {
- return AndroidFuture.completedFuture(true);
- }
-
boolean hasExecutionPermission =
mContext.checkPermission(
Manifest.permission.EXECUTE_APP_FUNCTIONS, callingPid, callingUid)
@@ -138,7 +121,8 @@ class CallerValidatorImpl implements CallerValidator {
.build())
.thenApply(
batchResult -> getGenericDocumentFromBatchResult(batchResult, documentId))
- .thenApply(document -> !getRestrictCallersWithExecuteAppFunctionsProperty(document))
+ // At this point, already checked the app has the permission.
+ .thenApply(document -> true)
.whenComplete(
(result, throwable) -> {
futureAppSearchSession.close();
@@ -160,12 +144,6 @@ class CallerValidatorImpl implements CallerValidator {
+ failedResult.getErrorMessage());
}
- private static boolean getRestrictCallersWithExecuteAppFunctionsProperty(
- GenericDocument genericDocument) {
- return genericDocument.getPropertyBoolean(
- STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS);
- }
-
@Override
public boolean verifyEnterprisePolicyIsAllowed(
@NonNull UserHandle callingUser, @NonNull UserHandle targetUser) {
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index cc73288cdbfa..9d13e37b2503 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -78,7 +78,6 @@ public class MetadataSyncAdapter {
// Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
// by permissions.
public static final int EXECUTE_APP_FUNCTIONS = 9;
- public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
public MetadataSyncAdapter(
@NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
@@ -281,8 +280,6 @@ public class MetadataSyncAdapter {
new PackageIdentifier(packageName, packageCert));
setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility(
runtimeMetadataSchema.getSchemaType(), Set.of(EXECUTE_APP_FUNCTIONS));
- setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility(
- runtimeMetadataSchema.getSchemaType(), Set.of(EXECUTE_APP_FUNCTIONS_TRUSTED));
}
return setSchemaRequestBuilder.build();
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index aef1c081cf03..d47aab061788 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -4673,12 +4673,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
keep.add(providerId);
// Use the new AppWidgetProviderInfo.
provider.setPartialInfoLocked(info);
- // Clear old previews
- if (remoteViewsProto()) {
- clearGeneratedPreviewsAsync(provider);
- } else {
- provider.clearGeneratedPreviewsLocked();
- }
// If it's enabled
final int M = provider.widgets.size();
if (M > 0) {
@@ -5104,6 +5098,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
AndroidFuture<RemoteViews> result = new AndroidFuture<>();
mSavePreviewsHandler.post(() -> {
SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+ if (previews.size() == 0 && provider.info.generatedPreviewCategories != 0) {
+ // Failed to read previews from file, clear the file and update providers.
+ saveGeneratedPreviews(provider, previews, /* notify= */ true);
+ }
for (int i = 0; i < previews.size(); i++) {
if ((widgetCategory & previews.keyAt(i)) != 0) {
result.complete(previews.valueAt(i));
@@ -5222,8 +5220,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
continue;
}
ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
- provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
- input);
+ try {
+ provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
+ input);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read generated previews from file for " + provider, e);
+ previewsFile.delete();
+ provider.info.generatedPreviewCategories = 0;
+ }
if (DEBUG) {
Slog.i(TAG, TextUtils.formatSimple(
"loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId,
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 02a8f6218468..521f676a6703 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -47,6 +47,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
+import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.ecm.EnhancedConfirmationManager;
@@ -302,8 +303,17 @@ public class CompanionDeviceManagerService extends SystemService {
enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
"create associations");
- mAssociationRequestsProcessor.processNewAssociationRequest(
- request, packageName, userId, callback);
+ if (request.isSkipRoleGrant()) {
+ checkCallerCanSkipRoleGrant();
+ mAssociationRequestsProcessor.createAssociation(userId, packageName,
+ /* macAddress= */ null, request.getDisplayName(),
+ request.getDeviceProfile(), /* associatedDevice= */ null,
+ request.isSelfManaged(), callback, /* resultReceiver= */ null,
+ request.getDeviceIcon(), /* skipRoleGrant= */ true);
+ } else {
+ mAssociationRequestsProcessor.processNewAssociationRequest(
+ request, packageName, userId, callback);
+ }
}
@Override
@@ -669,7 +679,7 @@ public class CompanionDeviceManagerService extends SystemService {
final MacAddress macAddressObj = MacAddress.fromString(macAddress);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
- null, null, null, false, null, null, null);
+ null, null, null, false, null, null, null, false);
}
private void checkCanCallNotificationApi(String callingPackage, int userId) {
@@ -684,6 +694,19 @@ public class CompanionDeviceManagerService extends SystemService {
"App must have an association before calling this API");
}
+ private void checkCallerCanSkipRoleGrant() {
+ final KeyguardManager keyguardManager =
+ getContext().getSystemService(KeyguardManager.class);
+ if (keyguardManager != null && keyguardManager.isKeyguardSecure()) {
+ throw new SecurityException("Skipping CDM role grant requires insecure keyguard.");
+ }
+ if (getContext().checkCallingPermission(ASSOCIATE_COMPANION_DEVICES)
+ != PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Skipping CDM role grant requires ASSOCIATE_COMPANION_DEVICES permission.");
+ }
+ }
+
@Override
public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) {
final AssociationInfo association =
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 3508f2ffc4c4..e7d1460aa66a 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -106,8 +106,9 @@ class CompanionDeviceShellCommand extends ShellCommand {
boolean selfManaged = getNextBooleanArg();
final MacAddress macAddress = MacAddress.fromString(address);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
- /* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
+ deviceProfile, deviceProfile, /* associatedDevice= */ null, selfManaged,
+ /* callback= */ null, /* resultReceiver= */ null,
+ /* deviceIcon= */ null, /* skipRoleGrant= */ false);
}
break;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 899b302316f9..8c2c63cbbd84 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -282,8 +282,8 @@ public class AssociationRequestsProcessor {
Binder.withCleanCallingIdentity(() -> {
createAssociation(userId, packageName, macAddress, request.getDisplayName(),
request.getDeviceProfile(), request.getAssociatedDevice(),
- request.isSelfManaged(),
- callback, resultReceiver, request.getDeviceIcon());
+ request.isSelfManaged(), callback, resultReceiver, request.getDeviceIcon(),
+ /* skipRoleGrant= */ false);
});
}
@@ -294,7 +294,8 @@ public class AssociationRequestsProcessor {
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
boolean selfManaged, @Nullable IAssociationRequestCallback callback,
- @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon) {
+ @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon,
+ boolean skipRoleGrant) {
final int id = mAssociationStore.getNextId();
final long timestamp = System.currentTimeMillis();
@@ -303,8 +304,17 @@ public class AssociationRequestsProcessor {
selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
/* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0,
deviceIcon, /* deviceId */ null);
- // Add role holder for association (if specified) and add new association to store.
- maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
+
+ if (skipRoleGrant) {
+ Slog.i(TAG, "Created association for " + association.getDeviceProfile() + " and userId="
+ + association.getUserId() + ", packageName="
+ + association.getPackageName() + " without granting role");
+ mAssociationStore.addAssociation(association);
+ sendCallbackAndFinish(association, callback, resultReceiver);
+ } else {
+ // Add role holder for association (if specified) and add new association to store.
+ maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
+ }
}
/**
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 3de84f17b583..d2db8f74cd05 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -27,7 +27,6 @@ import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
-import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.UEventObserver;
@@ -37,6 +36,7 @@ import android.util.Pair;
import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -57,8 +57,6 @@ import java.util.Map;
final class DockObserver extends SystemService {
private static final String TAG = "DockObserver";
- private static final int MSG_DOCK_STATE_CHANGED = 0;
-
private final PowerManager mPowerManager;
private final PowerManager.WakeLock mWakeLock;
@@ -66,11 +64,16 @@ final class DockObserver extends SystemService {
private boolean mSystemReady;
+ @GuardedBy("mLock")
private int mActualDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ @GuardedBy("mLock")
private int mReportedDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+
+ @GuardedBy("mLock")
private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ @GuardedBy("mLock")
private boolean mUpdatesStopped;
private final boolean mKeepDreamingWhenUnplugging;
@@ -182,18 +185,24 @@ final class DockObserver extends SystemService {
ExtconInfo.EXTCON_DOCK
});
- if (!infos.isEmpty()) {
- ExtconInfo info = infos.get(0);
- Slog.i(TAG, "Found extcon info devPath: " + info.getDevicePath()
- + ", statePath: " + info.getStatePath());
-
- // set initial status
- setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath()));
- mPreviousDockState = mActualDockState;
-
- mExtconUEventObserver.startObserving(info);
- } else {
- Slog.i(TAG, "No extcon dock device found in this kernel.");
+ synchronized (mLock) {
+ if (!infos.isEmpty()) {
+ ExtconInfo info = infos.get(0);
+ Slog.i(
+ TAG,
+ "Found extcon info devPath: "
+ + info.getDevicePath()
+ + ", statePath: "
+ + info.getStatePath());
+
+ // set initial status
+ setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath()));
+ mPreviousDockState = mActualDockState;
+
+ mExtconUEventObserver.startObserving(info);
+ } else {
+ Slog.i(TAG, "No extcon dock device found in this kernel.");
+ }
}
mDockObserverLocalService = new DockObserverLocalService();
@@ -223,13 +232,15 @@ final class DockObserver extends SystemService {
}
}
+ @GuardedBy("mLock")
private void updateIfDockedLocked() {
// don't bother broadcasting undocked here
if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- updateLocked();
+ postWakefulDockStateChange();
}
}
+ @GuardedBy("mLock")
private void setActualDockStateLocked(int newState) {
mActualDockState = newState;
if (!mUpdatesStopped) {
@@ -237,6 +248,7 @@ final class DockObserver extends SystemService {
}
}
+ @GuardedBy("mLock")
private void setDockStateLocked(int newState) {
if (newState != mReportedDockState) {
mReportedDockState = newState;
@@ -246,10 +258,12 @@ final class DockObserver extends SystemService {
if (mSystemReady) {
// Wake up immediately when docked or undocked unless prohibited from doing so.
if (allowWakeFromDock()) {
- mPowerManager.wakeUp(SystemClock.uptimeMillis(),
+ mPowerManager.wakeUp(
+ SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_DOCK,
"android.server:DOCK");
}
- updateLocked();
+ postWakefulDockStateChange();
}
}
}
@@ -263,9 +277,8 @@ final class DockObserver extends SystemService {
Settings.Global.THEATER_MODE_ON, 0) == 0);
}
- private void updateLocked() {
- mWakeLock.acquire();
- mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED);
+ private void postWakefulDockStateChange() {
+ mHandler.post(mWakeLock.wrap(this::handleDockStateChange));
}
private void handleDockStateChange() {
@@ -348,17 +361,7 @@ final class DockObserver extends SystemService {
}
}
- private final Handler mHandler = new Handler(true /*async*/) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_DOCK_STATE_CHANGED:
- handleDockStateChange();
- mWakeLock.release();
- break;
- }
- }
- };
+ private final Handler mHandler = new Handler(true /*async*/);
private int getDockedStateExtraValue(ExtconStateProvider state) {
for (ExtconStateConfig config : mExtconStateConfigs) {
@@ -386,6 +389,7 @@ final class DockObserver extends SystemService {
}
}
+ @GuardedBy("mLock")
private void setDockStateFromProviderLocked(ExtconStateProvider provider) {
int state = Intent.EXTRA_DOCK_STATE_UNDOCKED;
if ("1".equals(provider.getValue("DOCK"))) {
diff --git a/services/core/java/com/android/server/TradeInModeService.java b/services/core/java/com/android/server/TradeInModeService.java
index 70a033086261..a69667395ebd 100644
--- a/services/core/java/com/android/server/TradeInModeService.java
+++ b/services/core/java/com/android/server/TradeInModeService.java
@@ -137,12 +137,13 @@ public final class TradeInModeService extends SystemService {
Slog.i(TAG, "Not starting trade-in mode, device is setup.");
return false;
}
- if (SystemProperties.getInt("ro.debuggable", 0) == 1) {
- // We don't want to force adbd into TIM on debug builds.
- Slog.e(TAG, "Not starting trade-in mode, device is debuggable.");
- return false;
- }
- if (isAdbEnabled()) {
+ if (isDebuggable()) {
+ if (!isForceEnabledForTesting()) {
+ // We don't want to force adbd into TIM on debug builds.
+ Slog.e(TAG, "Not starting trade-in mode, device is debuggable.");
+ return false;
+ }
+ } else if (isAdbEnabled()) {
Slog.e(TAG, "Not starting trade-in mode, adb is already enabled.");
return false;
}
@@ -234,6 +235,10 @@ public final class TradeInModeService extends SystemService {
return SystemProperties.getInt("ro.debuggable", 0) == 1;
}
+ private boolean isForceEnabledForTesting() {
+ return SystemProperties.getInt("persist.adb.test_tradeinmode", 0) == 1;
+ }
+
private boolean isAdbEnabled() {
final ContentResolver cr = mContext.getContentResolver();
return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 1;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0603c4506cd1..6ece2654f3bf 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19397,13 +19397,13 @@ public class ActivityManagerService extends IActivityManager.Stub
if (((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED) == 0)
&& intent.getExtras() != null && intent.getExtras().hasIntent()) {
Slog.wtf(TAG,
- "[IntentRedirect] The intent does not have its nested keys collected as a "
+ "[IntentRedirect Hardening] The intent does not have its nested keys collected as a "
+ "preparation for creating intent creator tokens. Intent: "
+ intent + "; creatorPackage: " + creatorPackage);
if (preventIntentRedirectShowToastIfNestedKeysNotCollectedRW()) {
UiThread.getHandler().post(
() -> Toast.makeText(mContext,
- "Nested keys not collected. go/report-bug-intentRedir to report a"
+ "Nested keys not collected, activity launch won't be blocked. go/report-bug-intentRedir to report a"
+ " bug", Toast.LENGTH_LONG).show());
}
if (preventIntentRedirectThrowExceptionIfNestedKeysNotCollected()) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index d335529a006a..ce526e510053 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -572,6 +572,7 @@ public class CachedAppOptimizer {
public long mTotalAnonMemFreedKBs;
public long mSumOrigAnonRss;
public double mMaxCompactEfficiency;
+ public double mMaxSwapEfficiency;
// Cpu time
public long mTotalCpuTimeMillis;
@@ -586,6 +587,10 @@ public class CachedAppOptimizer {
if (compactEfficiency > mMaxCompactEfficiency) {
mMaxCompactEfficiency = compactEfficiency;
}
+ final double swapEfficiency = anonRssSaved / (double) origAnonRss;
+ if (swapEfficiency > mMaxSwapEfficiency) {
+ mMaxSwapEfficiency = swapEfficiency;
+ }
mTotalDeltaAnonRssKBs += anonRssSaved;
mTotalZramConsumedKBs += zramConsumed;
mTotalAnonMemFreedKBs += memFreed;
@@ -628,7 +633,11 @@ public class CachedAppOptimizer {
pw.println(" -----Memory Stats----");
pw.println(" Total Delta Anon RSS (KB) : " + mTotalDeltaAnonRssKBs);
pw.println(" Total Physical ZRAM Consumed (KB): " + mTotalZramConsumedKBs);
+ // Anon Mem Freed = Delta Anon RSS - ZRAM Consumed
pw.println(" Total Anon Memory Freed (KB): " + mTotalAnonMemFreedKBs);
+ pw.println(" Avg Swap Efficiency (KB) (Delta Anon RSS/Orig Anon RSS): "
+ + (mTotalDeltaAnonRssKBs / (double) mSumOrigAnonRss));
+ pw.println(" Max Swap Efficiency: " + mMaxSwapEfficiency);
// This tells us how much anon memory we were able to free thanks to running
// compaction
pw.println(" Avg Compaction Efficiency (Anon Freed/Anon RSS): "
@@ -808,8 +817,9 @@ public class CachedAppOptimizer {
pw.println(" Tracking last compaction stats for " + mLastCompactionStats.size()
+ " processes.");
pw.println("Last Compaction per process stats:");
- pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs,"
- + "CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,oomAdjReason)");
+ pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs"
+ + ",SwapEfficiency,CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,"
+ + "oomAdjReason)");
for (Map.Entry<Integer, SingleCompactionStats> entry :
mLastCompactionStats.entrySet()) {
SingleCompactionStats stats = entry.getValue();
@@ -818,7 +828,8 @@ public class CachedAppOptimizer {
pw.println();
pw.println("Last 20 Compactions Stats:");
pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs,"
- + "CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,oomAdjReason)");
+ + "SwapEfficiency,CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,"
+ + "oomAdjReason)");
for (SingleCompactionStats stats : mCompactionStatsHistory) {
stats.dump(pw);
}
@@ -1779,6 +1790,8 @@ public class CachedAppOptimizer {
double getCompactEfficiency() { return mAnonMemFreedKBs / (double) mOrigAnonRss; }
+ double getSwapEfficiency() { return mDeltaAnonRssKBs / (double) mOrigAnonRss; }
+
double getCompactCost() {
// mCpuTimeMillis / (anonMemFreedKBs/1024) and metric is in (ms/MB)
return mCpuTimeMillis / (double) mAnonMemFreedKBs * 1024;
@@ -1791,7 +1804,8 @@ public class CachedAppOptimizer {
@NeverCompile
void dump(PrintWriter pw) {
pw.println(" (" + mProcessName + "," + mSourceType.name() + "," + mDeltaAnonRssKBs
- + "," + mZramConsumedKBs + "," + mAnonMemFreedKBs + "," + getCompactEfficiency()
+ + "," + mZramConsumedKBs + "," + mAnonMemFreedKBs + ","
+ + getSwapEfficiency() + "," + getCompactEfficiency()
+ "," + getCompactCost() + "," + mProcState + "," + mOomAdj + ","
+ OomAdjuster.oomAdjReasonToString(mOomAdjReason) + ")");
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 833599810210..e8a222625b1b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -237,6 +237,13 @@ public class AppOpsService extends IAppOpsService.Stub {
*/
private static final int CURRENT_VERSION = 1;
+ /**
+ * The upper limit of total number of attributed op entries that can be returned in a binder
+ * transaction to avoid TransactionTooLargeException
+ */
+ private static final int NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD = 2000;
+
+
private SensorPrivacyManager mSensorPrivacyManager;
// Write at most every 30 minutes.
@@ -1702,6 +1709,8 @@ public class AppOpsService extends IAppOpsService.Stub {
Manifest.permission.GET_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid())
== PackageManager.PERMISSION_GRANTED;
+ int totalAttributedOpEntryCount = 0;
+
if (ops == null) {
resOps = new ArrayList<>();
for (int j = 0; j < pkgOps.size(); j++) {
@@ -1709,7 +1718,12 @@ public class AppOpsService extends IAppOpsService.Stub {
if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
continue;
}
- resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
+ if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) {
+ break;
+ }
+ OpEntry opEntry = getOpEntryForResult(curOp, persistentDeviceId);
+ resOps.add(opEntry);
+ totalAttributedOpEntryCount += opEntry.getAttributedOpEntries().size();
}
} else {
for (int j = 0; j < ops.length; j++) {
@@ -1721,10 +1735,21 @@ public class AppOpsService extends IAppOpsService.Stub {
if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
continue;
}
- resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
+ if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) {
+ break;
+ }
+ OpEntry opEntry = getOpEntryForResult(curOp, persistentDeviceId);
+ resOps.add(opEntry);
+ totalAttributedOpEntryCount += opEntry.getAttributedOpEntries().size();
}
}
}
+
+ if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) {
+ Slog.w(TAG, "The number of attributed op entries has exceeded the threshold. This "
+ + "could be due to DoS attack from malicious apps. The result is throttled.");
+ }
+
return resOps;
}
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index bbd0e41e9af7..7890db1454b4 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -509,3 +509,11 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "display_category_built_in"
+ namespace: "display_manager"
+ description: "Add a new category to get the built in displays."
+ bug: "293651324"
+ is_fixed_read_only: false
+}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index b8ce86b7c98c..2f228538d978 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -22,8 +22,6 @@ import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECT
import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
-import static com.android.hardware.input.Flags.keyboardLayoutManagerMultiUserImeSetup;
-
import android.annotation.AnyThread;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -68,7 +66,6 @@ import android.util.SparseArray;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.R;
@@ -1081,8 +1078,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
List<ImeInfo> imeInfoList = new ArrayList<>();
UserManager userManager = Objects.requireNonNull(
mContext.getSystemService(UserManager.class));
- InputMethodManager inputMethodManager = Objects.requireNonNull(
- mContext.getSystemService(InputMethodManager.class));
// Need to use InputMethodManagerInternal to call getEnabledInputMethodListAsUser()
// instead of using InputMethodManager which uses enforceCallingPermissions() that
// breaks when we are calling the method for work profile user ID since it doesn't check
@@ -1093,14 +1088,10 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
for (InputMethodInfo imeInfo :
inputMethodManagerInternal.getEnabledInputMethodListAsUser(
userId)) {
- final List<InputMethodSubtype> imeSubtypes;
- if (keyboardLayoutManagerMultiUserImeSetup()) {
- imeSubtypes = inputMethodManagerInternal.getEnabledInputMethodSubtypeListAsUser(
- imeInfo.getId(), true /* allowsImplicitlyEnabledSubtypes */, userId);
- } else {
- imeSubtypes = inputMethodManager.getEnabledInputMethodSubtypeList(imeInfo,
- true /* allowsImplicitlyEnabledSubtypes */);
- }
+ final List<InputMethodSubtype> imeSubtypes =
+ inputMethodManagerInternal.getEnabledInputMethodSubtypeListAsUser(
+ imeInfo.getId(), true /* allowsImplicitlyEnabledSubtypes */,
+ userId);
for (InputMethodSubtype imeSubtype : imeSubtypes) {
if (!imeSubtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
continue;
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index d23a8638803b..d00ac4d9cd11 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -29,14 +29,13 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.tv.mediaquality.AmbientBacklightColorFormat;
-import android.hardware.tv.mediaquality.DolbyAudioProcessing;
-import android.hardware.tv.mediaquality.DtsVirtualX;
import android.hardware.tv.mediaquality.IMediaQuality;
import android.hardware.tv.mediaquality.IPictureProfileAdjustmentListener;
import android.hardware.tv.mediaquality.IPictureProfileChangedListener;
import android.hardware.tv.mediaquality.ISoundProfileAdjustmentListener;
import android.hardware.tv.mediaquality.ISoundProfileChangedListener;
import android.hardware.tv.mediaquality.ParamCapability;
+import android.hardware.tv.mediaquality.ParameterRange;
import android.hardware.tv.mediaquality.PictureParameter;
import android.hardware.tv.mediaquality.PictureParameters;
import android.hardware.tv.mediaquality.SoundParameter;
@@ -50,8 +49,6 @@ import android.media.quality.IMediaQualityManager;
import android.media.quality.IPictureProfileCallback;
import android.media.quality.ISoundProfileCallback;
import android.media.quality.MediaQualityContract.BaseParameters;
-import android.media.quality.MediaQualityContract.PictureQuality;
-import android.media.quality.MediaQualityContract.SoundQuality;
import android.media.quality.MediaQualityManager;
import android.media.quality.ParameterCapability;
import android.media.quality.PictureProfile;
@@ -77,21 +74,16 @@ import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
import com.android.server.utils.Slogf;
-import org.json.JSONException;
-import org.json.JSONObject;
-
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
-import java.util.UUID;
import java.util.stream.Collectors;
/**
@@ -106,7 +98,6 @@ public class MediaQualityService extends SystemService {
private static final String PICTURE_PROFILE_PREFERENCE = "picture_profile_preference";
private static final String SOUND_PROFILE_PREFERENCE = "sound_profile_preference";
private static final String COMMA_DELIMITER = ",";
- private static final int MAX_UUID_GENERATION_ATTEMPTS = 10;
private final Context mContext;
private final MediaQualityDbHelper mMediaQualityDbHelper;
private final BiMap<Long, String> mPictureProfileTempIdMap;
@@ -275,7 +266,7 @@ public class MediaQualityService extends SystemService {
synchronized (mPictureProfileLock) {
SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
- ContentValues values = getContentValues(null,
+ ContentValues values = MediaQualityUtils.getContentValues(null,
pp.getProfileType(),
pp.getName(),
pp.getPackageName() == null || pp.getPackageName().isEmpty()
@@ -286,7 +277,7 @@ public class MediaQualityService extends SystemService {
// id is auto-generated by SQLite upon successful insertion of row
Long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
null, values);
- populateTempIdMap(mPictureProfileTempIdMap, id);
+ MediaQualityUtils.populateTempIdMap(mPictureProfileTempIdMap, id);
String value = mPictureProfileTempIdMap.getValue(id);
pp.setProfileId(value);
notifyOnPictureProfileAdded(value, pp, Binder.getCallingUid(),
@@ -308,8 +299,9 @@ public class MediaQualityService extends SystemService {
private android.hardware.tv.mediaquality.PictureProfile convertToHalPictureProfile(Long id,
PersistableBundle params) {
PictureParameters pictureParameters = new PictureParameters();
- pictureParameters.pictureParameters = convertPersistableBundleToPictureParameterList(
- params);
+ pictureParameters.pictureParameters =
+ MediaQualityUtils.convertPersistableBundleToPictureParameterList(
+ params);
android.hardware.tv.mediaquality.PictureProfile toReturn =
new android.hardware.tv.mediaquality.PictureProfile();
@@ -329,7 +321,7 @@ public class MediaQualityService extends SystemService {
}
synchronized (mPictureProfileLock) {
- ContentValues values = getContentValues(dbId,
+ ContentValues values = MediaQualityUtils.getContentValues(dbId,
pp.getProfileType(),
pp.getName(),
pp.getPackageName(),
@@ -405,7 +397,7 @@ public class MediaQualityService extends SystemService {
try (
Cursor cursor = getCursorAfterQuerying(
mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
- getMediaProfileColumns(includeParams), selection,
+ MediaQualityUtils.getMediaProfileColumns(includeParams), selection,
selectionArguments)
) {
int count = cursor.getCount();
@@ -420,7 +412,8 @@ public class MediaQualityService extends SystemService {
return null;
}
cursor.moveToFirst();
- return convertCursorToPictureProfileWithTempId(cursor);
+ return MediaQualityUtils.convertCursorToPictureProfileWithTempId(cursor,
+ mPictureProfileTempIdMap);
}
}
}
@@ -432,7 +425,8 @@ public class MediaQualityService extends SystemService {
try (
Cursor cursor = getCursorAfterQuerying(
mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
- getMediaProfileColumns(false), selection, selectionArguments)
+ MediaQualityUtils.getMediaProfileColumns(false), selection,
+ selectionArguments)
) {
int count = cursor.getCount();
if (count == 0) {
@@ -445,7 +439,8 @@ public class MediaQualityService extends SystemService {
return null;
}
cursor.moveToFirst();
- return convertCursorToPictureProfileWithTempId(cursor);
+ return MediaQualityUtils.convertCursorToPictureProfileWithTempId(cursor,
+ mPictureProfileTempIdMap);
}
}
@@ -463,7 +458,8 @@ public class MediaQualityService extends SystemService {
options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
String[] selectionArguments = {packageName};
- return getPictureProfilesBasedOnConditions(getMediaProfileColumns(includeParams),
+ return getPictureProfilesBasedOnConditions(MediaQualityUtils
+ .getMediaProfileColumns(includeParams),
selection, selectionArguments);
}
}
@@ -492,8 +488,8 @@ public class MediaQualityService extends SystemService {
try {
if (mMediaQuality != null) {
- PictureParameter[] pictureParameters =
- convertPersistableBundleToPictureParameterList(params);
+ PictureParameter[] pictureParameters = MediaQualityUtils
+ .convertPersistableBundleToPictureParameterList(params);
PictureParameters pp = new PictureParameters();
pp.pictureParameters = pictureParameters;
@@ -507,337 +503,6 @@ public class MediaQualityService extends SystemService {
return false;
}
- private PictureParameter[] convertPersistableBundleToPictureParameterList(
- PersistableBundle params) {
- if (params == null) {
- return null;
- }
-
- List<PictureParameter> pictureParams = new ArrayList<>();
- if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) {
- pictureParams.add(PictureParameter.brightness(params.getLong(
- PictureQuality.PARAMETER_BRIGHTNESS)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_CONTRAST)) {
- pictureParams.add(PictureParameter.contrast(params.getInt(
- PictureQuality.PARAMETER_CONTRAST)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_SHARPNESS)) {
- pictureParams.add(PictureParameter.sharpness(params.getInt(
- PictureQuality.PARAMETER_SHARPNESS)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_SATURATION)) {
- pictureParams.add(PictureParameter.saturation(params.getInt(
- PictureQuality.PARAMETER_SATURATION)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_HUE)) {
- pictureParams.add(PictureParameter.hue(params.getInt(
- PictureQuality.PARAMETER_HUE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)) {
- pictureParams.add(PictureParameter.colorTunerBrightness(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)) {
- pictureParams.add(PictureParameter.colorTunerSaturation(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE)) {
- pictureParams.add(PictureParameter.colorTunerHue(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_HUE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)) {
- pictureParams.add(PictureParameter.colorTunerRedOffset(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)) {
- pictureParams.add(PictureParameter.colorTunerGreenOffset(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)) {
- pictureParams.add(PictureParameter.colorTunerBlueOffset(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
- pictureParams.add(PictureParameter.colorTunerRedGain(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
- pictureParams.add(PictureParameter.colorTunerGreenGain(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
- pictureParams.add(PictureParameter.colorTunerBlueGain(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_NOISE_REDUCTION)) {
- pictureParams.add(PictureParameter.noiseReduction(
- (byte) params.getInt(PictureQuality.PARAMETER_NOISE_REDUCTION)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) {
- pictureParams.add(PictureParameter.mpegNoiseReduction(
- (byte) params.getInt(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_FLESH_TONE)) {
- pictureParams.add(PictureParameter.fleshTone(
- (byte) params.getInt(PictureQuality.PARAMETER_FLESH_TONE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_DECONTOUR)) {
- pictureParams.add(PictureParameter.deContour(
- (byte) params.getInt(PictureQuality.PARAMETER_DECONTOUR)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) {
- pictureParams.add(PictureParameter.dynamicLumaControl(
- (byte) params.getInt(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_FILM_MODE)) {
- pictureParams.add(PictureParameter.filmMode(params.getBoolean(
- PictureQuality.PARAMETER_FILM_MODE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_BLUE_STRETCH)) {
- pictureParams.add(PictureParameter.blueStretch(params.getBoolean(
- PictureQuality.PARAMETER_BLUE_STRETCH)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNE)) {
- pictureParams.add(PictureParameter.colorTune(params.getBoolean(
- PictureQuality.PARAMETER_COLOR_TUNE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) {
- pictureParams.add(PictureParameter.colorTemperature(
- (byte) params.getInt(
- PictureQuality.PARAMETER_COLOR_TEMPERATURE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_GLOBAL_DIMMING)) {
- pictureParams.add(PictureParameter.globeDimming(params.getBoolean(
- PictureQuality.PARAMETER_GLOBAL_DIMMING)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)) {
- pictureParams.add(PictureParameter.autoPictureQualityEnabled(params.getBoolean(
- PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) {
- pictureParams.add(PictureParameter.autoSuperResolutionEnabled(params.getBoolean(
- PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
- pictureParams.add(PictureParameter.colorTemperatureRedGain(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
- pictureParams.add(PictureParameter.colorTemperatureGreenGain(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
- pictureParams.add(PictureParameter.colorTemperatureBlueGain(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_LEVEL_RANGE)) {
- pictureParams.add(PictureParameter.levelRange(
- (byte) params.getInt(PictureQuality.PARAMETER_LEVEL_RANGE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_GAMUT_MAPPING)) {
- pictureParams.add(PictureParameter.gamutMapping(params.getBoolean(
- PictureQuality.PARAMETER_GAMUT_MAPPING)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_PC_MODE)) {
- pictureParams.add(PictureParameter.pcMode(params.getBoolean(
- PictureQuality.PARAMETER_PC_MODE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_LOW_LATENCY)) {
- pictureParams.add(PictureParameter.lowLatency(params.getBoolean(
- PictureQuality.PARAMETER_LOW_LATENCY)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_VRR)) {
- pictureParams.add(PictureParameter.vrr(params.getBoolean(
- PictureQuality.PARAMETER_VRR)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_CVRR)) {
- pictureParams.add(PictureParameter.cvrr(params.getBoolean(
- PictureQuality.PARAMETER_CVRR)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) {
- pictureParams.add(PictureParameter.hdmiRgbRange(
- (byte) params.getInt(PictureQuality.PARAMETER_HDMI_RGB_RANGE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_SPACE)) {
- pictureParams.add(PictureParameter.colorSpace(
- (byte) params.getInt(PictureQuality.PARAMETER_COLOR_SPACE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)) {
- pictureParams.add(PictureParameter.panelInitMaxLuminceNits(
- params.getInt(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)) {
- pictureParams.add(PictureParameter.panelInitMaxLuminceValid(
- params.getBoolean(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_GAMMA)) {
- pictureParams.add(PictureParameter.gamma(
- (byte) params.getInt(PictureQuality.PARAMETER_GAMMA)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) {
- pictureParams.add(PictureParameter.colorTemperatureRedOffset(params.getInt(
- PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)) {
- pictureParams.add(PictureParameter.colorTemperatureGreenOffset(params.getInt(
- PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)) {
- pictureParams.add(PictureParameter.colorTemperatureBlueOffset(params.getInt(
- PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_RED)) {
- pictureParams.add(PictureParameter.elevenPointRed(params.getIntArray(
- PictureQuality.PARAMETER_ELEVEN_POINT_RED)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)) {
- pictureParams.add(PictureParameter.elevenPointGreen(params.getIntArray(
- PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)) {
- pictureParams.add(PictureParameter.elevenPointBlue(params.getIntArray(
- PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) {
- pictureParams.add(PictureParameter.lowBlueLight(
- (byte) params.getInt(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_LD_MODE)) {
- pictureParams.add(PictureParameter.LdMode(
- (byte) params.getInt(PictureQuality.PARAMETER_LD_MODE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_GAIN)) {
- pictureParams.add(PictureParameter.osdRedGain(params.getInt(
- PictureQuality.PARAMETER_OSD_RED_GAIN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_GAIN)) {
- pictureParams.add(PictureParameter.osdGreenGain(params.getInt(
- PictureQuality.PARAMETER_OSD_GREEN_GAIN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_GAIN)) {
- pictureParams.add(PictureParameter.osdBlueGain(params.getInt(
- PictureQuality.PARAMETER_OSD_BLUE_GAIN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_OFFSET)) {
- pictureParams.add(PictureParameter.osdRedOffset(params.getInt(
- PictureQuality.PARAMETER_OSD_RED_OFFSET)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_OFFSET)) {
- pictureParams.add(PictureParameter.osdGreenOffset(params.getInt(
- PictureQuality.PARAMETER_OSD_GREEN_OFFSET)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_OFFSET)) {
- pictureParams.add(PictureParameter.osdBlueOffset(params.getInt(
- PictureQuality.PARAMETER_OSD_BLUE_OFFSET)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_OSD_HUE)) {
- pictureParams.add(PictureParameter.osdHue(params.getInt(
- PictureQuality.PARAMETER_OSD_HUE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_OSD_SATURATION)) {
- pictureParams.add(PictureParameter.osdSaturation(params.getInt(
- PictureQuality.PARAMETER_OSD_SATURATION)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_OSD_CONTRAST)) {
- pictureParams.add(PictureParameter.osdContrast(params.getInt(
- PictureQuality.PARAMETER_OSD_CONTRAST)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)) {
- pictureParams.add(PictureParameter.colorTunerSwitch(params.getBoolean(
- PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)) {
- pictureParams.add(PictureParameter.colorTunerHueRed(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)) {
- pictureParams.add(PictureParameter.colorTunerHueGreen(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)) {
- pictureParams.add(PictureParameter.colorTunerHueBlue(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)) {
- pictureParams.add(PictureParameter.colorTunerHueCyan(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)) {
- pictureParams.add(PictureParameter.colorTunerHueMagenta(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)) {
- pictureParams.add(PictureParameter.colorTunerHueYellow(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)) {
- pictureParams.add(PictureParameter.colorTunerHueFlesh(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)) {
- pictureParams.add(PictureParameter.colorTunerSaturationRed(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)) {
- pictureParams.add(PictureParameter.colorTunerSaturationGreen(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)) {
- pictureParams.add(PictureParameter.colorTunerSaturationBlue(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)) {
- pictureParams.add(PictureParameter.colorTunerSaturationCyan(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)) {
- pictureParams.add(PictureParameter.colorTunerSaturationMagenta(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)) {
- pictureParams.add(PictureParameter.colorTunerSaturationYellow(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)) {
- pictureParams.add(PictureParameter.colorTunerSaturationFlesh(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)) {
- pictureParams.add(PictureParameter.colorTunerLuminanceRed(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)) {
- pictureParams.add(PictureParameter.colorTunerLuminanceGreen(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)) {
- pictureParams.add(PictureParameter.colorTunerLuminanceBlue(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)) {
- pictureParams.add(PictureParameter.colorTunerLuminanceCyan(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)) {
- pictureParams.add(PictureParameter.colorTunerLuminanceMagenta(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)) {
- pictureParams.add(PictureParameter.colorTunerLuminanceYellow(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)) {
- pictureParams.add(PictureParameter.colorTunerLuminanceFlesh(params.getInt(
- PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)));
- }
- if (params.containsKey(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)) {
- pictureParams.add(PictureParameter.pictureQualityEventType(
- (byte) params.getInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)));
- }
- return (PictureParameter[]) pictureParams.toArray();
- }
-
@GuardedBy("mPictureProfileLock")
@Override
public List<String> getPictureProfilePackageNames(UserHandle user) {
@@ -903,7 +568,7 @@ public class MediaQualityService extends SystemService {
synchronized (mSoundProfileLock) {
SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
- ContentValues values = getContentValues(null,
+ ContentValues values = MediaQualityUtils.getContentValues(null,
sp.getProfileType(),
sp.getName(),
sp.getPackageName() == null || sp.getPackageName().isEmpty()
@@ -914,7 +579,7 @@ public class MediaQualityService extends SystemService {
// id is auto-generated by SQLite upon successful insertion of row
Long id = db.insert(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
null, values);
- populateTempIdMap(mSoundProfileTempIdMap, id);
+ MediaQualityUtils.populateTempIdMap(mSoundProfileTempIdMap, id);
String value = mSoundProfileTempIdMap.getValue(id);
sp.setProfileId(value);
notifyOnSoundProfileAdded(value, sp, Binder.getCallingUid(),
@@ -935,7 +600,8 @@ public class MediaQualityService extends SystemService {
private android.hardware.tv.mediaquality.SoundProfile convertToHalSoundProfile(Long id,
PersistableBundle params) {
SoundParameters soundParameters = new SoundParameters();
- soundParameters.soundParameters = convertPersistableBundleToSoundParameterList(params);
+ soundParameters.soundParameters =
+ MediaQualityUtils.convertPersistableBundleToSoundParameterList(params);
android.hardware.tv.mediaquality.SoundProfile toReturn =
new android.hardware.tv.mediaquality.SoundProfile();
@@ -955,18 +621,19 @@ public class MediaQualityService extends SystemService {
}
synchronized (mSoundProfileLock) {
- ContentValues values = getContentValues(dbId,
+ ContentValues values = MediaQualityUtils.getContentValues(dbId,
sp.getProfileType(),
sp.getName(),
sp.getPackageName(),
sp.getInputId(),
sp.getParameters());
- SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
- db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values);
- notifyOnSoundProfileUpdated(mSoundProfileTempIdMap.getValue(dbId),
- getSoundProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid());
- notifyHalOnSoundProfileChange(dbId, sp.getParameters());
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+ db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+ null, values);
+ notifyOnSoundProfileUpdated(mSoundProfileTempIdMap.getValue(dbId),
+ getSoundProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid());
+ notifyHalOnSoundProfileChange(dbId, sp.getParameters());
}
}
@@ -1029,7 +696,7 @@ public class MediaQualityService extends SystemService {
try (
Cursor cursor = getCursorAfterQuerying(
mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
- getMediaProfileColumns(includeParams), selection,
+ MediaQualityUtils.getMediaProfileColumns(includeParams), selection,
selectionArguments)
) {
int count = cursor.getCount();
@@ -1044,7 +711,8 @@ public class MediaQualityService extends SystemService {
return null;
}
cursor.moveToFirst();
- return convertCursorToSoundProfileWithTempId(cursor);
+ return MediaQualityUtils.convertCursorToSoundProfileWithTempId(cursor,
+ mSoundProfileTempIdMap);
}
}
}
@@ -1056,7 +724,8 @@ public class MediaQualityService extends SystemService {
try (
Cursor cursor = getCursorAfterQuerying(
mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
- getMediaProfileColumns(false), selection, selectionArguments)
+ MediaQualityUtils.getMediaProfileColumns(false), selection,
+ selectionArguments)
) {
int count = cursor.getCount();
if (count == 0) {
@@ -1069,7 +738,8 @@ public class MediaQualityService extends SystemService {
return null;
}
cursor.moveToFirst();
- return convertCursorToSoundProfileWithTempId(cursor);
+ return MediaQualityUtils.convertCursorToSoundProfileWithTempId(
+ cursor, mSoundProfileTempIdMap);
}
}
@@ -1087,7 +757,8 @@ public class MediaQualityService extends SystemService {
options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
String[] selectionArguments = {packageName};
- return getSoundProfilesBasedOnConditions(getMediaProfileColumns(includeParams),
+ return getSoundProfilesBasedOnConditions(MediaQualityUtils
+ .getMediaProfileColumns(includeParams),
selection, selectionArguments);
}
}
@@ -1116,7 +787,7 @@ public class MediaQualityService extends SystemService {
try {
if (mMediaQuality != null) {
SoundParameter[] soundParameters =
- convertPersistableBundleToSoundParameterList(params);
+ MediaQualityUtils.convertPersistableBundleToSoundParameterList(params);
SoundParameters sp = new SoundParameters();
sp.soundParameters = soundParameters;
@@ -1130,95 +801,6 @@ public class MediaQualityService extends SystemService {
return false;
}
- private SoundParameter[] convertPersistableBundleToSoundParameterList(
- PersistableBundle params) {
- //TODO: set EqualizerDetail
- if (params == null) {
- return null;
- }
- List<SoundParameter> soundParams = new ArrayList<>();
- if (params.containsKey(SoundQuality.PARAMETER_BALANCE)) {
- soundParams.add(SoundParameter.balance(params.getInt(
- SoundQuality.PARAMETER_BALANCE)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_BASS)) {
- soundParams.add(SoundParameter.bass(params.getInt(SoundQuality.PARAMETER_BASS)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_TREBLE)) {
- soundParams.add(SoundParameter.treble(params.getInt(
- SoundQuality.PARAMETER_TREBLE)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_SURROUND_SOUND)) {
- soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean(
- SoundQuality.PARAMETER_SURROUND_SOUND)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS)) {
- soundParams.add(SoundParameter.speakersEnabled(params.getBoolean(
- SoundQuality.PARAMETER_SPEAKERS)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)) {
- soundParams.add(SoundParameter.speakersDelayMs(params.getInt(
- SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)) {
- soundParams.add(SoundParameter.autoVolumeControl(params.getBoolean(
- SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_DTS_DRC)) {
- soundParams.add(SoundParameter.dtsDrc(params.getBoolean(
- SoundQuality.PARAMETER_DTS_DRC)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)) {
- soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean(
- SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_EARC)) {
- soundParams.add(SoundParameter.enhancedAudioReturnChannelEnabled(params.getBoolean(
- SoundQuality.PARAMETER_EARC)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_DOWN_MIX_MODE)) {
- soundParams.add(SoundParameter.downmixMode((byte) params.getInt(
- SoundQuality.PARAMETER_DOWN_MIX_MODE)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_SOUND_STYLE)) {
- soundParams.add(SoundParameter.soundStyle((byte) params.getInt(
- SoundQuality.PARAMETER_SOUND_STYLE)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)) {
- soundParams.add(SoundParameter.digitalOutput((byte) params.getInt(
- SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)));
- }
- if (params.containsKey(SoundQuality.PARAMETER_DIALOGUE_ENHANCER)) {
- soundParams.add(SoundParameter.dolbyDialogueEnhancer((byte) params.getInt(
- SoundQuality.PARAMETER_DIALOGUE_ENHANCER)));
- }
-
- DolbyAudioProcessing dab = new DolbyAudioProcessing();
- dab.soundMode =
- (byte) params.getInt(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SOUND_MODE);
- dab.volumeLeveler =
- params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_VOLUME_LEVELER);
- dab.surroundVirtualizer = params.getBoolean(
- SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SURROUND_VIRTUALIZER);
- dab.dolbyAtmos =
- params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_DOLBY_ATMOS);
- soundParams.add(SoundParameter.dolbyAudioProcessing(dab));
-
- DtsVirtualX dts = new DtsVirtualX();
- dts.tbHdx = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TBHDX);
- dts.limiter = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_LIMITER);
- dts.truSurroundX = params.getBoolean(
- SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_SURROUND_X);
- dts.truVolumeHd = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_VOLUME_HD);
- dts.dialogClarity = params.getBoolean(
- SoundQuality.PARAMETER_DTS_VIRTUAL_X_DIALOG_CLARITY);
- dts.definition = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_DEFINITION);
- dts.height = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_HEIGHT);
- soundParams.add(SoundParameter.dtsVirtualX(dts));
-
- return (SoundParameter[]) soundParams.toArray();
- }
-
@GuardedBy("mSoundProfileLock")
@Override
public List<String> getSoundProfilePackageNames(UserHandle user) {
@@ -1269,169 +851,6 @@ public class MediaQualityService extends SystemService {
mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED;
}
- private void populateTempIdMap(BiMap<Long, String> map, Long id) {
- if (id != null && map.getValue(id) == null) {
- String uuid;
- int attempts = 0;
- while (attempts < MAX_UUID_GENERATION_ATTEMPTS) {
- uuid = UUID.randomUUID().toString();
- if (map.getKey(uuid) == null) {
- map.put(id, uuid);
- return;
- }
- attempts++;
- }
- }
- }
-
- private String persistableBundleToJson(PersistableBundle bundle) {
- JSONObject json = new JSONObject();
- for (String key : bundle.keySet()) {
- Object value = bundle.get(key);
- try {
- if (value instanceof String) {
- json.put(key, bundle.getString(key));
- } else if (value instanceof Integer) {
- json.put(key, bundle.getInt(key));
- } else if (value instanceof Long) {
- json.put(key, bundle.getLong(key));
- } else if (value instanceof Boolean) {
- json.put(key, bundle.getBoolean(key));
- } else if (value instanceof Double) {
- json.put(key, bundle.getDouble(key));
- }
- } catch (JSONException e) {
- Log.e(TAG, "Unable to serialize ", e);
- }
- }
- return json.toString();
- }
-
- private PersistableBundle jsonToPersistableBundle(String jsonString) {
- PersistableBundle bundle = new PersistableBundle();
- if (jsonString != null) {
- JSONObject jsonObject = null;
- try {
- jsonObject = new JSONObject(jsonString);
-
- Iterator<String> keys = jsonObject.keys();
- while (keys.hasNext()) {
- String key = keys.next();
- Object value = jsonObject.get(key);
-
- if (value instanceof String) {
- bundle.putString(key, (String) value);
- } else if (value instanceof Integer) {
- bundle.putInt(key, (Integer) value);
- } else if (value instanceof Boolean) {
- bundle.putBoolean(key, (Boolean) value);
- } else if (value instanceof Double) {
- bundle.putDouble(key, (Double) value);
- } else if (value instanceof Long) {
- bundle.putLong(key, (Long) value);
- }
- }
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- }
- return bundle;
- }
-
- private ContentValues getContentValues(Long dbId, Integer profileType, String name,
- String packageName, String inputId, PersistableBundle params) {
- ContentValues values = new ContentValues();
- if (dbId != null) {
- values.put(BaseParameters.PARAMETER_ID, dbId);
- }
- if (profileType != null) {
- values.put(BaseParameters.PARAMETER_TYPE, profileType);
- }
- if (name != null) {
- values.put(BaseParameters.PARAMETER_NAME, name);
- }
- if (packageName != null) {
- values.put(BaseParameters.PARAMETER_PACKAGE, packageName);
- }
- if (inputId != null) {
- values.put(BaseParameters.PARAMETER_INPUT_ID, inputId);
- }
- if (params != null) {
- values.put(mMediaQualityDbHelper.SETTINGS, persistableBundleToJson(params));
- }
- return values;
- }
-
- private String[] getMediaProfileColumns(boolean includeParams) {
- ArrayList<String> columns = new ArrayList<>(Arrays.asList(
- BaseParameters.PARAMETER_ID,
- BaseParameters.PARAMETER_TYPE,
- BaseParameters.PARAMETER_NAME,
- BaseParameters.PARAMETER_INPUT_ID,
- BaseParameters.PARAMETER_PACKAGE)
- );
- if (includeParams) {
- columns.add(mMediaQualityDbHelper.SETTINGS);
- }
- return columns.toArray(new String[0]);
- }
-
- private PictureProfile convertCursorToPictureProfileWithTempId(Cursor cursor) {
- return new PictureProfile(
- getTempId(mPictureProfileTempIdMap, cursor),
- getType(cursor),
- getName(cursor),
- getInputId(cursor),
- getPackageName(cursor),
- jsonToPersistableBundle(getSettingsString(cursor)),
- PictureProfileHandle.NONE
- );
- }
-
- private SoundProfile convertCursorToSoundProfileWithTempId(Cursor cursor) {
- return new SoundProfile(
- getTempId(mSoundProfileTempIdMap, cursor),
- getType(cursor),
- getName(cursor),
- getInputId(cursor),
- getPackageName(cursor),
- jsonToPersistableBundle(getSettingsString(cursor)),
- SoundProfileHandle.NONE
- );
- }
-
- private String getTempId(BiMap<Long, String> map, Cursor cursor) {
- int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_ID);
- Long dbId = colIndex != -1 ? cursor.getLong(colIndex) : null;
- populateTempIdMap(map, dbId);
- return map.getValue(dbId);
- }
-
- private int getType(Cursor cursor) {
- int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_TYPE);
- return colIndex != -1 ? cursor.getInt(colIndex) : 0;
- }
-
- private String getName(Cursor cursor) {
- int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_NAME);
- return colIndex != -1 ? cursor.getString(colIndex) : null;
- }
-
- private String getInputId(Cursor cursor) {
- int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_INPUT_ID);
- return colIndex != -1 ? cursor.getString(colIndex) : null;
- }
-
- private String getPackageName(Cursor cursor) {
- int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_PACKAGE);
- return colIndex != -1 ? cursor.getString(colIndex) : null;
- }
-
- private String getSettingsString(Cursor cursor) {
- int colIndex = cursor.getColumnIndex(mMediaQualityDbHelper.SETTINGS);
- return colIndex != -1 ? cursor.getString(colIndex) : null;
- }
-
private Cursor getCursorAfterQuerying(String table, String[] columns, String selection,
String[] selectionArgs) {
SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
@@ -1448,7 +867,8 @@ public class MediaQualityService extends SystemService {
) {
List<PictureProfile> pictureProfiles = new ArrayList<>();
while (cursor.moveToNext()) {
- pictureProfiles.add(convertCursorToPictureProfileWithTempId(cursor));
+ pictureProfiles.add(MediaQualityUtils.convertCursorToPictureProfileWithTempId(
+ cursor, mPictureProfileTempIdMap));
}
return pictureProfiles;
}
@@ -1463,7 +883,8 @@ public class MediaQualityService extends SystemService {
) {
List<SoundProfile> soundProfiles = new ArrayList<>();
while (cursor.moveToNext()) {
- soundProfiles.add(convertCursorToSoundProfileWithTempId(cursor));
+ soundProfiles.add(MediaQualityUtils.convertCursorToSoundProfileWithTempId(
+ cursor, mSoundProfileTempIdMap));
}
return soundProfiles;
}
@@ -1713,7 +1134,39 @@ public class MediaQualityService extends SystemService {
@Override
public List<ParameterCapability> getParameterCapabilities(
List<String> names, UserHandle user) {
- return new ArrayList<>();
+ byte[] byteArray = MediaQualityUtils.convertParameterToByteArray(names);
+ ParamCapability[] caps = new ParamCapability[byteArray.length];
+ try {
+ mMediaQuality.getParamCaps(byteArray, caps);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get parameter capabilities", e);
+ }
+
+ return getListParameterCapability(caps);
+ }
+
+ private List<ParameterCapability> getListParameterCapability(ParamCapability[] caps) {
+ List<ParameterCapability> pcList = new ArrayList<>();
+ for (ParamCapability pcHal : caps) {
+ String name = MediaQualityUtils.getParameterName(pcHal.name);
+ boolean isSupported = pcHal.isSupported;
+ int type = pcHal.defaultValue == null ? 0 : pcHal.defaultValue.getTag() + 1;
+ Bundle bundle = convertToCaps(pcHal.range);
+
+ pcList.add(new ParameterCapability(name, isSupported, type, bundle));
+ }
+ return pcList;
+ }
+
+ private Bundle convertToCaps(ParameterRange range) {
+ Bundle bundle = new Bundle();
+ bundle.putObject("INT_MIN_MAX", range.numRange.getIntMinMax());
+ bundle.putObject("INT_VALUES_SUPPORTED", range.numRange.getIntValuesSupported());
+ bundle.putObject("DOUBLE_MIN_MAX", range.numRange.getDoubleMinMax());
+ bundle.putObject("DOUBLE_VALUES_SUPPORTED", range.numRange.getDoubleValuesSupported());
+ bundle.putObject("LONG_MIN_MAX", range.numRange.getLongMinMax());
+ bundle.putObject("LONG_VALUES_SUPPORTED", range.numRange.getLongValuesSupported());
+ return bundle;
}
@GuardedBy("mPictureProfileLock")
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
new file mode 100644
index 000000000000..5bd4420e9944
--- /dev/null
+++ b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
@@ -0,0 +1,1543 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.quality;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.hardware.tv.mediaquality.DolbyAudioProcessing;
+import android.hardware.tv.mediaquality.DtsVirtualX;
+import android.hardware.tv.mediaquality.ParameterName;
+import android.hardware.tv.mediaquality.PictureParameter;
+import android.hardware.tv.mediaquality.SoundParameter;
+import android.media.quality.MediaQualityContract.BaseParameters;
+import android.media.quality.MediaQualityContract.PictureQuality;
+import android.media.quality.MediaQualityContract.SoundQuality;
+import android.media.quality.PictureProfile;
+import android.media.quality.PictureProfileHandle;
+import android.media.quality.SoundProfile;
+import android.media.quality.SoundProfileHandle;
+import android.os.PersistableBundle;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Utility class for media quality framework.
+ *
+ * @hide
+ */
+public final class MediaQualityUtils {
+
+ private static final int MAX_UUID_GENERATION_ATTEMPTS = 10;
+ private static final String TAG = "MediaQualityUtils";
+ public static final String SETTINGS = "settings";
+
+ /**
+ * Convert PictureParameter List to PersistableBundle.
+ */
+ public static PersistableBundle convertPictureParameterListToPersistableBundle(
+ PictureParameter[] parameters) {
+ PersistableBundle bundle = new PersistableBundle();
+ for (PictureParameter pp : parameters) {
+ if (pp.getBrightness() > -1) {
+ bundle.putLong(PictureQuality.PARAMETER_BRIGHTNESS, (long) pp.getBrightness());
+ }
+ if (pp.getContrast() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_CONTRAST, pp.getContrast());
+ }
+ if (pp.getSharpness() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_SHARPNESS, pp.getSharpness());
+ }
+ if (pp.getSaturation() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_SATURATION, pp.getSaturation());
+ }
+ if (pp.getHue() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_HUE, pp.getHue());
+ }
+ if (pp.getColorTunerBrightness() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS,
+ pp.getColorTunerBrightness());
+ }
+ if (pp.getColorTunerSaturation() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION,
+ pp.getColorTunerSaturation());
+ }
+ if (pp.getColorTunerHue() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE, pp.getColorTunerHue());
+ }
+ if (pp.getColorTunerRedOffset() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET,
+ pp.getColorTunerRedOffset());
+ }
+ if (pp.getColorTunerGreenOffset() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET,
+ pp.getColorTunerGreenOffset());
+ }
+ if (pp.getColorTunerBlueOffset() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET,
+ pp.getColorTunerBlueOffset());
+ }
+ if (pp.getColorTunerRedGain() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN,
+ pp.getColorTunerRedGain());
+ }
+ if (pp.getColorTunerGreenGain() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN,
+ pp.getColorTunerGreenGain());
+ }
+ if (pp.getColorTunerBlueGain() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN,
+ pp.getColorTunerBlueGain());
+ }
+ if (pp.getNoiseReduction() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_NOISE_REDUCTION,
+ pp.getNoiseReduction());
+ }
+ if (pp.getMpegNoiseReduction() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION,
+ pp.getMpegNoiseReduction());
+ }
+ if (pp.getFleshTone() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_FLESH_TONE, pp.getFleshTone());
+ }
+ if (pp.getDeContour() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_DECONTOUR, pp.getDeContour());
+ }
+ if (pp.getDynamicLumaControl() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL,
+ pp.getDynamicLumaControl());
+ }
+ if (pp.getColorTemperature() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TEMPERATURE,
+ pp.getColorTemperature());
+ }
+ if (pp.getColorTemperatureRedGain() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN,
+ pp.getColorTemperatureRedGain());
+ }
+ if (pp.getColorTemperatureGreenGain() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN,
+ pp.getColorTemperatureGreenGain());
+ }
+ if (pp.getColorTemperatureBlueGain() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN,
+ pp.getColorTemperatureBlueGain());
+ }
+ if (pp.getLevelRange() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_LEVEL_RANGE, pp.getLevelRange());
+ }
+ if (pp.getHdmiRgbRange() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_HDMI_RGB_RANGE, pp.getHdmiRgbRange());
+ }
+ if (pp.getColorSpace() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_SPACE, pp.getColorSpace());
+ }
+ if (pp.getPanelInitMaxLuminceNits() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS,
+ pp.getPanelInitMaxLuminceNits());
+ }
+ if (pp.getGamma() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_GAMMA, pp.getGamma());
+ }
+ if (pp.getColorTemperatureRedOffset() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET,
+ pp.getColorTemperatureRedOffset());
+ }
+ if (pp.getColorTemperatureGreenOffset() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET,
+ pp.getColorTemperatureGreenOffset());
+ }
+ if (pp.getColorTemperatureBlueOffset() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET,
+ pp.getColorTemperatureBlueOffset());
+ }
+ if (pp.getLowBlueLight() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_LOW_BLUE_LIGHT, pp.getLowBlueLight());
+ }
+ if (pp.getLdMode() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_LD_MODE, pp.getLdMode());
+ }
+ if (pp.getOsdRedGain() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_OSD_RED_GAIN, pp.getOsdRedGain());
+ }
+ if (pp.getOsdGreenGain() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_OSD_GREEN_GAIN, pp.getOsdGreenGain());
+ }
+ if (pp.getOsdBlueGain() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_OSD_BLUE_GAIN, pp.getOsdBlueGain());
+ }
+ if (pp.getOsdRedOffset() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_OSD_RED_OFFSET, pp.getOsdRedOffset());
+ }
+ if (pp.getOsdGreenOffset() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_OSD_GREEN_OFFSET,
+ pp.getOsdGreenOffset());
+ }
+ if (pp.getOsdBlueOffset() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_OSD_BLUE_OFFSET, pp.getOsdBlueOffset());
+ }
+ if (pp.getOsdHue() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_OSD_HUE, pp.getOsdHue());
+ }
+ if (pp.getOsdSaturation() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_OSD_SATURATION, pp.getOsdSaturation());
+ }
+ if (pp.getOsdContrast() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_OSD_CONTRAST, pp.getOsdContrast());
+ }
+ if (pp.getColorTunerHueRed() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED,
+ pp.getColorTunerHueRed());
+ }
+ if (pp.getColorTunerHueGreen() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN,
+ pp.getColorTunerHueGreen());
+ }
+ if (pp.getColorTunerHueBlue() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE,
+ pp.getColorTunerHueBlue());
+ }
+ if (pp.getColorTunerHueCyan() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN,
+ pp.getColorTunerHueCyan());
+ }
+ if (pp.getColorTunerHueMagenta() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA,
+ pp.getColorTunerHueMagenta());
+ }
+ if (pp.getColorTunerHueYellow() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW,
+ pp.getColorTunerHueYellow());
+ }
+ if (pp.getColorTunerHueFlesh() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH,
+ pp.getColorTunerHueFlesh());
+ }
+ if (pp.getColorTunerSaturationRed() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED,
+ pp.getColorTunerSaturationRed());
+ }
+ if (pp.getColorTunerSaturationGreen() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN,
+ pp.getColorTunerSaturationGreen());
+ }
+ if (pp.getColorTunerSaturationBlue() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE,
+ pp.getColorTunerSaturationBlue());
+ }
+ if (pp.getColorTunerSaturationCyan() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN,
+ pp.getColorTunerSaturationCyan());
+ }
+ if (pp.getColorTunerSaturationMagenta() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA,
+ pp.getColorTunerSaturationMagenta());
+ }
+ if (pp.getColorTunerSaturationYellow() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW,
+ pp.getColorTunerSaturationYellow());
+ }
+ if (pp.getColorTunerSaturationFlesh() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH,
+ pp.getColorTunerSaturationFlesh());
+ }
+ if (pp.getColorTunerLuminanceRed() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED,
+ pp.getColorTunerLuminanceRed());
+ }
+ if (pp.getColorTunerLuminanceGreen() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN,
+ pp.getColorTunerLuminanceGreen());
+ }
+ if (pp.getColorTunerLuminanceBlue() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE,
+ pp.getColorTunerLuminanceBlue());
+ }
+ if (pp.getColorTunerLuminanceCyan() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN,
+ pp.getColorTunerLuminanceCyan());
+ }
+ if (pp.getColorTunerLuminanceMagenta() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA,
+ pp.getColorTunerLuminanceMagenta());
+ }
+ if (pp.getColorTunerLuminanceYellow() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW,
+ pp.getColorTunerLuminanceYellow());
+ }
+ if (pp.getColorTunerLuminanceFlesh() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH,
+ pp.getColorTunerLuminanceFlesh());
+ }
+ if (pp.getPictureQualityEventType() > -1) {
+ bundle.putInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE,
+ pp.getPictureQualityEventType());
+ }
+ if (pp.getFilmMode()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_FILM_MODE, true);
+ }
+ if (pp.getBlueStretch()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_BLUE_STRETCH, true);
+ }
+ if (pp.getColorTune()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_COLOR_TUNE, true);
+ }
+ if (pp.getGlobeDimming()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_GLOBAL_DIMMING, true);
+ }
+ if (pp.getAutoPictureQualityEnabled()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED, true);
+ }
+ if (pp.getAutoSuperResolutionEnabled()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED, true);
+ }
+ if (pp.getGamutMapping()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_GAMUT_MAPPING, true);
+ }
+ if (pp.getPcMode()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_PC_MODE, true);
+ }
+ if (pp.getLowLatency()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_LOW_LATENCY, true);
+ }
+ if (pp.getVrr()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_VRR, true);
+ }
+ if (pp.getCvrr()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_CVRR, true);
+ }
+ if (pp.getPanelInitMaxLuminceValid()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID, true);
+ }
+ if (pp.getColorTunerSwitch()) {
+ bundle.putBoolean(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH, true);
+ }
+ if (pp.getElevenPointRed() != null) {
+ bundle.putIntArray(PictureQuality.PARAMETER_ELEVEN_POINT_RED,
+ pp.getElevenPointRed());
+ }
+ if (pp.getElevenPointBlue() != null) {
+ bundle.putIntArray(PictureQuality.PARAMETER_ELEVEN_POINT_RED,
+ pp.getElevenPointBlue());
+ }
+ if (pp.getElevenPointGreen() != null) {
+ bundle.putIntArray(PictureQuality.PARAMETER_ELEVEN_POINT_RED,
+ pp.getElevenPointGreen());
+ }
+ }
+ return bundle;
+ }
+
+ /**
+ * Convert PersistableBundle to PictureParameter List.
+ */
+ public static PictureParameter[] convertPersistableBundleToPictureParameterList(
+ PersistableBundle params) {
+ List<PictureParameter> pictureParams = new ArrayList<>();
+ if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) {
+ pictureParams.add(PictureParameter.brightness(params.getLong(
+ PictureQuality.PARAMETER_BRIGHTNESS)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_CONTRAST)) {
+ pictureParams.add(PictureParameter.contrast(params.getInt(
+ PictureQuality.PARAMETER_CONTRAST)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_SHARPNESS)) {
+ pictureParams.add(PictureParameter.sharpness(params.getInt(
+ PictureQuality.PARAMETER_SHARPNESS)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_SATURATION)) {
+ pictureParams.add(PictureParameter.saturation(params.getInt(
+ PictureQuality.PARAMETER_SATURATION)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_HUE)) {
+ pictureParams.add(PictureParameter.hue(params.getInt(
+ PictureQuality.PARAMETER_HUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)) {
+ pictureParams.add(PictureParameter.colorTunerBrightness(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)) {
+ pictureParams.add(PictureParameter.colorTunerSaturation(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE)) {
+ pictureParams.add(PictureParameter.colorTunerHue(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTunerRedOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTunerGreenOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTunerBlueOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
+ pictureParams.add(PictureParameter.colorTunerRedGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
+ pictureParams.add(PictureParameter.colorTunerGreenGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
+ pictureParams.add(PictureParameter.colorTunerBlueGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_NOISE_REDUCTION)) {
+ pictureParams.add(PictureParameter.noiseReduction(
+ (byte) params.getInt(PictureQuality.PARAMETER_NOISE_REDUCTION)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) {
+ pictureParams.add(PictureParameter.mpegNoiseReduction(
+ (byte) params.getInt(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_FLESH_TONE)) {
+ pictureParams.add(PictureParameter.fleshTone(
+ (byte) params.getInt(PictureQuality.PARAMETER_FLESH_TONE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_DECONTOUR)) {
+ pictureParams.add(PictureParameter.deContour(
+ (byte) params.getInt(PictureQuality.PARAMETER_DECONTOUR)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) {
+ pictureParams.add(PictureParameter.dynamicLumaControl(
+ (byte) params.getInt(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_FILM_MODE)) {
+ pictureParams.add(PictureParameter.filmMode(params.getBoolean(
+ PictureQuality.PARAMETER_FILM_MODE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_BLUE_STRETCH)) {
+ pictureParams.add(PictureParameter.blueStretch(params.getBoolean(
+ PictureQuality.PARAMETER_BLUE_STRETCH)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNE)) {
+ pictureParams.add(PictureParameter.colorTune(params.getBoolean(
+ PictureQuality.PARAMETER_COLOR_TUNE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) {
+ pictureParams.add(PictureParameter.colorTemperature(
+ (byte) params.getInt(
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_GLOBAL_DIMMING)) {
+ pictureParams.add(PictureParameter.globeDimming(params.getBoolean(
+ PictureQuality.PARAMETER_GLOBAL_DIMMING)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)) {
+ pictureParams.add(PictureParameter.autoPictureQualityEnabled(params.getBoolean(
+ PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) {
+ pictureParams.add(PictureParameter.autoSuperResolutionEnabled(params.getBoolean(
+ PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
+ pictureParams.add(PictureParameter.colorTemperatureRedGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
+ pictureParams.add(PictureParameter.colorTemperatureGreenGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
+ pictureParams.add(PictureParameter.colorTemperatureBlueGain(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_LEVEL_RANGE)) {
+ pictureParams.add(PictureParameter.levelRange(
+ (byte) params.getInt(PictureQuality.PARAMETER_LEVEL_RANGE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_GAMUT_MAPPING)) {
+ pictureParams.add(PictureParameter.gamutMapping(params.getBoolean(
+ PictureQuality.PARAMETER_GAMUT_MAPPING)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_PC_MODE)) {
+ pictureParams.add(PictureParameter.pcMode(params.getBoolean(
+ PictureQuality.PARAMETER_PC_MODE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_LOW_LATENCY)) {
+ pictureParams.add(PictureParameter.lowLatency(params.getBoolean(
+ PictureQuality.PARAMETER_LOW_LATENCY)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_VRR)) {
+ pictureParams.add(PictureParameter.vrr(params.getBoolean(
+ PictureQuality.PARAMETER_VRR)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_CVRR)) {
+ pictureParams.add(PictureParameter.cvrr(params.getBoolean(
+ PictureQuality.PARAMETER_CVRR)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) {
+ pictureParams.add(PictureParameter.hdmiRgbRange(
+ (byte) params.getInt(PictureQuality.PARAMETER_HDMI_RGB_RANGE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_SPACE)) {
+ pictureParams.add(PictureParameter.colorSpace(
+ (byte) params.getInt(PictureQuality.PARAMETER_COLOR_SPACE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)) {
+ pictureParams.add(PictureParameter.panelInitMaxLuminceNits(
+ params.getInt(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)) {
+ pictureParams.add(PictureParameter.panelInitMaxLuminceValid(
+ params.getBoolean(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_GAMMA)) {
+ pictureParams.add(PictureParameter.gamma(
+ (byte) params.getInt(PictureQuality.PARAMETER_GAMMA)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTemperatureRedOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTemperatureGreenOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)) {
+ pictureParams.add(PictureParameter.colorTemperatureBlueOffset(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_RED)) {
+ pictureParams.add(PictureParameter.elevenPointRed(params.getIntArray(
+ PictureQuality.PARAMETER_ELEVEN_POINT_RED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)) {
+ pictureParams.add(PictureParameter.elevenPointGreen(params.getIntArray(
+ PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)) {
+ pictureParams.add(PictureParameter.elevenPointBlue(params.getIntArray(
+ PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) {
+ pictureParams.add(PictureParameter.lowBlueLight(
+ (byte) params.getInt(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_LD_MODE)) {
+ pictureParams.add(PictureParameter.LdMode(
+ (byte) params.getInt(PictureQuality.PARAMETER_LD_MODE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_GAIN)) {
+ pictureParams.add(PictureParameter.osdRedGain(params.getInt(
+ PictureQuality.PARAMETER_OSD_RED_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_GAIN)) {
+ pictureParams.add(PictureParameter.osdGreenGain(params.getInt(
+ PictureQuality.PARAMETER_OSD_GREEN_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_GAIN)) {
+ pictureParams.add(PictureParameter.osdBlueGain(params.getInt(
+ PictureQuality.PARAMETER_OSD_BLUE_GAIN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_OFFSET)) {
+ pictureParams.add(PictureParameter.osdRedOffset(params.getInt(
+ PictureQuality.PARAMETER_OSD_RED_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_OFFSET)) {
+ pictureParams.add(PictureParameter.osdGreenOffset(params.getInt(
+ PictureQuality.PARAMETER_OSD_GREEN_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_OFFSET)) {
+ pictureParams.add(PictureParameter.osdBlueOffset(params.getInt(
+ PictureQuality.PARAMETER_OSD_BLUE_OFFSET)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_HUE)) {
+ pictureParams.add(PictureParameter.osdHue(params.getInt(
+ PictureQuality.PARAMETER_OSD_HUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_SATURATION)) {
+ pictureParams.add(PictureParameter.osdSaturation(params.getInt(
+ PictureQuality.PARAMETER_OSD_SATURATION)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_OSD_CONTRAST)) {
+ pictureParams.add(PictureParameter.osdContrast(params.getInt(
+ PictureQuality.PARAMETER_OSD_CONTRAST)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)) {
+ pictureParams.add(PictureParameter.colorTunerSwitch(params.getBoolean(
+ PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)) {
+ pictureParams.add(PictureParameter.colorTunerHueRed(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)) {
+ pictureParams.add(PictureParameter.colorTunerHueGreen(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)) {
+ pictureParams.add(PictureParameter.colorTunerHueBlue(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)) {
+ pictureParams.add(PictureParameter.colorTunerHueCyan(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)) {
+ pictureParams.add(PictureParameter.colorTunerHueMagenta(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)) {
+ pictureParams.add(PictureParameter.colorTunerHueYellow(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)) {
+ pictureParams.add(PictureParameter.colorTunerHueFlesh(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationRed(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationGreen(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationBlue(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationCyan(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationMagenta(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationYellow(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)) {
+ pictureParams.add(PictureParameter.colorTunerSaturationFlesh(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceRed(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceGreen(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceBlue(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceCyan(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceMagenta(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceYellow(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)) {
+ pictureParams.add(PictureParameter.colorTunerLuminanceFlesh(params.getInt(
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)));
+ }
+ if (params.containsKey(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)) {
+ pictureParams.add(PictureParameter.pictureQualityEventType(
+ (byte) params.getInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)));
+ }
+ return (PictureParameter[]) pictureParams.toArray();
+ }
+
+ /**
+ * Convert SoundParameter List to PersistableBundle.
+ */
+ public static PersistableBundle convertSoundParameterListToPersistableBundle(
+ SoundParameter[] parameters) {
+ if (parameters == null) {
+ return null;
+ }
+
+ PersistableBundle bundle = new PersistableBundle();
+ for (SoundParameter sp: parameters) {
+ if (sp.getSurroundSoundEnabled()) {
+ bundle.putBoolean(SoundQuality.PARAMETER_SURROUND_SOUND, true);
+ }
+ if (sp.getSpeakersEnabled()) {
+ bundle.putBoolean(SoundQuality.PARAMETER_SPEAKERS, true);
+ }
+ if (sp.getAutoVolumeControl()) {
+ bundle.putBoolean(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL, true);
+ }
+ if (sp.getDtsDrc()) {
+ bundle.putBoolean(SoundQuality.PARAMETER_DTS_DRC, true);
+ }
+ if (sp.getSurroundSoundEnabled()) {
+ bundle.putBoolean(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS, true);
+ }
+ if (sp.getEnhancedAudioReturnChannelEnabled()) {
+ bundle.putBoolean(SoundQuality.PARAMETER_EARC, true);
+ }
+ if (sp.getBalance() > -1) {
+ bundle.putInt(SoundQuality.PARAMETER_BALANCE, sp.getBalance());
+ }
+ if (sp.getBass() > -1) {
+ bundle.putInt(SoundQuality.PARAMETER_BASS, sp.getBass());
+ }
+ if (sp.getTreble() > -1) {
+ bundle.putInt(SoundQuality.PARAMETER_TREBLE, sp.getTreble());
+ }
+ if (sp.getSpeakersDelayMs() > -1) {
+ bundle.putInt(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS,
+ sp.getSpeakersDelayMs());
+ }
+ if (sp.getDownmixMode() > -1) {
+ bundle.putInt(SoundQuality.PARAMETER_DOWN_MIX_MODE, sp.getDownmixMode());
+ }
+ if (sp.getSoundStyle() > -1) {
+ bundle.putInt(SoundQuality.PARAMETER_SOUND_STYLE, sp.getSoundStyle());
+ }
+ if (sp.getDigitalOutput() > -1) {
+ bundle.putInt(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE,
+ sp.getDigitalOutput());
+ }
+ if (sp.getDolbyDialogueEnhancer() > -1) {
+ bundle.putInt(SoundQuality.PARAMETER_DIALOGUE_ENHANCER,
+ sp.getDolbyDialogueEnhancer());
+ }
+ if (sp.getDtsVirtualX().tbHdx) {
+ bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TBHDX, true);
+ }
+ if (sp.getDtsVirtualX().limiter) {
+ bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_LIMITER, true);
+ }
+ if (sp.getDtsVirtualX().truSurroundX) {
+ bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_SURROUND_X, true);
+ }
+ if (sp.getDtsVirtualX().truVolumeHd) {
+ bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_VOLUME_HD, true);
+ }
+ if (sp.getDtsVirtualX().dialogClarity) {
+ bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_DIALOG_CLARITY, true);
+ }
+ if (sp.getDtsVirtualX().definition) {
+ bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_DEFINITION, true);
+ }
+ if (sp.getDtsVirtualX().height) {
+ bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_HEIGHT, true);
+ }
+ if (sp.getDolbyAudioProcessing().soundMode > -1) {
+ bundle.putInt(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SOUND_MODE,
+ sp.getDolbyAudioProcessing().soundMode);
+ }
+ if (sp.getDolbyAudioProcessing().volumeLeveler) {
+ bundle.putBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_VOLUME_LEVELER,
+ true);
+ }
+ if (sp.getDolbyAudioProcessing().surroundVirtualizer) {
+ bundle.putBoolean(
+ SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SURROUND_VIRTUALIZER,
+ true);
+ }
+ if (sp.getDolbyAudioProcessing().dolbyAtmos) {
+ bundle.putBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_DOLBY_ATMOS,
+ true);
+ }
+ }
+ return bundle;
+ }
+ /**
+ * Convert PersistableBundle to SoundParameter List.
+ */
+ public static SoundParameter[] convertPersistableBundleToSoundParameterList(
+ PersistableBundle params) {
+ //TODO: set EqualizerDetail
+ List<SoundParameter> soundParams = new ArrayList<>();
+ if (params.containsKey(SoundQuality.PARAMETER_BALANCE)) {
+ soundParams.add(SoundParameter.balance(params.getInt(
+ SoundQuality.PARAMETER_BALANCE)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_BASS)) {
+ soundParams.add(SoundParameter.bass(params.getInt(SoundQuality.PARAMETER_BASS)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_TREBLE)) {
+ soundParams.add(SoundParameter.treble(params.getInt(
+ SoundQuality.PARAMETER_TREBLE)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_SURROUND_SOUND)) {
+ soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean(
+ SoundQuality.PARAMETER_SURROUND_SOUND)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS)) {
+ soundParams.add(SoundParameter.speakersEnabled(params.getBoolean(
+ SoundQuality.PARAMETER_SPEAKERS)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)) {
+ soundParams.add(SoundParameter.speakersDelayMs(params.getInt(
+ SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)) {
+ soundParams.add(SoundParameter.autoVolumeControl(params.getBoolean(
+ SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_DTS_DRC)) {
+ soundParams.add(SoundParameter.dtsDrc(params.getBoolean(
+ SoundQuality.PARAMETER_DTS_DRC)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)) {
+ soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean(
+ SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_EARC)) {
+ soundParams.add(SoundParameter.enhancedAudioReturnChannelEnabled(params.getBoolean(
+ SoundQuality.PARAMETER_EARC)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_DOWN_MIX_MODE)) {
+ soundParams.add(SoundParameter.downmixMode((byte) params.getInt(
+ SoundQuality.PARAMETER_DOWN_MIX_MODE)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_SOUND_STYLE)) {
+ soundParams.add(SoundParameter.soundStyle((byte) params.getInt(
+ SoundQuality.PARAMETER_SOUND_STYLE)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)) {
+ soundParams.add(SoundParameter.digitalOutput((byte) params.getInt(
+ SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)));
+ }
+ if (params.containsKey(SoundQuality.PARAMETER_DIALOGUE_ENHANCER)) {
+ soundParams.add(SoundParameter.dolbyDialogueEnhancer((byte) params.getInt(
+ SoundQuality.PARAMETER_DIALOGUE_ENHANCER)));
+ }
+
+ DolbyAudioProcessing dab = new DolbyAudioProcessing();
+ dab.soundMode =
+ (byte) params.getInt(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SOUND_MODE);
+ dab.volumeLeveler =
+ params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_VOLUME_LEVELER);
+ dab.surroundVirtualizer = params.getBoolean(
+ SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SURROUND_VIRTUALIZER);
+ dab.dolbyAtmos =
+ params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_DOLBY_ATMOS);
+ soundParams.add(SoundParameter.dolbyAudioProcessing(dab));
+
+ DtsVirtualX dts = new DtsVirtualX();
+ dts.tbHdx = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TBHDX);
+ dts.limiter = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_LIMITER);
+ dts.truSurroundX = params.getBoolean(
+ SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_SURROUND_X);
+ dts.truVolumeHd = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_VOLUME_HD);
+ dts.dialogClarity = params.getBoolean(
+ SoundQuality.PARAMETER_DTS_VIRTUAL_X_DIALOG_CLARITY);
+ dts.definition = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_DEFINITION);
+ dts.height = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_HEIGHT);
+ soundParams.add(SoundParameter.dtsVirtualX(dts));
+
+ return (SoundParameter[]) soundParams.toArray();
+ }
+
+ private static String persistableBundleToJson(PersistableBundle bundle) {
+ JSONObject json = new JSONObject();
+ for (String key : bundle.keySet()) {
+ Object value = bundle.get(key);
+ try {
+ if (value instanceof String) {
+ json.put(key, bundle.getString(key));
+ } else if (value instanceof Integer) {
+ json.put(key, bundle.getInt(key));
+ } else if (value instanceof Long) {
+ json.put(key, bundle.getLong(key));
+ } else if (value instanceof Boolean) {
+ json.put(key, bundle.getBoolean(key));
+ } else if (value instanceof Double) {
+ json.put(key, bundle.getDouble(key));
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "Unable to serialize ", e);
+ }
+ }
+ return json.toString();
+ }
+
+ private static PersistableBundle jsonToPersistableBundle(String jsonString) {
+ PersistableBundle bundle = new PersistableBundle();
+ if (jsonString != null) {
+ JSONObject jsonObject = null;
+ try {
+ jsonObject = new JSONObject(jsonString);
+
+ Iterator<String> keys = jsonObject.keys();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Object value = jsonObject.get(key);
+
+ if (value instanceof String) {
+ bundle.putString(key, (String) value);
+ } else if (value instanceof Integer) {
+ bundle.putInt(key, (Integer) value);
+ } else if (value instanceof Boolean) {
+ bundle.putBoolean(key, (Boolean) value);
+ } else if (value instanceof Double) {
+ bundle.putDouble(key, (Double) value);
+ } else if (value instanceof Long) {
+ bundle.putLong(key, (Long) value);
+ }
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return bundle;
+ }
+
+ /**
+ * Populates the given map with the ID and generated UUID.
+ */
+ public static void populateTempIdMap(BiMap<Long, String> map, Long id) {
+ if (id != null && map.getValue(id) == null) {
+ String uuid;
+ int attempts = 0;
+ while (attempts < MAX_UUID_GENERATION_ATTEMPTS) {
+ uuid = UUID.randomUUID().toString();
+ if (map.getKey(uuid) == null) {
+ map.put(id, uuid);
+ return;
+ }
+ attempts++;
+ }
+ }
+ }
+
+ /**
+ * Get Content Values.
+ */
+ public static ContentValues getContentValues(Long dbId, Integer profileType, String name,
+ String packageName, String inputId, PersistableBundle params) {
+ ContentValues values = new ContentValues();
+ if (dbId != null) {
+ values.put(BaseParameters.PARAMETER_ID, dbId);
+ }
+ if (profileType != null) {
+ values.put(BaseParameters.PARAMETER_TYPE, profileType);
+ }
+ if (name != null) {
+ values.put(BaseParameters.PARAMETER_NAME, name);
+ }
+ if (packageName != null) {
+ values.put(BaseParameters.PARAMETER_PACKAGE, packageName);
+ }
+ if (inputId != null) {
+ values.put(BaseParameters.PARAMETER_INPUT_ID, inputId);
+ }
+ if (params != null) {
+ values.put(SETTINGS, persistableBundleToJson(params));
+ }
+ return values;
+ }
+
+ /**
+ * Get Media Profile Columns.
+ */
+ public static String[] getMediaProfileColumns(boolean includeParams) {
+ ArrayList<String> columns = new ArrayList<>(Arrays.asList(
+ BaseParameters.PARAMETER_ID,
+ BaseParameters.PARAMETER_TYPE,
+ BaseParameters.PARAMETER_NAME,
+ BaseParameters.PARAMETER_INPUT_ID,
+ BaseParameters.PARAMETER_PACKAGE)
+ );
+ if (includeParams) {
+ columns.add(SETTINGS);
+ }
+ return columns.toArray(new String[0]);
+ }
+
+ /**
+ * Convert cursor to Picture Profile with temporary UUID.
+ */
+ public static PictureProfile convertCursorToPictureProfileWithTempId(Cursor cursor,
+ BiMap<Long, String> map) {
+ return new PictureProfile(
+ getTempId(map, cursor),
+ getType(cursor),
+ getName(cursor),
+ getInputId(cursor),
+ getPackageName(cursor),
+ jsonToPersistableBundle(getSettingsString(cursor)),
+ PictureProfileHandle.NONE
+ );
+ }
+
+ /**
+ * Convert cursor to Sound Profile with temporary UUID.
+ */
+ public static SoundProfile convertCursorToSoundProfileWithTempId(Cursor cursor, BiMap<Long,
+ String> map) {
+ return new SoundProfile(
+ getTempId(map, cursor),
+ getType(cursor),
+ getName(cursor),
+ getInputId(cursor),
+ getPackageName(cursor),
+ jsonToPersistableBundle(getSettingsString(cursor)),
+ SoundProfileHandle.NONE
+ );
+ }
+
+ /**
+ * Convert parameter to byte array.
+ */
+ public static byte[] convertParameterToByteArray(List<String> names) {
+ /**
+ * TODO Add following to ParameterName & add conversion here.
+ * - PICTURE_QUALITY_EVENT_TYPE
+ * - PANEL_INIT_MAX_LUMINCE_NITS
+ */
+
+ HashSet<String> nameMap = new HashSet<>(names);
+
+ List<Byte> bytes = new ArrayList<>();
+ // Picture Quality parameters
+ if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+ bytes.add(ParameterName.BRIGHTNESS);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+ bytes.add(ParameterName.BRIGHTNESS);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_CONTRAST)) {
+ bytes.add(ParameterName.CONTRAST);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_SHARPNESS)) {
+ bytes.add(ParameterName.SHARPNESS);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_SATURATION)) {
+ bytes.add(ParameterName.SATURATION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_HUE)) {
+ bytes.add(ParameterName.HUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+ bytes.add(ParameterName.BRIGHTNESS);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)) {
+ bytes.add(ParameterName.COLOR_TUNER_BRIGHTNESS);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_SATURATION)) {
+ bytes.add(ParameterName.SATURATION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_HUE)) {
+ bytes.add(ParameterName.HUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TUNER_RED_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TUNER_GREEN_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TUNER_BLUE_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
+ bytes.add(ParameterName.COLOR_TUNER_RED_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
+ bytes.add(ParameterName.COLOR_TUNER_GREEN_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
+ bytes.add(ParameterName.COLOR_TUNER_BLUE_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_NOISE_REDUCTION)) {
+ bytes.add(ParameterName.NOISE_REDUCTION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) {
+ bytes.add(ParameterName.MPEG_NOISE_REDUCTION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_FLESH_TONE)) {
+ bytes.add(ParameterName.FLASH_TONE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_DECONTOUR)) {
+ bytes.add(ParameterName.DE_CONTOUR);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) {
+ bytes.add(ParameterName.DYNAMIC_LUMA_CONTROL);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_FILM_MODE)) {
+ bytes.add(ParameterName.FILM_MODE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_BLUE_STRETCH)) {
+ bytes.add(ParameterName.BLUE_STRETCH);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNE)) {
+ bytes.add(ParameterName.COLOR_TUNE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) {
+ bytes.add(ParameterName.COLOR_TEMPERATURE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_GLOBAL_DIMMING)) {
+ bytes.add(ParameterName.GLOBE_DIMMING);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)) {
+ bytes.add(ParameterName.AUTO_PICTUREQUALITY_ENABLED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) {
+ bytes.add(ParameterName.AUTO_SUPER_RESOLUTION_ENABLED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_LEVEL_RANGE)) {
+ bytes.add(ParameterName.LEVEL_RANGE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_GAMUT_MAPPING)) {
+ bytes.add(ParameterName.GAMUT_MAPPING);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_PC_MODE)) {
+ bytes.add(ParameterName.PC_MODE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_LOW_LATENCY)) {
+ bytes.add(ParameterName.LOW_LATENCY);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_VRR)) {
+ bytes.add(ParameterName.VRR);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_CVRR)) {
+ bytes.add(ParameterName.CVRR);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) {
+ bytes.add(ParameterName.HDMI_RGB_RANGE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_SPACE)) {
+ bytes.add(ParameterName.COLOR_SPACE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)) {
+ bytes.add(ParameterName.PANEL_INIT_MAX_LUMINCE_VALID);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_GAMMA)) {
+ bytes.add(ParameterName.GAMMA);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TEMPERATURE_RED_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TEMPERATURE_GREEN_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TEMPERATURE_BLUE_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_RED)) {
+ bytes.add(ParameterName.ELEVEN_POINT_RED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)) {
+ bytes.add(ParameterName.ELEVEN_POINT_GREEN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)) {
+ bytes.add(ParameterName.ELEVEN_POINT_BLUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) {
+ bytes.add(ParameterName.LOW_BLUE_LIGHT);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_LD_MODE)) {
+ bytes.add(ParameterName.LD_MODE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_RED_GAIN)) {
+ bytes.add(ParameterName.OSD_RED_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_GREEN_GAIN)) {
+ bytes.add(ParameterName.OSD_GREEN_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_BLUE_GAIN)) {
+ bytes.add(ParameterName.OSD_BLUE_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_RED_OFFSET)) {
+ bytes.add(ParameterName.OSD_RED_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_GREEN_OFFSET)) {
+ bytes.add(ParameterName.OSD_GREEN_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_BLUE_OFFSET)) {
+ bytes.add(ParameterName.OSD_BLUE_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_HUE)) {
+ bytes.add(ParameterName.OSD_HUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_SATURATION)) {
+ bytes.add(ParameterName.OSD_SATURATION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_CONTRAST)) {
+ bytes.add(ParameterName.OSD_CONTRAST);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)) {
+ bytes.add(ParameterName.COLOR_TUNER_SWITCH);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_RED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_GREEN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_BLUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_CYAN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_MAGENTA);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_YELLOW);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_FLESH);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_RED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_GREEN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_BLUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_CYAN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_MAGENTA);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_YELLOW);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_FLESH);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_RED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_GREEN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_BLUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_CYAN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_MAGENTA);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_YELLOW);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_FLESH);
+ }
+
+ // Sound Quality parameters
+ if (nameMap.contains(SoundQuality.PARAMETER_BALANCE)) {
+ bytes.add(ParameterName.BALANCE);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_BASS)) {
+ bytes.add(ParameterName.BASS);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_TREBLE)) {
+ bytes.add(ParameterName.TREBLE);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_SURROUND_SOUND)) {
+ bytes.add(ParameterName.SURROUND_SOUND_ENABLED);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_EQUALIZER_DETAIL)) {
+ bytes.add(ParameterName.EQUALIZER_DETAIL);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_SPEAKERS)) {
+ bytes.add(ParameterName.SPEAKERS_ENABLED);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)) {
+ bytes.add(ParameterName.SPEAKERS_DELAY_MS);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_EARC)) {
+ bytes.add(ParameterName.ENHANCED_AUDIO_RETURN_CHANNEL_ENABLED);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)) {
+ bytes.add(ParameterName.AUTO_VOLUME_CONTROL);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DOWN_MIX_MODE)) {
+ bytes.add(ParameterName.DOWNMIX_MODE);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DTS_DRC)) {
+ bytes.add(ParameterName.DTS_DRC);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING)) {
+ bytes.add(ParameterName.DOLBY_AUDIO_PROCESSING);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DIALOGUE_ENHANCER)) {
+ bytes.add(ParameterName.DOLBY_DIALOGUE_ENHANCER);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DTS_VIRTUAL_X)) {
+ bytes.add(ParameterName.DTS_VIRTUAL_X);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)) {
+ bytes.add(ParameterName.DIGITAL_OUTPUT_DELAY_MS);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)) {
+ bytes.add(ParameterName.DIGITAL_OUTPUT);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_SOUND_STYLE)) {
+ bytes.add(ParameterName.SOUND_STYLE);
+ }
+
+ byte[] byteArray = new byte[bytes.size()];
+ for (int i = 0; i < bytes.size(); i++) {
+ byteArray[i] = bytes.get(i);
+ }
+ return byteArray;
+ }
+
+ /**
+ * Get Parameter Name based on byte.
+ */
+ public static String getParameterName(byte pn) {
+ Map<Byte, String> parameterNameMap = new HashMap<>();
+ parameterNameMap.put(ParameterName.BRIGHTNESS, PictureQuality.PARAMETER_BRIGHTNESS);
+ parameterNameMap.put(ParameterName.CONTRAST, PictureQuality.PARAMETER_CONTRAST);
+ parameterNameMap.put(ParameterName.SHARPNESS, PictureQuality.PARAMETER_SHARPNESS);
+ parameterNameMap.put(ParameterName.SATURATION, PictureQuality.PARAMETER_SATURATION);
+ parameterNameMap.put(ParameterName.HUE, PictureQuality.PARAMETER_HUE);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_BRIGHTNESS,
+ PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_RED_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_GREEN_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_BLUE_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_RED_GAIN,
+ PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_GREEN_GAIN,
+ PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_BLUE_GAIN,
+ PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN);
+ parameterNameMap.put(ParameterName.NOISE_REDUCTION,
+ PictureQuality.PARAMETER_NOISE_REDUCTION);
+ parameterNameMap.put(ParameterName.MPEG_NOISE_REDUCTION,
+ PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION);
+ parameterNameMap.put(ParameterName.FLASH_TONE, PictureQuality.PARAMETER_FLESH_TONE);
+ parameterNameMap.put(ParameterName.DE_CONTOUR, PictureQuality.PARAMETER_DECONTOUR);
+ parameterNameMap.put(ParameterName.DYNAMIC_LUMA_CONTROL,
+ PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL);
+ parameterNameMap.put(ParameterName.FILM_MODE,
+ PictureQuality.PARAMETER_FILM_MODE);
+ parameterNameMap.put(ParameterName.BLACK_STRETCH,
+ PictureQuality.PARAMETER_BLACK_STRETCH);
+ parameterNameMap.put(ParameterName.BLUE_STRETCH,
+ PictureQuality.PARAMETER_BLUE_STRETCH);
+ parameterNameMap.put(ParameterName.COLOR_TUNE,
+ PictureQuality.PARAMETER_COLOR_TUNE);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE);
+ parameterNameMap.put(ParameterName.GLOBE_DIMMING,
+ PictureQuality.PARAMETER_GLOBAL_DIMMING);
+ parameterNameMap.put(ParameterName.AUTO_PICTUREQUALITY_ENABLED,
+ PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED);
+ parameterNameMap.put(ParameterName.AUTO_SUPER_RESOLUTION_ENABLED,
+ PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED);
+ parameterNameMap.put(ParameterName.LEVEL_RANGE, PictureQuality.PARAMETER_LEVEL_RANGE);
+ parameterNameMap.put(ParameterName.GAMUT_MAPPING,
+ PictureQuality.PARAMETER_GAMUT_MAPPING);
+ parameterNameMap.put(ParameterName.PC_MODE, PictureQuality.PARAMETER_PC_MODE);
+ parameterNameMap.put(ParameterName.LOW_LATENCY, PictureQuality.PARAMETER_LOW_LATENCY);
+ parameterNameMap.put(ParameterName.VRR, PictureQuality.PARAMETER_VRR);
+ parameterNameMap.put(ParameterName.CVRR, PictureQuality.PARAMETER_CVRR);
+ parameterNameMap.put(ParameterName.HDMI_RGB_RANGE,
+ PictureQuality.PARAMETER_HDMI_RGB_RANGE);
+ parameterNameMap.put(ParameterName.COLOR_SPACE, PictureQuality.PARAMETER_COLOR_SPACE);
+ parameterNameMap.put(ParameterName.PANEL_INIT_MAX_LUMINCE_VALID,
+ PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID);
+ parameterNameMap.put(ParameterName.GAMMA, PictureQuality.PARAMETER_GAMMA);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_RED_GAIN,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_GAIN);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_GREEN_GAIN,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_GAIN);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_BLUE_GAIN,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_GAIN);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_RED_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_GREEN_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_BLUE_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET);
+ parameterNameMap.put(ParameterName.ELEVEN_POINT_RED,
+ PictureQuality.PARAMETER_ELEVEN_POINT_RED);
+ parameterNameMap.put(ParameterName.ELEVEN_POINT_GREEN,
+ PictureQuality.PARAMETER_ELEVEN_POINT_GREEN);
+ parameterNameMap.put(ParameterName.ELEVEN_POINT_BLUE,
+ PictureQuality.PARAMETER_ELEVEN_POINT_BLUE);
+ parameterNameMap.put(ParameterName.LOW_BLUE_LIGHT,
+ PictureQuality.PARAMETER_LOW_BLUE_LIGHT);
+ parameterNameMap.put(ParameterName.LD_MODE, PictureQuality.PARAMETER_LD_MODE);
+ parameterNameMap.put(ParameterName.OSD_RED_GAIN, PictureQuality.PARAMETER_OSD_RED_GAIN);
+ parameterNameMap.put(ParameterName.OSD_GREEN_GAIN,
+ PictureQuality.PARAMETER_OSD_GREEN_GAIN);
+ parameterNameMap.put(ParameterName.OSD_BLUE_GAIN,
+ PictureQuality.PARAMETER_OSD_BLUE_GAIN);
+ parameterNameMap.put(ParameterName.OSD_RED_OFFSET,
+ PictureQuality.PARAMETER_OSD_RED_OFFSET);
+ parameterNameMap.put(ParameterName.OSD_GREEN_OFFSET,
+ PictureQuality.PARAMETER_OSD_GREEN_OFFSET);
+ parameterNameMap.put(ParameterName.OSD_BLUE_OFFSET,
+ PictureQuality.PARAMETER_OSD_BLUE_OFFSET);
+ parameterNameMap.put(ParameterName.OSD_HUE, PictureQuality.PARAMETER_OSD_HUE);
+ parameterNameMap.put(ParameterName.OSD_SATURATION,
+ PictureQuality.PARAMETER_OSD_SATURATION);
+ parameterNameMap.put(ParameterName.OSD_CONTRAST,
+ PictureQuality.PARAMETER_OSD_CONTRAST);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SWITCH,
+ PictureQuality.PARAMETER_COLOR_TUNER_SWITCH);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_RED,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_GREEN,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_BLUE,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_CYAN,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_MAGENTA,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_YELLOW,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_FLESH,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_RED,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_GREEN,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_BLUE,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_CYAN,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_MAGENTA,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_YELLOW,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_FLESH,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_RED,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_GREEN,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_BLUE,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_CYAN,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_MAGENTA,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_YELLOW,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_FLESH,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH);
+ parameterNameMap.put(ParameterName.BALANCE, SoundQuality.PARAMETER_BALANCE);
+ parameterNameMap.put(ParameterName.BASS, SoundQuality.PARAMETER_BASS);
+ parameterNameMap.put(ParameterName.TREBLE, SoundQuality.PARAMETER_TREBLE);
+ parameterNameMap.put(ParameterName.SURROUND_SOUND_ENABLED,
+ SoundQuality.PARAMETER_SURROUND_SOUND);
+ parameterNameMap.put(ParameterName.EQUALIZER_DETAIL,
+ SoundQuality.PARAMETER_EQUALIZER_DETAIL);
+ parameterNameMap.put(ParameterName.SPEAKERS_ENABLED, SoundQuality.PARAMETER_SPEAKERS);
+ parameterNameMap.put(ParameterName.SPEAKERS_DELAY_MS,
+ SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS);
+ parameterNameMap.put(ParameterName.ENHANCED_AUDIO_RETURN_CHANNEL_ENABLED,
+ SoundQuality.PARAMETER_EARC);
+ parameterNameMap.put(ParameterName.AUTO_VOLUME_CONTROL,
+ SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL);
+ parameterNameMap.put(ParameterName.DOWNMIX_MODE, SoundQuality.PARAMETER_DOWN_MIX_MODE);
+ parameterNameMap.put(ParameterName.DTS_DRC, SoundQuality.PARAMETER_DTS_DRC);
+ parameterNameMap.put(ParameterName.DOLBY_AUDIO_PROCESSING,
+ SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING);
+ parameterNameMap.put(ParameterName.DOLBY_DIALOGUE_ENHANCER,
+ SoundQuality.PARAMETER_DIALOGUE_ENHANCER);
+ parameterNameMap.put(ParameterName.DTS_VIRTUAL_X,
+ SoundQuality.PARAMETER_DTS_VIRTUAL_X);
+ parameterNameMap.put(ParameterName.DIGITAL_OUTPUT,
+ SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE);
+ parameterNameMap.put(ParameterName.DIGITAL_OUTPUT_DELAY_MS,
+ SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS);
+ parameterNameMap.put(ParameterName.SOUND_STYLE, SoundQuality.PARAMETER_SOUND_STYLE);
+
+ return parameterNameMap.get(pn);
+ }
+
+ private static String getTempId(BiMap<Long, String> map, Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_ID);
+ Long dbId = colIndex != -1 ? cursor.getLong(colIndex) : null;
+ populateTempIdMap(map, dbId);
+ return map.getValue(dbId);
+ }
+
+ private static int getType(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_TYPE);
+ return colIndex != -1 ? cursor.getInt(colIndex) : 0;
+ }
+
+ private static String getName(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_NAME);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private static String getInputId(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_INPUT_ID);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private static String getPackageName(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_PACKAGE);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private static String getSettingsString(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(SETTINGS);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private MediaQualityUtils() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a16b122771ef..6d565d2157e1 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -679,6 +679,8 @@ public class NotificationManagerService extends SystemService {
WorkerHandler mHandler;
private final HandlerThread mRankingThread = new HandlerThread("ranker",
Process.THREAD_PRIORITY_BACKGROUND);
+ @FlaggedApi(Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST)
+ private Handler mBroadcastsHandler;
private final SparseArray<ArraySet<ComponentName>> mListenersDisablingEffects =
new SparseArray<>();
@@ -2682,7 +2684,7 @@ public class NotificationManagerService extends SystemService {
// TODO: All tests should use this init instead of the one-off setters above.
@VisibleForTesting
- void init(WorkerHandler handler, RankingHandler rankingHandler,
+ void init(WorkerHandler handler, RankingHandler rankingHandler, Handler broadcastsHandler,
IPackageManager packageManager, PackageManager packageManagerClient,
LightsManager lightsManager, NotificationListeners notificationListeners,
NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
@@ -2702,6 +2704,9 @@ public class NotificationManagerService extends SystemService {
ConnectivityManager connectivityManager,
PostNotificationTrackerFactory postNotificationTrackerFactory) {
mHandler = handler;
+ if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) {
+ mBroadcastsHandler = broadcastsHandler;
+ }
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -3045,13 +3050,22 @@ public class NotificationManagerService extends SystemService {
WorkerHandler handler = new WorkerHandler(Looper.myLooper());
+ Handler broadcastsHandler;
+ if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) {
+ HandlerThread broadcastsThread = new HandlerThread("NMS Broadcasts");
+ broadcastsThread.start();
+ broadcastsHandler = new Handler(broadcastsThread.getLooper());
+ } else {
+ broadcastsHandler = null;
+ }
+
mShowReviewPermissionsNotification = getContext().getResources().getBoolean(
R.bool.config_notificationReviewPermissions);
mDefaultUnsupportedAdjustments = getContext().getResources().getStringArray(
R.array.config_notificationDefaultUnsupportedAdjustments);
- init(handler, new RankingHandlerWorker(mRankingThread.getLooper()),
+ init(handler, new RankingHandlerWorker(mRankingThread.getLooper()), broadcastsHandler,
AppGlobals.getPackageManager(), getContext().getPackageManager(),
getLocalService(LightsManager.class),
new NotificationListeners(getContext(), mNotificationLock, mUserProfiles,
@@ -3297,10 +3311,11 @@ public class NotificationManagerService extends SystemService {
* so that e.g. rapidly changing some value A -> B -> C will only produce a broadcast for C
* (instead of every time because the extras are different).
*/
+ @FlaggedApi(Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST)
private void sendZenBroadcastWithDelay(Intent intent) {
String token = "zen_broadcast:" + intent.getAction();
- mHandler.removeCallbacksAndEqualMessages(token);
- mHandler.postDelayed(() -> sendRegisteredOnlyBroadcast(intent), token,
+ mBroadcastsHandler.removeCallbacksAndEqualMessages(token);
+ mBroadcastsHandler.postDelayed(() -> sendRegisteredOnlyBroadcast(intent), token,
ZEN_BROADCAST_DELAY.toMillis());
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 0ed522805bef..a80b1b2dd9e8 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -171,11 +171,11 @@ public interface StatusBarManagerInternal {
void onProposedRotationChanged(int displayId, int rotation, boolean isValid);
/**
- * Notifies System UI that the display is ready to show system decorations.
+ * Notifies System UI that the system decorations should be added on the display.
*
* @param displayId display ID
*/
- void onDisplayReady(int displayId);
+ void onDisplayAddSystemDecorations(int displayId);
/**
* Notifies System UI that the system decorations should be removed from the display.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index e753f273eb9b..c546388e4499 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -87,7 +87,6 @@ import android.service.quicksettings.TileService;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
-import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -125,7 +124,6 @@ import com.android.server.policy.GlobalActionsProvider;
import com.android.server.power.ShutdownCheckPoints;
import com.android.server.power.ShutdownThread;
import com.android.server.wm.ActivityTaskManagerInternal;
-import com.android.systemui.shared.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -343,19 +341,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void onDisplayAdded(int displayId) {
- if (Flags.statusBarConnectedDisplays()) {
- synchronized (mLock) {
- mDisplayUiState.put(displayId, new UiState());
- }
+ synchronized (mLock) {
+ mDisplayUiState.put(displayId, new UiState());
}
}
@Override
public void onDisplayRemoved(int displayId) {
- if (Flags.statusBarConnectedDisplays()) {
- synchronized (mLock) {
- mDisplayUiState.remove(displayId);
- }
+ synchronized (mLock) {
+ mDisplayUiState.remove(displayId);
}
}
@@ -776,10 +770,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
- public void onDisplayReady(int displayId) {
+ public void onDisplayAddSystemDecorations(int displayId) {
if (isVisibleBackgroundUserOnDisplay(displayId)) {
if (SPEW) {
- Slog.d(TAG, "Skipping onDisplayReady for visible background user "
+ Slog.d(TAG, "Skipping onDisplayAddSystemDecorations for visible background "
+ + "user "
+ mUserManagerInternal.getUserAssignedToDisplay(displayId));
}
return;
@@ -787,7 +782,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
IStatusBar bar = mBar;
if (bar != null) {
try {
- bar.onDisplayReady(displayId);
+ bar.onDisplayAddSystemDecorations(displayId);
} catch (RemoteException ex) {}
}
}
@@ -1366,66 +1361,53 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
return mTracingEnabled;
}
+ // TODO(b/117478341): make it aware of multi-display if needed.
@Override
public void disable(int what, IBinder token, String pkg) {
disableForUser(what, token, pkg, mCurrentUserId);
}
- /**
- * Disable additional status bar features for user for all displays. Pass the bitwise-or of the
- * {@code #DISABLE_*} flags. To re-enable everything, pass {@code #DISABLE_NONE}.
- *
- * Warning: Only pass {@code #DISABLE_*} flags into this function, do not use
- * {@code #DISABLE2_*} flags.
- */
+ // TODO(b/117478341): make it aware of multi-display if needed.
@Override
public void disableForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
enforceValidCallingUser();
synchronized (mLock) {
- IntArray displayIds = new IntArray();
- for (int i = 0; i < mDisplayUiState.size(); i++) {
- displayIds.add(mDisplayUiState.keyAt(i));
- }
- disableLocked(displayIds, userId, what, token, pkg, 1);
+ disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1);
}
}
+ // TODO(b/117478341): make it aware of multi-display if needed.
/**
- * Disable additional status bar features. Pass the bitwise-or of the {@code #DISABLE2_*} flags.
- * To re-enable everything, pass {@code #DISABLE2_NONE}.
+ * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags.
+ * To re-enable everything, pass {@link #DISABLE2_NONE}.
*
- * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
- * {@code #DISABLE_*} flags.
+ * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
*/
@Override
public void disable2(int what, IBinder token, String pkg) {
disable2ForUser(what, token, pkg, mCurrentUserId);
}
+ // TODO(b/117478341): make it aware of multi-display if needed.
/**
- * Disable additional status bar features for a given user for all displays. Pass the bitwise-or
- * of the {@code #DISABLE2_*} flags. To re-enable everything, pass {@code #DISABLE2_NONE}.
+ * Disable additional status bar features for a given user. Pass the bitwise-or of the
+ * DISABLE2_* flags. To re-enable everything, pass {@link #DISABLE_NONE}.
*
- * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
- * {@code #DISABLE_*} flags.
+ * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
*/
@Override
public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
synchronized (mLock) {
- IntArray displayIds = new IntArray();
- for (int i = 0; i < mDisplayUiState.size(); i++) {
- displayIds.add(mDisplayUiState.keyAt(i));
- }
- disableLocked(displayIds, userId, what, token, pkg, 2);
+ disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 2);
}
}
- private void disableLocked(IntArray displayIds, int userId, int what, IBinder token,
- String pkg, int whichFlag) {
+ private void disableLocked(int displayId, int userId, int what, IBinder token, String pkg,
+ int whichFlag) {
// It's important that the the callback and the call to mBar get done
// in the same order when multiple threads are calling this function
// so they are paired correctly. The messages on the handler will be
@@ -1435,27 +1417,18 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
// Ensure state for the current user is applied, even if passed a non-current user.
final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1);
final int net2 = gatherDisableActionsLocked(mCurrentUserId, 2);
- boolean shouldCallNotificationOnSetDisabled = false;
- IStatusBar bar = mBar;
- for (int displayId : displayIds.toArray()) {
- final UiState state = getUiState(displayId);
- if (!state.disableEquals(net1, net2)) {
- shouldCallNotificationOnSetDisabled = true;
- state.setDisabled(net1, net2);
- if (bar != null) {
- try {
- // TODO(b/388244660): Create IStatusBar#disableForAllDisplays to avoid
- // multiple IPC calls.
- bar.disable(displayId, net1, net2);
- } catch (RemoteException ex) {
- Slog.e(TAG, "Unable to disable Status bar.", ex);
- }
+ final UiState state = getUiState(displayId);
+ if (!state.disableEquals(net1, net2)) {
+ state.setDisabled(net1, net2);
+ mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
+ IStatusBar bar = mBar;
+ if (bar != null) {
+ try {
+ bar.disable(displayId, net1, net2);
+ } catch (RemoteException ex) {
}
}
}
- if (shouldCallNotificationOnSetDisabled) {
- mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
- }
}
/**
@@ -1610,8 +1583,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
if (SPEW) Slog.d(TAG, "setDisableFlags(0x" + Integer.toHexString(flags) + ")");
synchronized (mLock) {
- disableLocked(IntArray.wrap(new int[]{displayId}), mCurrentUserId, flags,
- mSysUiVisToken, cause, 1);
+ disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1);
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
index 872ab595994b..f413fe33c3f2 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
@@ -26,7 +26,7 @@ public abstract class WallpaperManagerInternal {
/**
* Notifies the display is ready for adding wallpaper on it.
*/
- public abstract void onDisplayReady(int displayId);
+ public abstract void onDisplayAddSystemDecorations(int displayId);
/** Notifies when display stop showing system decorations and wallpaper. */
public abstract void onDisplayRemoveSystemDecorations(int displayId);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index db530e728a1a..09b10739d469 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1636,8 +1636,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private final class LocalService extends WallpaperManagerInternal {
@Override
- public void onDisplayReady(int displayId) {
- onDisplayReadyInternal(displayId);
+ public void onDisplayAddSystemDecorations(int displayId) {
+ onDisplayAddSystemDecorationsInternal(displayId);
}
@Override
@@ -3944,7 +3944,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return (wallpaper != null) ? wallpaper.allowBackup : false;
}
- private void onDisplayReadyInternal(int displayId) {
+ private void onDisplayAddSystemDecorationsInternal(int displayId) {
synchronized (mLock) {
if (mLastWallpaper == null) {
return;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c7d4467a6e98..6f76618b0029 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -3655,7 +3655,7 @@ class ActivityStarter {
private static String getIntentRedirectPreventedLogMessage(@NonNull String message,
@NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
int callingUid, @Nullable String callingPackage) {
- return "[IntentRedirect]" + message + " intentCreatorUid: " + intentCreatorUid
+ return "[IntentRedirect Hardening] " + message + " intentCreatorUid: " + intentCreatorUid
+ "; intentCreatorPackage: " + intentCreatorPackage + "; callingUid: " + callingUid
+ "; callingPackage: " + callingPackage + "; intent: " + intent;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index cf111cdbcc6a..ddb9f178cb8b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2988,38 +2988,45 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
throw new SecurityException("Requires permission "
+ android.Manifest.permission.DEVICE_POWER);
}
-
- synchronized (mGlobalLock) {
- final long ident = Binder.clearCallingIdentity();
- if (mKeyguardShown != keyguardShowing) {
- mKeyguardShown = keyguardShowing;
- final Message msg = PooledLambda.obtainMessage(
- ActivityManagerInternal::reportCurKeyguardUsageEvent, mAmInternal,
- keyguardShowing);
- mH.sendMessage(msg);
- }
- // Always reset the state regardless of keyguard-showing change, because that means the
- // unlock is either completed or canceled.
- if ((mDemoteTopAppReasons & DEMOTE_TOP_REASON_DURING_UNLOCKING) != 0) {
- mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING;
- // The scheduling group of top process was demoted by unlocking, so recompute
- // to restore its real top priority if possible.
- if (mTopApp != null) {
- mTopApp.scheduleUpdateOomAdj();
- }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ setLockScreenShownLocked(keyguardShowing, aodShowing);
}
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "setLockScreenShown");
- mRootWindowContainer.forAllDisplays(displayContent -> {
- mKeyguardController.setKeyguardShown(displayContent.getDisplayId(),
- keyguardShowing, aodShowing);
- });
- maybeHideLockedProfileActivityLocked();
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- Binder.restoreCallingIdentity(ident);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @GuardedBy("mGlobalLock")
+ void setLockScreenShownLocked(boolean keyguardShowing, boolean aodShowing) {
+ if (mKeyguardShown != keyguardShowing) {
+ mKeyguardShown = keyguardShowing;
+ final Message msg = PooledLambda.obtainMessage(
+ ActivityManagerInternal::reportCurKeyguardUsageEvent, mAmInternal,
+ keyguardShowing);
+ mH.sendMessage(msg);
+ }
+ // Always reset the state regardless of keyguard-showing change, because that means the
+ // unlock is either completed or canceled.
+ if ((mDemoteTopAppReasons & DEMOTE_TOP_REASON_DURING_UNLOCKING) != 0) {
+ mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING;
+ // The scheduling group of top process was demoted by unlocking, so recompute
+ // to restore its real top priority if possible.
+ if (mTopApp != null) {
+ mTopApp.scheduleUpdateOomAdj();
}
}
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "setLockScreenShown");
+ mRootWindowContainer.forAllDisplays(displayContent -> {
+ mKeyguardController.setKeyguardShown(displayContent.getDisplayId(),
+ keyguardShowing, aodShowing);
+ });
+ maybeHideLockedProfileActivityLocked();
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
mH.post(() -> {
for (int i = mScreenObservers.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index 6bf1c466aeb5..e3906f9119c2 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -23,6 +23,7 @@ import android.window.DesktopModeFlags;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
/**
* Constants for desktop mode feature
@@ -35,7 +36,7 @@ public final class DesktopModeHelper {
"persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
/** Whether desktop mode is enabled. */
- static boolean isDesktopModeEnabled() {
+ private static boolean isDesktopModeEnabled() {
return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue();
}
@@ -56,11 +57,30 @@ public final class DesktopModeHelper {
return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported);
}
+ static boolean isDesktopModeDevOptionsSupported(@NonNull Context context) {
+ return context.getResources().getBoolean(R.bool.config_isDesktopModeDevOptionSupported);
+ }
+
+ /**
+ * Check if Desktop mode should be enabled because the dev option is shown and enabled.
+ */
+ private static boolean isDesktopModeEnabledByDevOption(@NonNull Context context) {
+ return DesktopModeFlags.isDesktopModeForcedEnabled() && (isDesktopModeDevOptionsSupported(
+ context) || isDeviceEligibleForDesktopMode(context));
+ }
+
+ @VisibleForTesting
+ static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
+ return !shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context) || (
+ Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionsSupported(
+ context));
+ }
+
/**
* Return {@code true} if desktop mode can be entered on the current device.
*/
static boolean canEnterDesktopMode(@NonNull Context context) {
- return isDesktopModeEnabled()
- && (!shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context));
+ return (isDesktopModeEnabled() && isDeviceEligibleForDesktopMode(context))
+ || isDesktopModeEnabledByDevOption(context);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5b1619995529..5329e3b9abb3 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1887,17 +1887,17 @@ public class DisplayPolicy {
mCanSystemBarsBeShownByUser = canBeShown;
}
- void notifyDisplayReady() {
+ void notifyDisplayAddSystemDecorations() {
mHandler.post(() -> {
final int displayId = getDisplayId();
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
if (statusBar != null) {
- statusBar.onDisplayReady(displayId);
+ statusBar.onDisplayAddSystemDecorations(displayId);
}
final WallpaperManagerInternal wpMgr = LocalServices
.getService(WallpaperManagerInternal.class);
if (wpMgr != null) {
- wpMgr.onDisplayReady(displayId);
+ wpMgr.onDisplayAddSystemDecorations(displayId);
}
});
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index abd26b5164f7..cf464c707ff4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2845,7 +2845,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
startHomeOnDisplay(mCurrentUser, reason, displayContent.getDisplayId());
- displayContent.getDisplayPolicy().notifyDisplayReady();
+ if (enableDisplayContentModeManagement()) {
+ if (displayContent.isSystemDecorationsSupported()) {
+ displayContent.getDisplayPolicy().notifyDisplayAddSystemDecorations();
+ }
+ } else {
+ displayContent.getDisplayPolicy().notifyDisplayAddSystemDecorations();
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 060f2e803ec9..b4c2c0173767 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1865,7 +1865,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (keyguardState != null) {
boolean keyguardShowing = keyguardState.getKeyguardShowing();
boolean aodShowing = keyguardState.getAodShowing();
- mService.setLockScreenShown(keyguardShowing, aodShowing);
+ mService.setLockScreenShownLocked(keyguardShowing, aodShowing);
}
return effects;
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index bf4595c815bd..b37bcc73829c 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -602,12 +602,14 @@ void NativeInputManager::dump(std::string& dump) {
return std::to_string(displayId.val());
};
dump += StringPrintf(INDENT "Display not interactive: %s\n",
- dumpSet(mLocked.nonInteractiveDisplays, streamableToString).c_str());
+ dumpContainer(mLocked.nonInteractiveDisplays, streamableToString)
+ .c_str());
dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
toString(mLocked.systemUiLightsOut));
dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
dump += StringPrintf(INDENT "Display with Mouse Scaling Disabled: %s\n",
- dumpSet(mLocked.displaysWithMouseScalingDisabled, streamableToString)
+ dumpContainer(mLocked.displaysWithMouseScalingDisabled,
+ streamableToString)
.c_str());
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index b92afc5c0ca7..d80fd20dd1e6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -731,7 +731,7 @@ public class WallpaperManagerServiceTests {
// WHEN display ID, 2, is ready.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Then there is a connection established for the system & lock wallpaper for display ID, 2.
verify(mockIWallpaperService).attach(
@@ -771,7 +771,7 @@ public class WallpaperManagerServiceTests {
// WHEN display ID, 2, is ready.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Then there is a connection established for the system wallpaper for display ID, 2.
verify(mockIWallpaperService).attach(
@@ -818,7 +818,7 @@ public class WallpaperManagerServiceTests {
// WHEN display ID, 2, is ready.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Then there is a connection established for the fallback wallpaper for display ID, 2.
verify(mockIWallpaperService).attach(
@@ -856,7 +856,7 @@ public class WallpaperManagerServiceTests {
// GIVEN wallpaper connections have been established for display ID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Save displayConnector for displayId 2 before display removal.
WallpaperManagerService.DisplayConnector displayConnector =
wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -894,7 +894,7 @@ public class WallpaperManagerServiceTests {
// GIVEN wallpaper connections have been established for display ID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Save displayConnectors for display ID, 2, before display removal.
WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector =
systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -930,7 +930,7 @@ public class WallpaperManagerServiceTests {
// GIVEN wallpaper connections have been established for display ID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Save fallback wallpaper displayConnector for display ID, 2, before display removal.
WallpaperManagerService.DisplayConnector fallbackWallpaperConnector =
mService.mFallbackWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -977,7 +977,7 @@ public class WallpaperManagerServiceTests {
// GIVEN wallpaper connections have been established for displayID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Save displayConnector for displayId 2 before display removal.
WallpaperManagerService.DisplayConnector displayConnector =
wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -1011,7 +1011,7 @@ public class WallpaperManagerServiceTests {
// GIVEN wallpaper connections have been established for displayID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Save displayConnectors for displayId 2 before display removal.
WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector =
systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 28e5be505556..9cfa51a85988 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -29,6 +29,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES;
+import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
@@ -38,6 +39,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.UserSh
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity.EXTRA_TYPE_TO_CHOOSE;
import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
+import static com.android.server.accessibility.Flags.FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL;
import static com.google.common.truth.Truth.assertThat;
@@ -93,6 +95,7 @@ import android.os.UserHandle;
import android.os.test.FakePermissionEnforcer;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
@@ -584,6 +587,31 @@ public class AccessibilityManagerServiceTest {
}
@Test
+ @RequiresFlagsEnabled({FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL, FLAG_KEYBOARD_REPEAT_KEYS})
+ public void testRepeatKeysSettingsChanges_propagateToMagnificationController() {
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ Settings.Secure.putIntForUser(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.KEY_REPEAT_ENABLED,
+ 0, mA11yms.getCurrentUserIdLocked());
+
+ mA11yms.readRepeatKeysSettingsLocked(userState);
+
+ verify(mMockMagnificationController).setRepeatKeysEnabled(false);
+
+ final int timeoutMs = 42;
+ Settings.Secure.putIntForUser(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.KEY_REPEAT_TIMEOUT_MS,
+ timeoutMs, mA11yms.getCurrentUserIdLocked());
+
+ mA11yms.readRepeatKeysSettingsLocked(userState);
+
+ verify(mMockMagnificationController).setRepeatKeysTimeoutMs(timeoutMs);
+ }
+
+ @Test
public void testSettingsAlwaysOn_setEnabled_featureFlagDisabled_doNothing() {
when(mMockMagnificationController.isAlwaysOnMagnificationFeatureFlagEnabled())
.thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index d4f2dcc24af6..5d94e7274a1f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -250,9 +250,26 @@ public class AccessibilityServiceConnectionTest {
}
@Test
- public void sendGesture_touchableDevice_injectEvents()
- throws RemoteException {
+ public void sendGesture_touchableDevice_injectEvents_fromAccessibilityTool() {
+ when(mMockWindowManagerInternal.isTouchOrFaketouchDevice()).thenReturn(true);
+ when(mServiceInfo.isAccessibilityTool()).thenReturn(true);
+ setServiceBinding(COMPONENT_NAME);
+ mConnection.bindLocked();
+ mConnection.onServiceConnected(COMPONENT_NAME, mMockIBinder);
+
+ ParceledListSlice parceledListSlice = mock(ParceledListSlice.class);
+ List<GestureDescription.GestureStep> gestureSteps = mock(List.class);
+ when(parceledListSlice.getList()).thenReturn(gestureSteps);
+ mConnection.dispatchGesture(0, parceledListSlice, Display.DEFAULT_DISPLAY);
+
+ verify(mMockMotionEventInjector).injectEvents(gestureSteps, mMockServiceClient, 0,
+ Display.DEFAULT_DISPLAY, true);
+ }
+
+ @Test
+ public void sendGesture_touchableDevice_injectEvents_fromNonTool() {
when(mMockWindowManagerInternal.isTouchOrFaketouchDevice()).thenReturn(true);
+ when(mServiceInfo.isAccessibilityTool()).thenReturn(false);
setServiceBinding(COMPONENT_NAME);
mConnection.bindLocked();
mConnection.onServiceConnected(COMPONENT_NAME, mMockIBinder);
@@ -263,13 +280,14 @@ public class AccessibilityServiceConnectionTest {
mConnection.dispatchGesture(0, parceledListSlice, Display.DEFAULT_DISPLAY);
verify(mMockMotionEventInjector).injectEvents(gestureSteps, mMockServiceClient, 0,
- Display.DEFAULT_DISPLAY);
+ Display.DEFAULT_DISPLAY, false);
}
@Test
public void sendGesture_untouchableDevice_performGestureResultFailed()
throws RemoteException {
when(mMockWindowManagerInternal.isTouchOrFaketouchDevice()).thenReturn(false);
+ when(mServiceInfo.isAccessibilityTool()).thenReturn(true);
setServiceBinding(COMPONENT_NAME);
mConnection.bindLocked();
mConnection.onServiceConnected(COMPONENT_NAME, mMockIBinder);
@@ -280,7 +298,7 @@ public class AccessibilityServiceConnectionTest {
mConnection.dispatchGesture(0, parceledListSlice, Display.DEFAULT_DISPLAY);
verify(mMockMotionEventInjector, never()).injectEvents(gestureSteps, mMockServiceClient, 0,
- Display.DEFAULT_DISPLAY);
+ Display.DEFAULT_DISPLAY, true);
verify(mMockServiceClient).onPerformGestureResult(0, false);
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index 233caf9c7761..d2d8c682ed90 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -20,8 +20,7 @@ import static android.view.KeyCharacterMap.VIRTUAL_KEYBOARD;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_HOVER_MOVE;
import static android.view.MotionEvent.ACTION_UP;
-import static android.view.WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
-import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER;
+import static android.view.accessibility.Flags.FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.anyOf;
@@ -48,10 +47,14 @@ import android.graphics.Point;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.WindowManagerPolicyConstants;
import android.view.accessibility.AccessibilityEvent;
import androidx.test.runner.AndroidJUnit4;
@@ -64,6 +67,7 @@ import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -77,7 +81,7 @@ import java.util.List;
*/
@RunWith(AndroidJUnit4.class)
public class MotionEventInjectorTest {
- private static final String LOG_TAG = "MotionEventInjectorTest";
+
private static final Matcher<MotionEvent> IS_ACTION_DOWN =
new MotionEventActionMatcher(ACTION_DOWN);
private static final Matcher<MotionEvent> IS_ACTION_POINTER_DOWN =
@@ -120,6 +124,9 @@ public class MotionEventInjectorTest {
private static final float POINTER_SIZE = 1;
private static final int METASTATE = 0;
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
MotionEventInjector mMotionEventInjector;
IAccessibilityServiceClient mServiceInterface;
AccessibilityTraceManager mTrace;
@@ -201,7 +208,8 @@ public class MotionEventInjectorTest {
verifyNoMoreInteractions(next);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
- final int expectedFlags = FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY;
+ final int expectedFlags = WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+ | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(expectedFlags));
verify(next).onMotionEvent(argThat(mIsLineStart), argThat(mIsLineStart), eq(expectedFlags));
verifyNoMoreInteractions(next);
@@ -227,6 +235,21 @@ public class MotionEventInjectorTest {
}
@Test
+ @EnableFlags(FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS)
+ public void testInjectEvents_fromAccessibilityTool_providesToolPolicyFlag() {
+ EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE,
+ /*fromAccessibilityTool=*/true);
+
+ mMessageCapturingHandler.sendOneMessage(); // Send a motion event
+ verify(next).onMotionEvent(
+ argThat(mIsLineStart), argThat(mIsLineStart),
+ eq(WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+ | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY
+ | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL));
+ }
+
+ @Test
public void testInjectEvents_gestureWithTooManyPoints_shouldNotCrash() throws Exception {
int tooManyPointsCount = 20;
TouchPoint[] startTouchPoints = new TouchPoint[tooManyPointsCount];
@@ -251,14 +274,28 @@ public class MotionEventInjectorTest {
}
@Test
- public void testRegularEvent_afterGestureComplete_shouldPassToNext() {
+ @DisableFlags(FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS)
+ public void testRegularEvent_afterGestureComplete_shouldPassToNext_withFlagInjectedFromA11y() {
+ EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ reset(next);
+ mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
+ verify(next).onMotionEvent(argThat(mIsClickDown), argThat(mIsClickDown),
+ eq(WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY));
+ }
+
+ @Test
+ @EnableFlags(FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS)
+ public void testRegularEvent_afterGestureComplete_shouldPassToNext_withNoPolicyFlagChanges() {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
mMessageCapturingHandler.sendAllMessages(); // Send all motion events
reset(next);
mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
verify(next).onMotionEvent(argThat(mIsClickDown), argThat(mIsClickDown),
- eq(FLAG_INJECTED_FROM_ACCESSIBILITY));
+ // The regular event passing through the filter should have no policy flag changes
+ eq(0));
}
@Test
@@ -275,7 +312,8 @@ public class MotionEventInjectorTest {
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
verify(next).onMotionEvent(
argThat(mIsLineStart), argThat(mIsLineStart),
- eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY));
+ eq(WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+ | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY));
}
@Test
@@ -307,10 +345,12 @@ public class MotionEventInjectorTest {
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
- eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY));
+ eq(WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+ | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY));
verify(next).onMotionEvent(
argThat(mIsLineStart), argThat(mIsLineStart),
- eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY));
+ eq(WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+ | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY));
}
@Test
@@ -731,8 +771,14 @@ public class MotionEventInjectorTest {
private void injectEventsSync(List<GestureStep> gestureSteps,
IAccessibilityServiceClient serviceInterface, int sequence) {
+ injectEventsSync(gestureSteps, serviceInterface, sequence, false);
+ }
+
+ private void injectEventsSync(List<GestureStep> gestureSteps,
+ IAccessibilityServiceClient serviceInterface, int sequence,
+ boolean fromAccessibilityTool) {
mMotionEventInjector.injectEvents(gestureSteps, serviceInterface, sequence,
- Display.DEFAULT_DISPLAY);
+ Display.DEFAULT_DISPLAY, fromAccessibilityTool);
// Dispatch the message sent by the injector. Our simple handler doesn't guarantee stuff
// happens in order.
mMessageCapturingHandler.sendLastMessage();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 3511ae12497a..cd6b36dbc1c6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -885,66 +885,142 @@ public class MagnificationControllerTest {
}
@Test
- public void magnificationCallbacks_panMagnificationContinuous() throws RemoteException {
+ public void magnificationCallbacks_scaleMagnificationContinuous() throws RemoteException {
setMagnificationEnabled(MODE_FULLSCREEN);
- mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false);
+ float currentScale = 2.0f;
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, currentScale, false);
reset(mScreenMagnificationController);
- DisplayMetrics metrics = new DisplayMetrics();
- mDisplay.getMetrics(metrics);
- float expectedStep = 27 * metrics.density;
-
float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
- // Start moving right using keyboard callbacks.
- mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
- MagnificationController.PAN_DIRECTION_RIGHT);
+ // Start zooming in using keyboard callbacks.
+ mMagnificationController.onScaleMagnificationStart(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN);
+ // The center is unchanged.
float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
- expect.that(currentCenterX).isLessThan(newCenterX);
- expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
+ expect.that(currentCenterX).isWithin(1.0f).of(newCenterX);
expect.that(currentCenterY).isEqualTo(newCenterY);
- currentCenterX = newCenterX;
- currentCenterY = newCenterY;
+ // The scale is increased.
+ float newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ expect.that(currentScale).isLessThan(newScale);
+ currentScale = newScale;
// Wait for the initial delay to occur.
- advanceTime(MagnificationController.INITIAL_KEYBOARD_REPEAT_INTERVAL_MS + 1);
+ advanceTime(mMagnificationController.getInitialKeyboardRepeatIntervalMs() + 1);
- // It should have moved again after the handler was triggered.
- newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
- newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
- expect.that(currentCenterX).isLessThan(newCenterX);
- expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
- expect.that(currentCenterY).isEqualTo(newCenterY);
- currentCenterX = newCenterX;
- currentCenterY = newCenterY;
+ // It should have scaled again after the handler was triggered.
+ newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ expect.that(currentScale).isLessThan(newScale);
+ currentScale = newScale;
- // Wait for repeat delay to occur.
+ for (int i = 0; i < 3; i++) {
+ // Wait for repeat delay to occur.
+ advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
+
+ // It should have scaled another time.
+ newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ expect.that(currentScale).isLessThan(newScale);
+ currentScale = newScale;
+ }
+
+ // Stop magnification scale.
+ mMagnificationController.onScaleMagnificationStop(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN);
+
+ // It should not scale again, even after the appropriate delay.
advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
- // It should have moved a third time.
- newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
- newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
- expect.that(currentCenterX).isLessThan(newCenterX);
- expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
- expect.that(currentCenterY).isEqualTo(newCenterY);
+ newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ expect.that(currentScale).isEqualTo(newScale);
+ }
+
+ @Test
+ public void magnificationCallbacks_panMagnificationContinuous_repeatKeysTimeout200()
+ throws RemoteException {
+ // Shorter than default.
+ testMagnificationContinuousPanningWithTimeout(200);
+ }
+
+ @Test
+ public void magnificationCallbacks_panMagnificationContinuous_repeatKeysTimeout1000()
+ throws RemoteException {
+ // Longer than default.
+ testMagnificationContinuousPanningWithTimeout(1000);
+ }
+
+ @Test
+ public void magnificationCallbacks_panMagnification_notContinuousWithRepeatKeysDisabled()
+ throws RemoteException {
+ mMagnificationController.setRepeatKeysEnabled(false);
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 4.0f, false);
+ reset(mScreenMagnificationController);
+
+ float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+
+ // Start moving down using keyboard callbacks.
+ mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_DOWN);
+
+ float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+ expect.that(currentCenterY).isLessThan(newCenterY);
+ expect.that(currentCenterX).isEqualTo(newCenterX);
+
currentCenterX = newCenterX;
currentCenterY = newCenterY;
- // Stop magnification pan.
+ for (int i = 0; i < 3; i++) {
+ // Wait for the initial delay to occur.
+ advanceTime(mMagnificationController.getInitialKeyboardRepeatIntervalMs() + 1);
+
+ // It should not have moved again because repeat keys is disabled.
+ newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+ expect.that(currentCenterX).isEqualTo(newCenterX);
+ expect.that(currentCenterY).isEqualTo(newCenterY);
+ currentCenterX = newCenterX;
+ currentCenterY = newCenterY;
+ }
+
mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
- MagnificationController.PAN_DIRECTION_RIGHT);
+ MagnificationController.PAN_DIRECTION_DOWN);
+ }
- // It should not move again, even after the appropriate delay.
- advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
+ @Test
+ public void magnificationCallbacks_scaleMagnification_notContinuousWithRepeatKeysDisabled()
+ throws RemoteException {
+ mMagnificationController.setRepeatKeysEnabled(false);
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ float currentScale = 8.0f;
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, currentScale, false);
+ reset(mScreenMagnificationController);
- newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
- newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
- expect.that(newCenterX).isEqualTo(currentCenterX);
- expect.that(newCenterY).isEqualTo(currentCenterY);
+ // Start scaling out using keyboard callbacks.
+ mMagnificationController.onScaleMagnificationStart(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT);
+
+ float newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ expect.that(currentScale).isGreaterThan(newScale);
+
+ currentScale = newScale;
+
+ for (int i = 0; i < 3; i++) {
+ // Wait for the initial delay to occur.
+ advanceTime(mMagnificationController.getInitialKeyboardRepeatIntervalMs() + 1);
+
+ // It should not have scaled again because repeat keys is disabled.
+ newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ expect.that(currentScale).isEqualTo(newScale);
+ }
+
+ mMagnificationController.onScaleMagnificationStop(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT);
}
@Test
@@ -1736,6 +1812,75 @@ public class MagnificationControllerTest {
MagnificationController.PAN_DIRECTION_UP);
}
+ private void
+ testMagnificationContinuousPanningWithTimeout(int timeoutMs) throws RemoteException {
+ mMagnificationController.setRepeatKeysTimeoutMs(timeoutMs);
+ expect.that(timeoutMs).isEqualTo(
+ mMagnificationController.getInitialKeyboardRepeatIntervalMs());
+
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false);
+ reset(mScreenMagnificationController);
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ mDisplay.getMetrics(metrics);
+ float expectedStep = 27 * metrics.density;
+
+ float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+
+ // Start moving right using keyboard callbacks.
+ mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_RIGHT);
+
+ float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+ expect.that(currentCenterX).isLessThan(newCenterX);
+ expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
+ expect.that(currentCenterY).isEqualTo(newCenterY);
+
+ currentCenterX = newCenterX;
+ currentCenterY = newCenterY;
+
+ // Wait for the initial delay to occur.
+ advanceTime(timeoutMs + 1);
+
+ // It should have moved again after the handler was triggered.
+ newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+ expect.that(currentCenterX).isLessThan(newCenterX);
+ expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
+ expect.that(currentCenterY).isEqualTo(newCenterY);
+ currentCenterX = newCenterX;
+ currentCenterY = newCenterY;
+
+ for (int i = 0; i < 3; i++) {
+ // Wait for repeat delay to occur.
+ advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
+
+ // It should have moved another time.
+ newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+ expect.that(currentCenterX).isLessThan(newCenterX);
+ expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
+ expect.that(currentCenterY).isEqualTo(newCenterY);
+ currentCenterX = newCenterX;
+ currentCenterY = newCenterY;
+ }
+
+ // Stop magnification pan.
+ mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
+ MagnificationController.PAN_DIRECTION_RIGHT);
+
+ // It should not move again, even after the appropriate delay.
+ advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
+
+ newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+ newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+ expect.that(newCenterX).isEqualTo(currentCenterX);
+ expect.that(newCenterY).isEqualTo(currentCenterY);
+ }
+
private void advanceTime(long timeMs) {
mTestLooper.moveTimeForward(timeMs);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/OWNERS b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
index 832bcd9d70e6..3caf7faa13ec 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
@@ -1 +1,2 @@
+# Bug component: 1345447
include /media/java/android/media/projection/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 263ada8b36f6..148c96850d34 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -69,7 +69,6 @@ import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.platform.test.annotations.EnableFlags;
import android.service.quicksettings.TileService;
import android.testing.TestableContext;
@@ -80,7 +79,6 @@ import com.android.internal.statusbar.IStatusBar;
import com.android.server.LocalServices;
import com.android.server.policy.GlobalActionsProvider;
import com.android.server.wm.ActivityTaskManagerInternal;
-import com.android.systemui.shared.Flags;
import libcore.junit.util.compat.CoreCompatChangeRule;
@@ -107,7 +105,6 @@ public class StatusBarManagerServiceTest {
TEST_SERVICE);
private static final CharSequence APP_NAME = "AppName";
private static final CharSequence TILE_LABEL = "Tile label";
- private static final int SECONDARY_DISPLAY_ID = 2;
@Rule
public final TestableContext mContext =
@@ -752,29 +749,6 @@ public class StatusBarManagerServiceTest {
}
@Test
- @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
- public void testDisableForAllDisplays() throws Exception {
- int user1Id = 0;
- mockUidCheck();
- mockCurrentUserCheck(user1Id);
-
- mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
-
- int expectedFlags = DISABLE_MASK & DISABLE_BACK;
- String pkg = mContext.getPackageName();
-
- // before disabling
- assertEquals(DISABLE_NONE,
- mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
-
- // disable
- mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
-
- verify(mMockStatusBar).disable(0, expectedFlags, 0);
- verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, expectedFlags, 0);
- }
-
- @Test
public void testSetHomeDisabled() throws Exception {
int expectedFlags = DISABLE_MASK & DISABLE_HOME;
String pkg = mContext.getPackageName();
@@ -877,29 +851,6 @@ public class StatusBarManagerServiceTest {
}
@Test
- @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
- public void testDisable2ForAllDisplays() throws Exception {
- int user1Id = 0;
- mockUidCheck();
- mockCurrentUserCheck(user1Id);
-
- mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
-
- int expectedFlags = DISABLE2_MASK & DISABLE2_NOTIFICATION_SHADE;
- String pkg = mContext.getPackageName();
-
- // before disabling
- assertEquals(DISABLE_NONE,
- mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
-
- // disable
- mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
-
- verify(mMockStatusBar).disable(0, 0, expectedFlags);
- verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, 0, expectedFlags);
- }
-
- @Test
public void testSetQuickSettingsDisabled2() throws Exception {
int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS;
String pkg = mContext.getPackageName();
@@ -1141,7 +1092,6 @@ public class StatusBarManagerServiceTest {
// disable
mStatusBarManagerService.disableForUser(expectedUser1Flags, mMockStatusBar, pkg, user1Id);
mStatusBarManagerService.disableForUser(expectedUser2Flags, mMockStatusBar, pkg, user2Id);
-
// check that right flag is disabled
assertEquals(expectedUser1Flags,
mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 839f27634d0f..19b90b6b76d9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -679,7 +679,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
public void testDisallowAdjustmentType_readWriteXml_entries() throws Exception {
int userId = ActivityManager.getCurrentUser();
@@ -724,7 +724,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
public void testDisallowAdjustmentKeyType_readWriteXml() throws Exception {
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index caff9139e26c..f8c8a1dbb481 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -263,6 +263,7 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
@@ -578,6 +579,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NetworkCapabilities mWifiNetworkCapabilities;
private NotificationManagerService.WorkerHandler mWorkerHandler;
+ private Handler mBroadcastsHandler;
private class TestableToastCallback extends ITransientNotification.Stub {
@Override
@@ -604,8 +606,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return FlagsParameterization.allCombinationsOf(
- FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NM_BINDER_PERF_CACHE_CHANNELS);
+ return FlagsParameterization.allCombinationsOf();
}
public NotificationManagerServiceTest(FlagsParameterization flags) {
@@ -815,14 +816,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mUmInternal.isUserInitialized(anyInt())).thenReturn(true);
mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
- mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
- mLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
- mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
- mAppUsageStats, mDevicePolicyManager, mUgm, mUgmInternal,
- mAppOpsManager, mUm, mHistoryManager, mStatsManager,
- mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
- mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager,
- mPowerManager, mConnectivityManager, mPostNotificationTrackerFactory);
+ mBroadcastsHandler = new Handler(mTestableLooper.getLooper());
+
+ mService.init(mWorkerHandler, mRankingHandler, mBroadcastsHandler, mPackageManager,
+ mPackageManagerClient, mLightsManager, mListeners, mAssistants, mConditionProviders,
+ mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
+ mGroupHelper, mAm, mAtm, mAppUsageStats, mDevicePolicyManager, mUgm, mUgmInternal,
+ mAppOpsManager, mUm, mHistoryManager, mStatsManager, mAmi, mToastRateLimiter,
+ mPermissionHelper, mock(UsageStatsManagerInternal.class), mTelecomManager, mLogger,
+ mTestFlagResolver, mPermissionManager, mPowerManager, mConnectivityManager,
+ mPostNotificationTrackerFactory);
mService.setAttentionHelper(mAttentionHelper);
mService.setLockPatternUtils(mock(LockPatternUtils.class));
@@ -1004,6 +1007,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// problematic interactions with mocks when they're no longer working as expected).
mWorkerHandler.removeCallbacksAndMessages(null);
}
+ if (mBroadcastsHandler != null) {
+ mBroadcastsHandler.removeCallbacksAndMessages(null);
+ }
if (mTestableLooper != null) {
// Must remove static reference to this test object to prevent leak (b/261039202)
@@ -17648,8 +17654,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
- public void testSetCanBePromoted_granted() throws Exception {
+ @EnableFlags({android.app.Flags.FLAG_API_RICH_ONGOING})
+ public void testSetCanBePromoted_granted_noui() throws Exception {
+ testSetCanBePromoted_granted();
+ }
+
+ @Test
+ @EnableFlags({android.app.Flags.FLAG_API_RICH_ONGOING,
+ android.app.Flags.FLAG_UI_RICH_ONGOING })
+ public void testSetCanBePromoted_granted_ui() throws Exception {
+ testSetCanBePromoted_granted();
+ }
+
+ private void testSetCanBePromoted_granted() throws Exception {
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -17704,6 +17721,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(r);
mService.addEnqueuedNotification(r1);
+ // GIVEN - make sure the promoted value does not depend on the default value.
+ mBinderService.setCanBePromoted(mPkg, mUid, false, true);
+ waitForIdle();
+ clearInvocations(mListeners);
+
mBinderService.setCanBePromoted(mPkg, mUid, true, true);
waitForIdle();
@@ -17726,7 +17748,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
- public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
+ public void testSetCanBePromoted_granted_onlyNotifiesOnce_noui() throws Exception {
+ testSetCanBePromoted_granted_onlyNotifiesOnce();
+ }
+
+ @Test
+ @EnableFlags({android.app.Flags.FLAG_API_RICH_ONGOING,
+ android.app.Flags.FLAG_UI_RICH_ONGOING})
+ public void testSetCanBePromoted_granted_onlyNotifiesOnce_ui() throws Exception {
+ testSetCanBePromoted_granted_onlyNotifiesOnce();
+ }
+
+ private void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -17742,6 +17775,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
mService.addNotification(r);
+ // GIVEN - make sure the promoted value does not depend on the default value.
+ mBinderService.setCanBePromoted(mPkg, mUid, false, true);
+ waitForIdle();
+ clearInvocations(mListeners);
mBinderService.setCanBePromoted(mPkg, mUid, true, true);
waitForIdle();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 82d87d40031a..ad900fe6e376 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -48,13 +48,13 @@ import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
+import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.PermissionManager;
import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -152,7 +152,7 @@ public class RoleObserverTest extends UiServiceTestCase {
try {
mService.init(mService.new WorkerHandler(mTestableLooper.getLooper()),
- mock(RankingHandler.class),
+ mock(RankingHandler.class), new Handler(mTestableLooper.getLooper()),
mock(IPackageManager.class), mock(PackageManager.class),
mock(LightsManager.class),
mock(NotificationListeners.class), mock(NotificationAssistants.class),
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
new file mode 100644
index 000000000000..e0b700a4ffe3
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2025 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;
+
+import static android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.window.DesktopModeFlags;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.R;
+import com.android.window.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+/**
+ * Test class for {@link DesktopModeHelper}.
+ */
+@SmallTest
+@Presubmit
+@EnableFlags(Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+public class DesktopModeHelperTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private Context mContext;
+ private Context mMockContext;
+ private Resources mMockResources;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mMockContext = mock(Context.class);
+ mMockResources = mock(Resources.class);
+
+ doReturn(mMockResources).when(mMockContext).getResources();
+ doReturn(false).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+ doReturn(false).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported));
+ doReturn(mContext.getContentResolver()).when(mMockContext).getContentResolver();
+ resetDesktopModeFlagsCache();
+ resetEnforceDeviceRestriction();
+ resetFlagOverride();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ resetDesktopModeFlagsCache();
+ resetEnforceDeviceRestriction();
+ resetFlagOverride();
+ }
+
+ @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+ @Test
+ public void canEnterDesktopMode_DWFlagDisabled_configsOff_returnsFalse() {
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+ }
+
+ @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+ @Test
+ public void canEnterDesktopMode_DWFlagDisabled_configsOn_disableDeviceCheck_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported));
+ disableEnforceDeviceRestriction();
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+ }
+
+ @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+ @Test
+ public void canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_returnsFalse() {
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported));
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+ }
+
+ @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+ @Test
+ public void canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_flagOverrideOn_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported));
+ setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON);
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ public void canEnterDesktopMode_DWFlagEnabled_configsOff_returnsFalse() {
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ public void canEnterDesktopMode_DWFlagEnabled_configDesktopModeOff_returnsFalse() {
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported));
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ public void canEnterDesktopMode_DWFlagEnabled_configDesktopModeOn_returnsTrue() {
+ doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ public void canEnterDesktopMode_DWFlagEnabled_configsOff_disableDeviceRestrictions_returnsTrue()
+ throws Exception {
+ disableEnforceDeviceRestriction();
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ public void canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ );
+ setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON);
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+ }
+
+ @Test
+ public void isDeviceEligibleForDesktopMode_configDEModeOn_returnsTrue() {
+ doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ public void isDeviceEligibleForDesktopMode_supportFlagOff_returnsFalse() {
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ public void isDeviceEligibleForDesktopMode_supportFlagOn_returnsFalse() {
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ public void isDeviceEligibleForDesktopMode_supportFlagOn_configDevOptModeOn_returnsTrue() {
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ );
+
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
+ }
+
+ private void resetEnforceDeviceRestriction() throws Exception {
+ setEnforceDeviceRestriction(true);
+ }
+
+ private void disableEnforceDeviceRestriction() throws Exception {
+ setEnforceDeviceRestriction(false);
+ }
+
+ private void setEnforceDeviceRestriction(boolean value) throws Exception {
+ Field deviceRestriction = DesktopModeHelper.class.getDeclaredField(
+ "ENFORCE_DEVICE_RESTRICTIONS");
+ deviceRestriction.setAccessible(true);
+ deviceRestriction.setBoolean(/* obj= */ null, /* z= */ value);
+ }
+
+ private void resetDesktopModeFlagsCache() throws Exception {
+ Field cachedToggleOverride = DesktopModeFlags.class.getDeclaredField(
+ "sCachedToggleOverride");
+ cachedToggleOverride.setAccessible(true);
+ cachedToggleOverride.set(/* obj= */ null, /* value= */ null);
+ }
+
+ private void resetFlagOverride() {
+ Settings.Global.putString(mContext.getContentResolver(),
+ DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null);
+ }
+
+ private void setFlagOverride(DesktopModeFlags.ToggleOverride override) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.getSetting());
+ }
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
index a0e047759dab..1fb18a6bb391 100644
--- a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
+++ b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
@@ -39,13 +39,4 @@ python_test_host {
device_common_data: [
":cdm_snippet_legacy",
],
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- embedded_launcher: true,
- },
- },
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 03d6ce88ac0c..18f44ddff086 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -51,8 +51,8 @@ constructor(
instrumentation.targetContext.contentResolver,
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
0,
- UserHandle.USER_CURRENT
- );
+ UserHandle.USER_CURRENT_OR_SELF
+ )
}
private val logTag = this::class.java.simpleName
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index 83d22d923c78..61fa7b542bc0 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -18,18 +18,24 @@ package android.os.test;
import static org.junit.Assert.assertTrue;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.os.SystemClock;
+import android.os.TestLooperManager;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.ArrayDeque;
+import java.util.Queue;
import java.util.concurrent.Executor;
/**
@@ -44,7 +50,9 @@ import java.util.concurrent.Executor;
* The Robolectric class also allows advancing time.
*/
public class TestLooper {
- protected final Looper mLooper;
+ private final Looper mLooper;
+ private final TestLooperManager mTestLooperManager;
+ private final Clock mClock;
private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
private static final Field THREAD_LOCAL_LOOPER_FIELD;
@@ -54,24 +62,37 @@ public class TestLooper {
private static final Method MESSAGE_MARK_IN_USE_METHOD;
private static final String TAG = "TestLooper";
- private final Clock mClock;
-
private AutoDispatchThread mAutoDispatchThread;
+ /**
+ * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
+ */
+ private static boolean isAtLeastBaklava() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
+ }
+
static {
try {
LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
LOOPER_CONSTRUCTOR.setAccessible(true);
THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
- MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
- MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
- MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
- MESSAGE_NEXT_FIELD.setAccessible(true);
- MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
- MESSAGE_WHEN_FIELD.setAccessible(true);
- MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
- MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
+
+ if (isAtLeastBaklava()) {
+ MESSAGE_QUEUE_MESSAGES_FIELD = null;
+ MESSAGE_NEXT_FIELD = null;
+ MESSAGE_WHEN_FIELD = null;
+ MESSAGE_MARK_IN_USE_METHOD = null;
+ } else {
+ MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+ MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
+ MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+ MESSAGE_NEXT_FIELD.setAccessible(true);
+ MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+ MESSAGE_WHEN_FIELD.setAccessible(true);
+ MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
+ MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
+ }
} catch (NoSuchFieldException | NoSuchMethodException e) {
throw new RuntimeException("Failed to initialize TestLooper", e);
}
@@ -106,6 +127,13 @@ public class TestLooper {
throw new RuntimeException("Reflection error constructing or accessing looper", e);
}
+ if (isAtLeastBaklava()) {
+ mTestLooperManager =
+ InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper);
+ } else {
+ mTestLooperManager = null;
+ }
+
mClock = clock;
}
@@ -117,19 +145,72 @@ public class TestLooper {
return new HandlerExecutor(new Handler(getLooper()));
}
- private Message getMessageLinkedList() {
+ private Message getMessageLinkedListLegacy() {
try {
MessageQueue queue = mLooper.getQueue();
return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
} catch (IllegalAccessException e) {
throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages",
- e);
+ e);
}
}
public void moveTimeForward(long milliSeconds) {
+ if (isAtLeastBaklava()) {
+ moveTimeForwardBaklava(milliSeconds);
+ } else {
+ moveTimeForwardLegacy(milliSeconds);
+ }
+ }
+
+ private void moveTimeForwardBaklava(long milliSeconds) {
+ // Drain all Messages from the queue.
+ Queue<Message> messages = new ArrayDeque<>();
+ while (true) {
+ Message message = mTestLooperManager.poll();
+ if (message == null) {
+ break;
+ }
+
+ // Adjust the Message's delivery time.
+ long newWhen = message.when - milliSeconds;
+ if (newWhen < 0) {
+ newWhen = 0;
+ }
+ message.when = newWhen;
+ messages.add(message);
+ }
+
+ // Repost all Messages back to the queuewith a new time.
+ while (true) {
+ Message message = messages.poll();
+ if (message == null) {
+ break;
+ }
+
+ Runnable callback = message.getCallback();
+ Handler handler = message.getTarget();
+ long when = message.getWhen();
+
+ // The Message cannot be re-enqueued because it is marked in use.
+ // Make a copy of the Message and recycle the original.
+ // This resets {@link Message#isInUse()} but retains all other content.
+ {
+ Message newMessage = Message.obtain();
+ newMessage.copyFrom(message);
+ newMessage.setCallback(callback);
+ mTestLooperManager.recycle(message);
+ message = newMessage;
+ }
+
+ // Send the Message back to its Handler to be re-enqueued.
+ handler.sendMessageAtTime(message, when);
+ }
+ }
+
+ private void moveTimeForwardLegacy(long milliSeconds) {
try {
- Message msg = getMessageLinkedList();
+ Message msg = getMessageLinkedListLegacy();
while (msg != null) {
long updatedWhen = msg.getWhen() - milliSeconds;
if (updatedWhen < 0) {
@@ -147,12 +228,12 @@ public class TestLooper {
return mClock.uptimeMillis();
}
- private Message messageQueueNext() {
+ private Message messageQueueNextLegacy() {
try {
long now = currentTime();
Message prevMsg = null;
- Message msg = getMessageLinkedList();
+ Message msg = getMessageLinkedListLegacy();
if (msg != null && msg.getTarget() == null) {
// Stalled by a barrier. Find the next asynchronous message in
// the queue.
@@ -185,18 +266,46 @@ public class TestLooper {
/**
* @return true if there are pending messages in the message queue
*/
- public synchronized boolean isIdle() {
- Message messageList = getMessageLinkedList();
+ public boolean isIdle() {
+ if (isAtLeastBaklava()) {
+ return isIdleBaklava();
+ } else {
+ return isIdleLegacy();
+ }
+ }
+ private boolean isIdleBaklava() {
+ Long when = mTestLooperManager.peekWhen();
+ return when != null && currentTime() >= when;
+ }
+
+ private synchronized boolean isIdleLegacy() {
+ Message messageList = getMessageLinkedListLegacy();
return messageList != null && currentTime() >= messageList.getWhen();
}
/**
* @return the next message in the Looper's message queue or null if there is none
*/
- public synchronized Message nextMessage() {
+ public Message nextMessage() {
+ if (isAtLeastBaklava()) {
+ return nextMessageBaklava();
+ } else {
+ return nextMessageLegacy();
+ }
+ }
+
+ private Message nextMessageBaklava() {
if (isIdle()) {
- return messageQueueNext();
+ return mTestLooperManager.poll();
+ } else {
+ return null;
+ }
+ }
+
+ private synchronized Message nextMessageLegacy() {
+ if (isIdle()) {
+ return messageQueueNextLegacy();
} else {
return null;
}
@@ -206,9 +315,26 @@ public class TestLooper {
* Dispatch the next message in the queue
* Asserts that there is a message in the queue
*/
- public synchronized void dispatchNext() {
+ public void dispatchNext() {
+ if (isAtLeastBaklava()) {
+ dispatchNextBaklava();
+ } else {
+ dispatchNextLegacy();
+ }
+ }
+
+ private void dispatchNextBaklava() {
+ assertTrue(isIdle());
+ Message msg = mTestLooperManager.poll();
+ if (msg == null) {
+ return;
+ }
+ msg.getTarget().dispatchMessage(msg);
+ }
+
+ private synchronized void dispatchNextLegacy() {
assertTrue(isIdle());
- Message msg = messageQueueNext();
+ Message msg = messageQueueNextLegacy();
if (msg == null) {
return;
}