summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java11
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java14
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java16
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java27
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java199
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java47
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java42
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java164
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java14
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java111
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java5
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java71
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java2
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java125
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java12
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java119
-rw-r--r--libs/WindowManager/Shell/Android.bp14
-rw-r--r--libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml24
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml26
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml21
-rw-r--r--libs/WindowManager/Shell/res/drawable/ic_expand_less.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml34
-rw-r--r--libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml20
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml56
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml41
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml77
-rw-r--r--libs/WindowManager/Shell/res/layout/compat_ui_layout.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml13
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml41
-rw-r--r--libs/WindowManager/Shell/res/values/colors.xml5
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml52
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java198
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt73
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt74
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java115
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java71
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java185
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java222
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java131
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java77
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java145
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java247
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt148
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt90
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt232
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java)40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt)113
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt201
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java)11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt251
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java)40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java)45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java)49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt364
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java)6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java)36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUiEventLogger.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java)88
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt144
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java476
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java140
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java269
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java179
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java239
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java129
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java305
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java224
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java194
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt618
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java336
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt154
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java69
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java165
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java104
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java94
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java367
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java137
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java110
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java535
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java210
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java140
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java155
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java272
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java184
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt39
-rw-r--r--libs/WindowManager/Shell/tests/flicker/Android.bp147
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTest.xml53
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml101
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml (renamed from libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml)12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt108
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt108
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt26
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt128
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt275
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt99
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt63
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt15
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt88
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt15
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt15
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt19
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt15
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt19
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt26
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt53
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt34
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt34
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt34
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt34
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt40
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt40
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt67
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt80
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt66
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt65
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt70
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt72
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt83
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt70
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt73
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt151
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt70
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt69
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt69
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt72
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt69
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt42
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt22
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt40
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt29
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt26
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt42
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt49
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt69
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt29
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt36
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt35
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt39
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt37
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt29
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt29
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt)78
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MultiWindowUtils.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/NotificationListener.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt)27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/WaitUtils.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto75
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java)8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt201
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java335
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java67
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt172
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java123
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java218
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java155
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java390
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt91
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt165
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java23
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java)58
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java35
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java23
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java41
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java83
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java41
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java280
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java48
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt61
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt63
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java17
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt6
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt10
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt54
-rw-r--r--libs/dream/lowlight/tests/Android.bp1
-rw-r--r--libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt15
-rw-r--r--libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt20
-rw-r--r--libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt53
-rw-r--r--libs/hwui/Android.bp1
-rw-r--r--libs/hwui/effects/GainmapRenderer.cpp5
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.cpp52
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.h54
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp41
-rw-r--r--libs/hwui/renderthread/CanvasContext.h8
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.cpp128
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.h36
-rw-r--r--libs/hwui/tests/unit/HintSessionWrapperTests.cpp287
-rw-r--r--libs/hwui/tests/unit/ShaderCacheTests.cpp11
464 files changed, 18765 insertions, 5251 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 76e0e1eb7a95..9da6c10c6d74 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -19,7 +19,6 @@ package androidx.window.extensions;
import android.app.ActivityThread;
import android.app.Application;
import android.content.Context;
-import android.window.TaskFragmentOrganizer;
import androidx.annotation.NonNull;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
@@ -48,7 +47,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 3;
+ return 4;
}
@NonNull
@@ -81,13 +80,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
Context context = getApplication();
DeviceStateManagerFoldingFeatureProducer producer =
getFoldingFeatureProducer();
- // TODO(b/263263909) Use the organizer to tell if an Activity is embededed.
- // Need to improve our Dependency Injection and centralize the logic.
- TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(command -> {
- throw new RuntimeException("Not allowed!");
- });
- mWindowLayoutComponent = new WindowLayoutComponentImpl(context, organizer,
- producer);
+ mWindowLayoutComponent = new WindowLayoutComponentImpl(context, producer);
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 1e6e50359cf3..15d14e87fcf6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -213,9 +213,6 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
if (mRearDisplayStateRequest != null || isRearDisplayActive()) {
mRearDisplayStateRequest = null;
mDeviceStateManager.cancelStateRequest();
- } else {
- throw new IllegalStateException(
- "Unable to cancel a rear display session as there is no active session");
}
}
}
@@ -432,10 +429,6 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
synchronized (mLock) {
if (mRearDisplayPresentationController != null) {
mDeviceStateManager.cancelStateRequest();
- } else {
- throw new IllegalStateException(
- "Unable to cancel a rear display presentation session as there is no "
- + "active session");
}
}
}
@@ -518,8 +511,11 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
return WindowAreaComponent.STATUS_UNSUPPORTED;
}
- if (mCurrentDeviceState == mConcurrentDisplayState
- || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState)
+ if (mCurrentDeviceState == mConcurrentDisplayState) {
+ return WindowAreaComponent.STATUS_ACTIVE;
+ }
+
+ if (!ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState)
|| isDeviceFolded()) {
return WindowAreaComponent.STATUS_UNAVAILABLE;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index d94e8e426c4b..4d73c20fe39f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -17,7 +17,9 @@
package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
@@ -340,6 +342,20 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.deleteTaskFragment(fragmentToken);
}
+ void reorderTaskFragmentToFront(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_REORDER_TO_FRONT).build();
+ wct.addTaskFragmentOperation(fragmentToken, operation);
+ }
+
+ void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, boolean isolatedNav) {
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_ISOLATED_NAVIGATION).setIsolatedNav(isolatedNav).build();
+ wct.addTaskFragmentOperation(fragmentToken, operation);
+ }
+
void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 18497ad249ee..381e9d472f0f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -32,7 +32,7 @@ import androidx.window.extensions.core.util.function.Function;
*/
class SplitContainer {
@NonNull
- private final TaskFragmentContainer mPrimaryContainer;
+ private TaskFragmentContainer mPrimaryContainer;
@NonNull
private final TaskFragmentContainer mSecondaryContainer;
@NonNull
@@ -46,17 +46,35 @@ class SplitContainer {
@NonNull
private final IBinder mToken;
+ /**
+ * Whether the selection of which container is primary can be changed at runtime. Runtime
+ * updates is currently possible only for {@link SplitPinContainer}
+ *
+ * @see SplitPinContainer
+ */
+ private final boolean mIsPrimaryContainerMutable;
+
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
@NonNull Activity primaryActivity,
@NonNull TaskFragmentContainer secondaryContainer,
@NonNull SplitRule splitRule,
@NonNull SplitAttributes splitAttributes) {
+ this(primaryContainer, primaryActivity, secondaryContainer, splitRule, splitAttributes,
+ false /* isPrimaryContainerMutable */);
+ }
+
+ SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
+ @NonNull Activity primaryActivity,
+ @NonNull TaskFragmentContainer secondaryContainer,
+ @NonNull SplitRule splitRule,
+ @NonNull SplitAttributes splitAttributes, boolean isPrimaryContainerMutable) {
mPrimaryContainer = primaryContainer;
mSecondaryContainer = secondaryContainer;
mSplitRule = splitRule;
mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes();
mCurrentSplitAttributes = splitAttributes;
mToken = new Binder("SplitContainer");
+ mIsPrimaryContainerMutable = isPrimaryContainerMutable;
if (shouldFinishPrimaryWithSecondary(splitRule)) {
if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -74,6 +92,13 @@ class SplitContainer {
}
}
+ void setPrimaryContainer(@NonNull TaskFragmentContainer primaryContainer) {
+ if (!mIsPrimaryContainerMutable) {
+ throw new IllegalStateException("Cannot update primary TaskFragmentContainer");
+ }
+ mPrimaryContainer = primaryContainer;
+ }
+
@NonNull
TaskFragmentContainer getPrimaryContainer() {
return mPrimaryContainer;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 89f4890c254e..3ad30457697e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -213,6 +213,93 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@Override
+ public boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) {
+ synchronized (mLock) {
+ final TaskContainer task = getTaskContainer(taskId);
+ if (task == null) {
+ Log.e(TAG, "Cannot find the task for id: " + taskId);
+ return false;
+ }
+
+ final TaskFragmentContainer topContainer =
+ task.getTopNonFinishingTaskFragmentContainer();
+ // Cannot pin the TaskFragment if no other TaskFragment behind it.
+ if (topContainer == null || task.indexOf(topContainer) <= 0) {
+ Log.w(TAG, "Cannot find an ActivityStack to pin or split");
+ return false;
+ }
+ // Abort if the top container is already pinned.
+ if (task.getSplitPinContainer() != null) {
+ Log.w(TAG, "There is already a pinned ActivityStack.");
+ return false;
+ }
+
+ // Find a valid adjacent TaskFragmentContainer
+ final TaskFragmentContainer primaryContainer =
+ task.getNonFinishingTaskFragmentContainerBelow(topContainer);
+ if (primaryContainer == null) {
+ Log.w(TAG, "Cannot find another ActivityStack to split");
+ return false;
+ }
+
+ // Abort if no space to split.
+ final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
+ task.getTaskProperties(), splitPinRule,
+ splitPinRule.getDefaultSplitAttributes(),
+ getActivitiesMinDimensionsPair(primaryContainer.getTopNonFinishingActivity(),
+ topContainer.getTopNonFinishingActivity()));
+ if (!SplitPresenter.shouldShowSplit(calculatedSplitAttributes)) {
+ Log.w(TAG, "No space to split, abort pinning top ActivityStack.");
+ return false;
+ }
+
+ // Registers a Split
+ final SplitPinContainer splitPinContainer = new SplitPinContainer(primaryContainer,
+ topContainer, splitPinRule, calculatedSplitAttributes);
+ task.addSplitContainer(splitPinContainer);
+
+ // Updates the Split
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+ mPresenter.updateSplitContainer(splitPinContainer, wct);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ updateCallbackIfNecessary();
+ return true;
+ }
+ }
+
+ @Override
+ public void unpinTopActivityStack(int taskId){
+ synchronized (mLock) {
+ final TaskContainer task = getTaskContainer(taskId);
+ if (task == null) {
+ Log.e(TAG, "Cannot find the task to unpin, id: " + taskId);
+ return;
+ }
+
+ final SplitPinContainer splitPinContainer = task.getSplitPinContainer();
+ if (splitPinContainer == null) {
+ Log.e(TAG, "No ActivityStack is pinned.");
+ return;
+ }
+
+ // Remove the SplitPinContainer from the task.
+ final TaskFragmentContainer containerToUnpin =
+ splitPinContainer.getSecondaryContainer();
+ task.removeSplitPinContainer();
+
+ // Resets the isolated navigation and updates the container.
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+ mPresenter.setTaskFragmentIsolatedNavigation(wct,
+ containerToUnpin.getTaskFragmentToken(), false /* isolated */);
+ updateContainer(wct, containerToUnpin);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ updateCallbackIfNecessary();
+ }
+ }
+
+ @Override
public void setSplitAttributesCalculator(
@NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) {
synchronized (mLock) {
@@ -308,7 +395,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
forAllTaskContainers(taskContainer -> {
synchronized (mLock) {
- final List<TaskFragmentContainer> containers = taskContainer.mContainers;
+ final List<TaskFragmentContainer> containers =
+ taskContainer.getTaskFragmentContainers();
// Clean up the TaskFragmentContainers by the z-order from the lowest.
for (int i = 0; i < containers.size(); i++) {
final TaskFragmentContainer container = containers.get(i);
@@ -611,8 +699,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@NonNull TaskContainer taskContainer) {
// Update all TaskFragments in the Task. Make a copy of the list since some may be
// removed on updating.
- final List<TaskFragmentContainer> containers =
- new ArrayList<>(taskContainer.mContainers);
+ final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
for (int i = containers.size() - 1; i >= 0; i--) {
final TaskFragmentContainer container = containers.get(i);
// Wait until onTaskFragmentAppeared to update new container.
@@ -672,7 +759,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (targetContainer == null) {
// When there is no embedding rule matched, try to place it in the top container
// like a normal launch.
- targetContainer = taskContainer.getTopTaskFragmentContainer();
+ targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
}
if (targetContainer == null) {
return;
@@ -777,7 +864,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
- if (!isOnReparent && getContainerWithActivity(activity) == null
+ final TaskFragmentContainer container = getContainerWithActivity(activity);
+ if (!isOnReparent && container == null
&& getTaskFragmentTokenFromActivityClientRecord(activity) != null) {
// We can't find the new launched activity in any recorded container, but it is
// currently placed in an embedded TaskFragment. This can happen in two cases:
@@ -789,10 +877,21 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
- final TaskFragmentContainer container = getContainerWithActivity(activity);
- if (!isOnReparent && container != null
- && container.getTaskContainer().getTopTaskFragmentContainer() != container) {
- // Do not resolve if the launched activity is not the top-most container in the Task.
+ // Skip resolving if the activity is on a pinned TaskFragmentContainer.
+ // TODO(b/243518738): skip resolving for overlay container.
+ if (container != null) {
+ final TaskContainer taskContainer = container.getTaskContainer();
+ if (taskContainer.isTaskFragmentContainerPinned(container)) {
+ return true;
+ }
+ }
+
+ final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
+ if (!isOnReparent && taskContainer != null
+ && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
+ != container) {
+ // Do not resolve if the launched activity is not the top-most container (excludes
+ // the pinned container) in the Task.
return true;
}
@@ -888,7 +987,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (taskContainer == null) {
return;
}
- final TaskFragmentContainer targetContainer = taskContainer.getTopTaskFragmentContainer();
+ final TaskFragmentContainer targetContainer =
+ taskContainer.getTopNonFinishingTaskFragmentContainer();
if (targetContainer == null) {
return;
}
@@ -1188,6 +1288,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
+ // Skip resolving if started from pinned TaskFragmentContainer.
+ // TODO(b/243518738): skip resolving for overlay container.
+ if (launchingActivity != null) {
+ final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
+ launchingActivity);
+ final TaskContainer taskContainer =
+ taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
+ if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
+ taskFragmentContainer)) {
+ return null;
+ }
+ }
+
/*
* We will check the following to see if there is any embedding rule matched:
* 1. Whether the new activity intent should always expand.
@@ -1213,11 +1326,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// 3. Whether the top activity (if any) should be split with the new activity intent.
final TaskContainer taskContainer = getTaskContainer(taskId);
- if (taskContainer == null || taskContainer.getTopTaskFragmentContainer() == null) {
+ if (taskContainer == null
+ || taskContainer.getTopNonFinishingTaskFragmentContainer() == null) {
// There is no other activity in the Task to check split with.
return null;
}
- final TaskFragmentContainer topContainer = taskContainer.getTopTaskFragmentContainer();
+ final TaskFragmentContainer topContainer =
+ taskContainer.getTopNonFinishingTaskFragmentContainer();
final Activity topActivity = topContainer.getTopNonFinishingActivity();
if (topActivity != null && topActivity != launchingActivity) {
final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
@@ -1331,7 +1446,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Check pending appeared activity first because there can be a delay for the server
// update.
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+ .getTaskFragmentContainers();
for (int j = containers.size() - 1; j >= 0; j--) {
final TaskFragmentContainer container = containers.get(j);
if (container.hasPendingAppearedActivity(activityToken)) {
@@ -1342,7 +1458,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Check appeared activity if there is no such pending appeared activity.
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+ .getTaskFragmentContainers();
for (int j = containers.size() - 1; j >= 0; j--) {
final TaskFragmentContainer container = containers.get(j);
if (container.hasAppearedActivity(activityToken)) {
@@ -1418,7 +1535,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
removeExistingSecondaryContainers(wct, primaryContainer);
}
- primaryContainer.getTaskContainer().mSplitContainers.add(splitContainer);
+ primaryContainer.getTaskContainer().addSplitContainer(splitContainer);
}
/** Cleanups all the dependencies when the TaskFragment is entering PIP. */
@@ -1430,8 +1547,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return;
}
final List<SplitContainer> splitsToRemove = new ArrayList<>();
+ final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>();
- for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
+ for (SplitContainer splitContainer : splitContainers) {
if (splitContainer.getPrimaryContainer() != container
&& splitContainer.getSecondaryContainer() != container) {
continue;
@@ -1449,7 +1567,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
container.resetDependencies();
- taskContainer.mSplitContainers.removeAll(splitsToRemove);
+ taskContainer.removeSplitContainers(splitsToRemove);
// If there is any TaskFragment split with the PIP TaskFragment, update their presentations
// since the split is dismissed.
// We don't want to close any of them even if they are dependencies of the PIP TaskFragment.
@@ -1471,7 +1589,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
void removeContainers(@NonNull TaskContainer taskContainer,
@NonNull List<TaskFragmentContainer> containers) {
// Remove all split containers that included this one
- taskContainer.mContainers.removeAll(containers);
+ taskContainer.removeTaskFragmentContainers(containers);
// Marked as a pending removal which will be removed after it is actually removed on the
// server side (#onTaskFragmentVanished).
// In this way, we can keep track of the Task bounds until we no longer have any
@@ -1481,7 +1599,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Cleanup any split references.
final List<SplitContainer> containersToRemove = new ArrayList<>();
- for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
+ final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
+ for (SplitContainer splitContainer : splitContainers) {
if (containersToRemove.contains(splitContainer)) {
// Don't need to check because it has been in the remove list.
continue;
@@ -1492,10 +1611,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
containersToRemove.add(splitContainer);
}
}
- taskContainer.mSplitContainers.removeAll(containersToRemove);
+ taskContainer.removeSplitContainers(containersToRemove);
// Cleanup any dependent references.
- for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
+ final List<TaskFragmentContainer> taskFragmentContainers =
+ taskContainer.getTaskFragmentContainers();
+ for (TaskFragmentContainer containerToUpdate : taskFragmentContainers) {
containerToUpdate.removeContainersToFinishOnExit(containers);
}
}
@@ -1520,6 +1641,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return;
}
+ // If the secondary container is pinned, it should not be removed.
+ final SplitContainer activeContainer =
+ getActiveSplitForContainer(existingSplitContainer.getSecondaryContainer());
+ if (activeContainer instanceof SplitPinContainer) {
+ return;
+ }
+
existingSplitContainer.getSecondaryContainer().finish(
false /* shouldFinishDependent */, mPresenter, wct, this);
}
@@ -1534,8 +1662,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (taskContainer == null) {
return null;
}
- for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) {
- final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+ final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
+ for (int i = containers.size() - 1; i >= 0; i--) {
+ final TaskFragmentContainer container = containers.get(i);
if (!container.isFinished() && (container.getRunningActivityCount() > 0
// We may be waiting for the top TaskFragment to become non-empty after
// creation. In that case, we don't want to treat the TaskFragment below it as
@@ -1560,6 +1689,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// background.
return;
}
+
if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
@@ -1572,7 +1702,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// If the info is not available yet the task fragment will be expanded when it's ready
return;
}
- SplitContainer splitContainer = getActiveSplitForContainer(container);
+ final SplitContainer splitContainer = getActiveSplitForContainer(container);
if (splitContainer == null) {
return;
}
@@ -1629,7 +1759,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** Whether the given split is the topmost split in the Task. */
private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) {
final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer()
- .getTaskContainer().mSplitContainers;
+ .getTaskContainer().getSplitContainers();
return splitContainer == splitContainers.get(splitContainers.size() - 1);
}
@@ -1641,7 +1771,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (container == null) {
return null;
}
- final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
+ final List<SplitContainer> splitContainers =
+ container.getTaskContainer().getSplitContainers();
if (splitContainers.isEmpty()) {
return null;
}
@@ -1665,7 +1796,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@NonNull TaskFragmentContainer firstContainer,
@NonNull TaskFragmentContainer secondContainer) {
final List<SplitContainer> splitContainers = firstContainer.getTaskContainer()
- .mSplitContainers;
+ .getSplitContainers();
for (int i = splitContainers.size() - 1; i >= 0; i--) {
final SplitContainer splitContainer = splitContainers.get(i);
final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
@@ -1755,6 +1886,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Don't launch placeholder for primary split container.
return false;
}
+ if (splitContainer instanceof SplitPinContainer) {
+ // Don't launch placeholder if pinned
+ return false;
+ }
return true;
}
@@ -1930,7 +2065,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+ .getTaskFragmentContainers();
for (TaskFragmentContainer container : containers) {
if (container.getTaskFragmentToken().equals(fragmentToken)) {
return container;
@@ -1945,7 +2081,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
SplitContainer getSplitContainer(@NonNull IBinder token) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<SplitContainer> containers = mTaskContainers.valueAt(i).mSplitContainers;
+ final List<SplitContainer> containers = mTaskContainers.valueAt(i).getSplitContainers();
for (SplitContainer container : containers) {
if (container.getToken().equals(token)) {
return container;
@@ -2000,8 +2136,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Returns {@code true} if an Activity with the provided component name should always be
* expanded to occupy full task bounds. Such activity must not be put in a split.
*/
+ @VisibleForTesting
@GuardedBy("mLock")
- private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
+ boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
for (EmbeddingRule rule : mSplitRules) {
if (!(rule instanceof ActivityRule)) {
continue;
@@ -2091,7 +2228,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
- .mContainers;
+ .getTaskFragmentContainers();
for (int j = containers.size() - 1; j >= 0; j--) {
final TaskFragmentContainer container = containers.get(j);
if (!container.hasActivity(activityToken)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java
new file mode 100644
index 000000000000..03c77a089012
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java
@@ -0,0 +1,47 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Client-side descriptor of a split that holds two containers while the secondary
+ * container is pinned on top of the Task and the primary container is the container that is
+ * currently below the secondary container. The primary container could be updated to
+ * another container whenever the existing primary container is removed or no longer
+ * be the container that's right behind the secondary container.
+ */
+class SplitPinContainer extends SplitContainer {
+
+ SplitPinContainer(@NonNull TaskFragmentContainer primaryContainer,
+ @NonNull TaskFragmentContainer secondaryContainer,
+ @NonNull SplitPinRule splitPinRule,
+ @NonNull SplitAttributes splitAttributes) {
+ super(primaryContainer, primaryContainer.getTopNonFinishingActivity(), secondaryContainer,
+ splitPinRule, splitAttributes, true /* isPrimaryContainerMutable */);
+ }
+
+ @Override
+ public String toString() {
+ return "SplitPinContainer{"
+ + " primaryContainer=" + getPrimaryContainer()
+ + " secondaryContainer=" + getSecondaryContainer()
+ + " splitPinRule=" + getSplitRule()
+ + " splitAttributes" + getCurrentSplitAttributes()
+ + "}";
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 53d39d9fa28e..d894487fafb6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -336,10 +336,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// value.
final SplitRule rule = splitContainer.getSplitRule();
final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
- final Activity activity = primaryContainer.getTopNonFinishingActivity();
- if (activity == null) {
- return;
- }
final TaskContainer taskContainer = splitContainer.getTaskContainer();
final TaskProperties taskProperties = taskContainer.getTaskProperties();
final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
@@ -386,6 +382,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(),
secondaryContainer.getTaskFragmentToken(), splitRule, isStacked);
+
+ // Setting isolated navigation and clear non-sticky pinned container if needed.
+ final SplitPinRule splitPinRule =
+ splitRule instanceof SplitPinRule ? (SplitPinRule) splitRule : null;
+ if (splitPinRule == null) {
+ return;
+ }
+
+ setTaskFragmentIsolatedNavigation(wct, secondaryContainer.getTaskFragmentToken(),
+ !isStacked /* isolatedNav */);
+ if (isStacked && !splitPinRule.isSticky()) {
+ secondaryContainer.getTaskContainer().removeSplitPinContainer();
+ }
}
/**
@@ -424,6 +433,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
container.setLastRequestedBounds(fragmentOptions.getInitialRelativeBounds());
container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode());
super.createTaskFragment(wct, fragmentOptions);
+
+ // Reorders the pinned TaskFragment to front to ensure it is the front-most TaskFragment.
+ final SplitPinContainer pinnedContainer =
+ container.getTaskContainer().getSplitPinContainer();
+ if (pinnedContainer != null) {
+ reorderTaskFragmentToFront(wct,
+ pinnedContainer.getSecondaryContainer().getTaskFragmentToken());
+ }
}
@Override
@@ -634,7 +651,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
if (minDimensionsPair == null) {
return splitAttributes;
}
- final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+ final FoldingFeature foldingFeature = getFoldingFeatureForHingeType(
+ taskProperties, splitAttributes);
final Configuration taskConfiguration = taskProperties.getConfiguration();
final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes,
foldingFeature);
@@ -709,7 +727,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
Rect getRelBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties,
@NonNull SplitAttributes splitAttributes) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
- final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+ final FoldingFeature foldingFeature = getFoldingFeatureForHingeType(
+ taskProperties, splitAttributes);
if (!shouldShowSplit(splitAttributes)) {
return new Rect();
}
@@ -916,6 +935,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@Nullable
+ private FoldingFeature getFoldingFeatureForHingeType(
+ @NonNull TaskProperties taskProperties,
+ @NonNull SplitAttributes splitAttributes) {
+ SplitType splitType = splitAttributes.getSplitType();
+ if (!(splitType instanceof HingeSplitType)) {
+ return null;
+ }
+ return getFoldingFeature(taskProperties);
+ }
+
+ @Nullable
@VisibleForTesting
FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
final int displayId = taskProperties.getDisplayId();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 4b15bb187035..463c8ceaf992 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -51,11 +51,15 @@ class TaskContainer {
/** Active TaskFragments in this Task. */
@NonNull
- final List<TaskFragmentContainer> mContainers = new ArrayList<>();
+ private final List<TaskFragmentContainer> mContainers = new ArrayList<>();
/** Active split pairs in this Task. */
@NonNull
- final List<SplitContainer> mSplitContainers = new ArrayList<>();
+ private final List<SplitContainer> mSplitContainers = new ArrayList<>();
+
+ /** Active pin split pair in this Task. */
+ @Nullable
+ private SplitPinContainer mSplitPinContainer;
@NonNull
private final Configuration mConfiguration;
@@ -174,11 +178,36 @@ class TaskContainer {
}
@Nullable
- TaskFragmentContainer getTopTaskFragmentContainer() {
- if (mContainers.isEmpty()) {
- return null;
+ TaskFragmentContainer getTopNonFinishingTaskFragmentContainer() {
+ return getTopNonFinishingTaskFragmentContainer(true /* includePin */);
+ }
+
+ @Nullable
+ TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
+ for (int i = mContainers.size() - 1; i >= 0; i--) {
+ final TaskFragmentContainer container = mContainers.get(i);
+ if (!includePin && isTaskFragmentContainerPinned(container)) {
+ continue;
+ }
+ if (!container.isFinished()) {
+ return container;
+ }
+ }
+ return null;
+ }
+
+ /** Gets a non-finishing container below the given one. */
+ @Nullable
+ TaskFragmentContainer getNonFinishingTaskFragmentContainerBelow(
+ @NonNull TaskFragmentContainer current) {
+ final int index = mContainers.indexOf(current);
+ for (int i = index - 1; i >= 0; i--) {
+ final TaskFragmentContainer container = mContainers.get(i);
+ if (!container.isFinished()) {
+ return container;
+ }
}
- return mContainers.get(mContainers.size() - 1);
+ return null;
}
@Nullable
@@ -207,6 +236,129 @@ class TaskContainer {
return false;
}
+ /**
+ * Returns a list of {@link SplitContainer}. Do not modify the containers directly on the
+ * returned list. Use {@link #addSplitContainer} or {@link #removeSplitContainers} instead.
+ */
+ @NonNull
+ List<SplitContainer> getSplitContainers() {
+ return mSplitContainers;
+ }
+
+ void addSplitContainer(@NonNull SplitContainer splitContainer) {
+ if (splitContainer instanceof SplitPinContainer) {
+ mSplitPinContainer = (SplitPinContainer) splitContainer;
+ mSplitContainers.add(splitContainer);
+ return;
+ }
+
+ // Keeps the SplitPinContainer on the top of the list.
+ mSplitContainers.remove(mSplitPinContainer);
+ mSplitContainers.add(splitContainer);
+ if (mSplitPinContainer != null) {
+ mSplitContainers.add(mSplitPinContainer);
+ }
+ }
+
+ void removeSplitContainers(@NonNull List<SplitContainer> containers) {
+ mSplitContainers.removeAll(containers);
+ }
+
+ void removeSplitPinContainer() {
+ if (mSplitPinContainer == null) {
+ return;
+ }
+
+ final TaskFragmentContainer primaryContainer = mSplitPinContainer.getPrimaryContainer();
+ final TaskFragmentContainer secondaryContainer = mSplitPinContainer.getSecondaryContainer();
+ mSplitContainers.remove(mSplitPinContainer);
+ mSplitPinContainer = null;
+
+ // Remove the other SplitContainers that contains the unpinned container (unless it
+ // is the current top-most split-pair), since the state are no longer valid.
+ final List<SplitContainer> splitsToRemove = new ArrayList<>();
+ for (SplitContainer splitContainer : mSplitContainers) {
+ if (splitContainer.getSecondaryContainer().equals(secondaryContainer)
+ && !splitContainer.getPrimaryContainer().equals(primaryContainer)) {
+ splitsToRemove.add(splitContainer);
+ }
+ }
+ removeSplitContainers(splitsToRemove);
+ }
+
+ @Nullable
+ SplitPinContainer getSplitPinContainer() {
+ return mSplitPinContainer;
+ }
+
+ boolean isTaskFragmentContainerPinned(@NonNull TaskFragmentContainer taskFragmentContainer) {
+ return mSplitPinContainer != null
+ && mSplitPinContainer.getSecondaryContainer() == taskFragmentContainer;
+ }
+
+ void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
+ mContainers.add(taskFragmentContainer);
+ onTaskFragmentContainerUpdated();
+ }
+
+ void addTaskFragmentContainer(int index, @NonNull TaskFragmentContainer taskFragmentContainer) {
+ mContainers.add(index, taskFragmentContainer);
+ onTaskFragmentContainerUpdated();
+ }
+
+ void removeTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
+ mContainers.remove(taskFragmentContainer);
+ onTaskFragmentContainerUpdated();
+ }
+
+ void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
+ mContainers.removeAll(taskFragmentContainer);
+ onTaskFragmentContainerUpdated();
+ }
+
+ void clearTaskFragmentContainer() {
+ mContainers.clear();
+ onTaskFragmentContainerUpdated();
+ }
+
+ /**
+ * Returns a list of {@link TaskFragmentContainer}. Do not modify the containers directly on
+ * the returned list. Use {@link #addTaskFragmentContainer},
+ * {@link #removeTaskFragmentContainer} or other related methods instead.
+ */
+ @NonNull
+ List<TaskFragmentContainer> getTaskFragmentContainers() {
+ return mContainers;
+ }
+
+ private void onTaskFragmentContainerUpdated() {
+ if (mSplitPinContainer == null) {
+ return;
+ }
+
+ final TaskFragmentContainer pinnedContainer = mSplitPinContainer.getSecondaryContainer();
+ final int pinnedContainerIndex = mContainers.indexOf(pinnedContainer);
+ if (pinnedContainerIndex <= 0) {
+ removeSplitPinContainer();
+ return;
+ }
+
+ // Ensure the pinned container is top-most.
+ if (pinnedContainerIndex != mContainers.size() - 1) {
+ mContainers.remove(pinnedContainer);
+ mContainers.add(pinnedContainer);
+ }
+
+ // Update the primary container adjacent to the pinned container if needed.
+ final TaskFragmentContainer adjacentContainer =
+ getNonFinishingTaskFragmentContainerBelow(pinnedContainer);
+ if (adjacentContainer == null) {
+ removeSplitPinContainer();
+ } else if (mSplitPinContainer.getPrimaryContainer() != adjacentContainer) {
+ mSplitPinContainer.setPrimaryContainer(adjacentContainer);
+ }
+ }
+
/** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
for (SplitContainer container : mSplitContainers) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 60be9d16d749..61df335515b8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -180,23 +180,25 @@ class TaskFragmentContainer {
throw new IllegalArgumentException(
"pairedPrimaryContainer must be in the same Task");
}
- final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
- taskContainer.mContainers.add(primaryIndex + 1, this);
+ final int primaryIndex = taskContainer.indexOf(pairedPrimaryContainer);
+ taskContainer.addTaskFragmentContainer(primaryIndex + 1, this);
} else if (pendingAppearedActivity != null) {
// The TaskFragment will be positioned right above the pending appeared Activity. If any
// existing TaskFragment is empty with pending Intent, it is likely that the Activity of
// the pending Intent hasn't been created yet, so the new Activity should be below the
// empty TaskFragment.
- int i = taskContainer.mContainers.size() - 1;
+ final List<TaskFragmentContainer> containers =
+ taskContainer.getTaskFragmentContainers();
+ int i = containers.size() - 1;
for (; i >= 0; i--) {
- final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+ final TaskFragmentContainer container = containers.get(i);
if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
break;
}
}
- taskContainer.mContainers.add(i + 1, this);
+ taskContainer.addTaskFragmentContainer(i + 1, this);
} else {
- taskContainer.mContainers.add(this);
+ taskContainer.addTaskFragmentContainer(this);
}
if (pendingAppearedActivity != null) {
addPendingAppearedActivity(pendingAppearedActivity);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a45a8a183ac8..a1fe7f75a826 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -17,7 +17,6 @@
package androidx.window.extensions.layout;
import static android.view.Display.DEFAULT_DISPLAY;
-
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
import static androidx.window.util.ExtensionHelper.isZero;
@@ -25,7 +24,7 @@ import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
import android.app.Activity;
-import android.app.ActivityClient;
+import android.app.ActivityThread;
import android.app.Application;
import android.app.WindowConfiguration;
import android.content.ComponentCallbacks;
@@ -35,8 +34,7 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
-import android.view.WindowManager;
-import android.window.TaskFragmentOrganizer;
+import android.util.Log;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
@@ -52,7 +50,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
/**
@@ -64,7 +61,7 @@ import java.util.Set;
* Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead.
*/
public class WindowLayoutComponentImpl implements WindowLayoutComponent {
- private static final String TAG = "SampleExtension";
+ private static final String TAG = WindowLayoutComponentImpl.class.getSimpleName();
private final Object mLock = new Object();
@@ -86,16 +83,15 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private final Map<java.util.function.Consumer<WindowLayoutInfo>, Consumer<WindowLayoutInfo>>
mJavaToExtConsumers = new ArrayMap<>();
- private final TaskFragmentOrganizer mTaskFragmentOrganizer;
+ private final RawConfigurationChangedListener mRawConfigurationChangedListener =
+ new RawConfigurationChangedListener();
public WindowLayoutComponentImpl(@NonNull Context context,
- @NonNull TaskFragmentOrganizer taskFragmentOrganizer,
@NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
mFoldingFeatureProducer = foldingFeatureProducer;
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
- mTaskFragmentOrganizer = taskFragmentOrganizer;
}
/** Registers to listen to {@link CommonFoldingFeature} changes */
@@ -118,6 +114,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
final Consumer<WindowLayoutInfo> extConsumer = consumer::accept;
synchronized (mLock) {
mJavaToExtConsumers.put(consumer, extConsumer);
+ updateListenerRegistrations();
}
addWindowLayoutInfoListener(activity, extConsumer);
}
@@ -171,6 +168,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
final Consumer<WindowLayoutInfo> extConsumer;
synchronized (mLock) {
extConsumer = mJavaToExtConsumers.remove(consumer);
+ updateListenerRegistrations();
}
if (extConsumer != null) {
removeWindowLayoutInfoListener(extConsumer);
@@ -201,6 +199,17 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
@GuardedBy("mLock")
+ private void updateListenerRegistrations() {
+ ActivityThread currentThread = ActivityThread.currentActivityThread();
+ if (mJavaToExtConsumers.isEmpty()) {
+ currentThread.removeConfigurationChangedListener(mRawConfigurationChangedListener);
+ } else {
+ currentThread.addConfigurationChangedListener(Runnable::run,
+ mRawConfigurationChangedListener);
+ }
+ }
+
+ @GuardedBy("mLock")
@NonNull
private Set<Context> getContextsListeningForLayoutChanges() {
return mWindowLayoutChangeListeners.keySet();
@@ -304,7 +313,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* {@link IllegalArgumentException} since this can cause negative UI effects down stream.
*
* @param context a proxy for the {@link android.view.Window} that contains the
- * {@link DisplayFeature}.
+ * {@link DisplayFeature}.
* @return a {@link List} of {@link DisplayFeature}s that are within the
* {@link android.view.Window} of the {@link Activity}
*/
@@ -327,19 +336,48 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return features;
}
+ // We will transform the feature bounds to the Activity window, so using the rotation
+ // from the same source (WindowConfiguration) to make sure they are synchronized.
+ final int rotation = windowConfiguration.getDisplayRotation();
+
for (CommonFoldingFeature baseFeature : storedFeatures) {
Integer state = convertToExtensionState(baseFeature.getState());
if (state == null) {
continue;
}
Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
+ rotateRectToDisplayRotation(displayId, rotation, featureRect);
transformToWindowSpaceRect(windowConfiguration, featureRect);
- if (!isZero(featureRect)) {
+ if (isZero(featureRect)) {
// TODO(b/228641877): Remove guarding when fixed.
- features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
+ continue;
+ }
+ if (featureRect.left != 0 && featureRect.top != 0) {
+ Log.wtf(TAG, "Bounding rectangle must start at the top or "
+ + "left of the window. BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
+ continue;
+
+ }
+ if (featureRect.left == 0
+ && featureRect.width() != windowConfiguration.getBounds().width()) {
+ Log.wtf(TAG, "Horizontal FoldingFeature must have full width."
+ + " BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
+ continue;
+ }
+ if (featureRect.top == 0
+ && featureRect.height() != windowConfiguration.getBounds().height()) {
+ Log.wtf(TAG, "Vertical FoldingFeature must have full height."
+ + " BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
+ continue;
}
+ features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
}
return features;
}
@@ -357,38 +395,11 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
// Display features are not supported on secondary displays.
return false;
}
- final int windowingMode;
- IBinder activityToken = context.getActivityToken();
- if (activityToken != null) {
- final Configuration taskConfig = ActivityClient.getInstance().getTaskConfiguration(
- activityToken);
- if (taskConfig == null) {
- // If we cannot determine the task configuration for any reason, it is likely that
- // we won't be able to determine its position correctly as well. DisplayFeatures'
- // bounds in this case can't be computed correctly, so we should skip.
- return false;
- }
- final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
- final WindowManager windowManager = Objects.requireNonNull(
- context.getSystemService(WindowManager.class));
- final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds();
- boolean isTaskExpanded = maxBounds.equals(taskBounds);
- /*
- * We need to proxy being in full screen because when a user enters PiP and exits PiP
- * the task windowingMode will report multi-window/pinned until the transition is
- * finished in WM Shell.
- * maxBounds == taskWindowBounds is a proxy check to verify the window is full screen
- */
- return isTaskExpanded;
- } else {
- // TODO(b/242674941): use task windowing mode for window context that associates with
- // activity.
- windowingMode = context.getResources().getConfiguration().windowConfiguration
- .getWindowingMode();
- }
- // It is recommended not to report any display features in multi-window mode, since it
- // won't be possible to synchronize the display feature positions with window movement.
- return !WindowConfiguration.inMultiWindowMode(windowingMode);
+
+ // We do not report folding features for Activities in PiP because the bounds are
+ // not updated fast enough and the window is too small for the UI to adapt.
+ return context.getResources().getConfiguration().windowConfiguration
+ .getWindowingMode() != WindowConfiguration.WINDOWING_MODE_PINNED;
}
@GuardedBy("mLock")
@@ -417,6 +428,16 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
}
+ private final class RawConfigurationChangedListener implements
+ java.util.function.Consumer<IBinder> {
+ @Override
+ public void accept(IBinder activityToken) {
+ synchronized (mLock) {
+ onDisplayFeaturesChangedIfListening(activityToken);
+ }
+ }
+ }
+
private final class ConfigurationChangeListener implements ComponentCallbacks {
final IBinder mToken;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 5bfb0ebdcaa8..a836e05b2d66 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -17,7 +17,6 @@
package androidx.window.sidecar;
import static android.view.Display.DEFAULT_DISPLAY;
-
import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
@@ -120,10 +119,12 @@ class SampleSidecarImpl extends StubSidecar {
}
List<SidecarDisplayFeature> features = new ArrayList<>();
+ final int rotation = activity.getResources().getConfiguration().windowConfiguration
+ .getDisplayRotation();
for (CommonFoldingFeature baseFeature : mStoredFeatures) {
SidecarDisplayFeature feature = new SidecarDisplayFeature();
Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
+ rotateRectToDisplayRotation(displayId, rotation, featureRect);
transformToWindowSpaceRect(activity, featureRect);
feature.setRect(featureRect);
feature.setType(baseFeature.getType());
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index 9e2611f392a3..a08db7939eca 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -16,21 +16,22 @@
package androidx.window.util;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import android.annotation.SuppressLint;
import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
+import android.util.RotationUtils;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.UiContext;
+import androidx.annotation.VisibleForTesting;
/**
* Util class for both Sidecar and Extensions.
@@ -44,47 +45,39 @@ public final class ExtensionHelper {
/**
* Rotates the input rectangle specified in default display orientation to the current display
* rotation.
+ *
+ * @param displayId the display id.
+ * @param rotation the target rotation relative to the default display orientation.
+ * @param inOutRect the input/output Rect as specified in the default display orientation.
*/
- public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) {
- DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance();
- DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId);
- int rotation = displayInfo.rotation;
+ public static void rotateRectToDisplayRotation(
+ int displayId, @Surface.Rotation int rotation, @NonNull Rect inOutRect) {
+ final DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance();
+ final DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId);
- boolean isSideRotation = rotation == ROTATION_90 || rotation == ROTATION_270;
- int displayWidth = isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth;
- int displayHeight = isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight;
-
- inOutRect.intersect(0, 0, displayWidth, displayHeight);
-
- rotateBounds(inOutRect, displayWidth, displayHeight, rotation);
+ rotateRectToDisplayRotation(displayInfo, rotation, inOutRect);
}
- /**
- * Rotates the input rectangle within parent bounds for a given delta.
- */
- private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight,
- @Surface.Rotation int delta) {
- int origLeft = inOutRect.left;
- switch (delta) {
- case ROTATION_0:
- return;
- case ROTATION_90:
- inOutRect.left = inOutRect.top;
- inOutRect.top = parentWidth - inOutRect.right;
- inOutRect.right = inOutRect.bottom;
- inOutRect.bottom = parentWidth - origLeft;
- return;
- case ROTATION_180:
- inOutRect.left = parentWidth - inOutRect.right;
- inOutRect.right = parentWidth - origLeft;
- return;
- case ROTATION_270:
- inOutRect.left = parentHeight - inOutRect.bottom;
- inOutRect.bottom = inOutRect.right;
- inOutRect.right = parentHeight - inOutRect.top;
- inOutRect.top = origLeft;
- return;
- }
+ // We suppress the Lint error CheckResult for Rect#intersect because in case the displayInfo and
+ // folding features are out of sync, e.g. when a foldable devices is unfolding, it is acceptable
+ // to provide the original folding feature Rect even if they don't intersect.
+ @SuppressLint("RectIntersectReturnValueIgnored")
+ @VisibleForTesting
+ static void rotateRectToDisplayRotation(@NonNull DisplayInfo displayInfo,
+ @Surface.Rotation int rotation, @NonNull Rect inOutRect) {
+ // The inOutRect is specified in the default display orientation, so here we need to get
+ // the display width and height in the default orientation to perform the intersection and
+ // rotation.
+ final boolean isSideRotation =
+ displayInfo.rotation == ROTATION_90 || displayInfo.rotation == ROTATION_270;
+ final int baseDisplayWidth =
+ isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth;
+ final int baseDisplayHeight =
+ isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight;
+
+ inOutRect.intersect(0, 0, baseDisplayWidth, baseDisplayHeight);
+
+ RotationUtils.rotateBounds(inOutRect, baseDisplayWidth, baseDisplayHeight, rotation);
}
/** Transforms rectangle from absolute coordinate space to the window coordinate space. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index d189ae2cf72e..a0590dc2c832 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -29,7 +29,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
/**
- * Test class for {@link WindowExtensionsTest}.
+ * Test class for {@link WindowExtensions}.
*
* Build/Install/Run:
* atest WMJetpackUnitTests:WindowExtensionsTest
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index ff08782e8cd8..b2ffad7a74e4 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -183,23 +183,23 @@ public class SplitControllerTest {
// tf2 has running activity so is active.
final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
doReturn(1).when(tf2).getRunningActivityCount();
- taskContainer.mContainers.add(tf2);
+ taskContainer.addTaskFragmentContainer(tf2);
// tf3 is finished so is not active.
final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
doReturn(true).when(tf3).isFinished();
doReturn(false).when(tf3).isWaitingActivityAppear();
- taskContainer.mContainers.add(tf3);
+ taskContainer.addTaskFragmentContainer(tf3);
mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
assertWithMessage("Must return tf2 because tf3 is not active.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
- taskContainer.mContainers.remove(tf3);
+ taskContainer.removeTaskFragmentContainer(tf3);
assertWithMessage("Must return tf2 because tf2 has running activity.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
- taskContainer.mContainers.remove(tf2);
+ taskContainer.removeTaskFragmentContainer(tf2);
assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
@@ -320,11 +320,11 @@ public class SplitControllerTest {
doReturn(tf).when(splitContainer).getSecondaryContainer();
doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
- final List<SplitContainer> splitContainers =
- mSplitController.getTaskContainer(TASK_ID).mSplitContainers;
- splitContainers.add(splitContainer);
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ taskContainer.addSplitContainer(splitContainer);
// Add a mock SplitContainer on top of splitContainer
- splitContainers.add(1, mock(SplitContainer.class));
+ final SplitContainer splitContainer2 = mock(SplitContainer.class);
+ taskContainer.addSplitContainer(splitContainer2);
mSplitController.updateContainer(mTransaction, tf);
@@ -332,7 +332,9 @@ public class SplitControllerTest {
// Verify if one or both containers in the top SplitContainer are finished,
// dismissPlaceholder() won't be called.
- splitContainers.remove(1);
+ final ArrayList<SplitContainer> splitContainersToRemove = new ArrayList<>();
+ splitContainersToRemove.add(splitContainer2);
+ taskContainer.removeSplitContainers(splitContainersToRemove);
doReturn(true).when(tf).isFinished();
mSplitController.updateContainer(mTransaction, tf);
@@ -363,7 +365,8 @@ public class SplitControllerTest {
final Activity r1 = createMockActivity();
addSplitTaskFragments(r0, r1);
final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
- final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0);
+ final TaskFragmentContainer taskFragmentContainer =
+ taskContainer.getTaskFragmentContainers().get(0);
spyOn(taskContainer);
// No update when the Task is invisible.
@@ -377,7 +380,7 @@ public class SplitControllerTest {
doReturn(true).when(taskContainer).isVisible();
mSplitController.updateContainer(mTransaction, taskFragmentContainer);
- verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0),
+ verify(mSplitPresenter).updateSplitContainer(taskContainer.getSplitContainers().get(0),
mTransaction);
}
@@ -592,6 +595,18 @@ public class SplitControllerTest {
}
@Test
+ public void testResolveStartActivityIntent_skipIfPinned() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final TaskContainer taskContainer = container.getTaskContainer();
+ spyOn(taskContainer);
+ final Intent intent = new Intent();
+ setupSplitRule(mActivity, intent);
+ doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
+ assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
+ mActivity));
+ }
+
+ @Test
public void testPlaceActivityInTopContainer() {
mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
@@ -1041,6 +1056,29 @@ public class SplitControllerTest {
}
@Test
+ public void testResolveActivityToContainer_skipIfNonTopOrPinned() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final Activity pinnedActivity = createMockActivity();
+ final TaskFragmentContainer topContainer = mSplitController.newContainer(pinnedActivity,
+ TASK_ID);
+ final TaskContainer taskContainer = container.getTaskContainer();
+ spyOn(taskContainer);
+ doReturn(container).when(taskContainer).getTopNonFinishingTaskFragmentContainer(false);
+ doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(topContainer);
+
+ // No need to handle when the new launched activity is in a pinned TaskFragment.
+ assertTrue(mSplitController.resolveActivityToContainer(mTransaction, pinnedActivity,
+ false /* isOnReparent */));
+ verify(mSplitController, never()).shouldExpand(any(), any());
+
+ // Should proceed to resolve if the new launched activity is in the next top TaskFragment
+ // (e.g. the top-most TaskFragment is pinned)
+ mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+ false /* isOnReparent */);
+ verify(mSplitController).shouldExpand(any(), any());
+ }
+
+ @Test
public void testGetPlaceholderOptions() {
// Setup to make sure a transaction record is started.
mTransactionManager.startNewTransaction();
@@ -1090,8 +1128,8 @@ public class SplitControllerTest {
verify(mTransaction).finishActivity(mActivity.getActivityToken());
verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken());
verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken());
- assertTrue(taskContainer.mContainers.isEmpty());
- assertTrue(taskContainer.mSplitContainers.isEmpty());
+ assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
+ assertTrue(taskContainer.getSplitContainers().isEmpty());
}
@Test
@@ -1363,15 +1401,13 @@ public class SplitControllerTest {
TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity));
- List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
- .mContainers;
-
- assertEquals(containers.get(0), tf);
+ final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID);
+ assertEquals(taskContainer.getTaskFragmentContainers().get(0), tf);
mSplitController.finishActivityStacks(Collections.singleton(tf.getTaskFragmentToken()));
verify(mSplitPresenter).deleteTaskFragment(any(), eq(tf.getTaskFragmentToken()));
- assertTrue(containers.isEmpty());
+ assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
}
@Test
@@ -1381,10 +1417,8 @@ public class SplitControllerTest {
bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity));
topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity()));
- List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
- .mContainers;
-
- assertEquals(containers.size(), 2);
+ final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID);
+ assertEquals(taskContainer.getTaskFragmentContainers().size(), 2);
Set<IBinder> activityStackTokens = new ArraySet<>(new IBinder[]{
topTf.getTaskFragmentToken(), bottomTf.getTaskFragmentToken()});
@@ -1403,7 +1437,7 @@ public class SplitControllerTest {
+ "regardless of the order in ActivityStack set",
topTf.getTaskFragmentToken(), fragmentTokens.get(1));
- assertTrue(containers.isEmpty());
+ assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
}
@Test
@@ -1463,6 +1497,51 @@ public class SplitControllerTest {
verify(testRecord).apply(eq(false));
}
+ @Test
+ public void testPinTopActivityStack() {
+ // Create two activities.
+ final Activity primaryActivity = createMockActivity();
+ final Activity secondaryActivity = createMockActivity();
+
+ // Unable to pin if not being embedded.
+ SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(),
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+ // Split the two activities.
+ addSplitTaskFragments(primaryActivity, secondaryActivity);
+ final TaskFragmentContainer primaryContainer =
+ mSplitController.getContainerWithActivity(primaryActivity);
+ spyOn(primaryContainer);
+
+ // Unable to pin if no valid TaskFragment.
+ doReturn(true).when(primaryContainer).isFinished();
+ assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+ // Otherwise, should pin successfully.
+ doReturn(false).when(primaryContainer).isFinished();
+ assertTrue(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+ // Unable to pin if there is already a pinned TaskFragment
+ assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+ // Unable to pin on an unknown Task.
+ assertFalse(mSplitController.pinTopActivityStack(TASK_ID + 1, splitPinRule));
+
+ // Gets the current size of all the SplitContainers.
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ final int splitContainerCount = taskContainer.getSplitContainers().size();
+
+ // Create another activity and split with primary activity.
+ final Activity thirdActivity = createMockActivity();
+ addSplitTaskFragments(primaryActivity, thirdActivity);
+
+ // Ensure another SplitContainer is added and the pinned TaskFragment still on top
+ assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
+ assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
+ == secondaryActivity);
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
return createMockActivity(TASK_ID);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 13e709271221..000c65a75c81 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -127,7 +127,7 @@ public class TaskContainerTest {
assertFalse(taskContainer.isEmpty());
taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken());
- taskContainer.mContainers.clear();
+ taskContainer.clearTaskFragmentContainer();
assertFalse(taskContainer.isEmpty());
}
@@ -135,15 +135,15 @@ public class TaskContainerTest {
@Test
public void testGetTopTaskFragmentContainer() {
final TaskContainer taskContainer = createTestTaskContainer();
- assertNull(taskContainer.getTopTaskFragmentContainer());
+ assertNull(taskContainer.getTopNonFinishingTaskFragmentContainer());
final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
- assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
+ assertEquals(tf0, taskContainer.getTopNonFinishingTaskFragmentContainer());
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
- assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
+ assertEquals(tf1, taskContainer.getTopNonFinishingTaskFragmentContainer());
}
@Test
@@ -152,13 +152,13 @@ public class TaskContainerTest {
assertNull(taskContainer.getTopNonFinishingActivity());
final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class);
- taskContainer.mContainers.add(tf0);
+ taskContainer.addTaskFragmentContainer(tf0);
final Activity activity0 = mock(Activity.class);
doReturn(activity0).when(tf0).getTopNonFinishingActivity();
assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
final TaskFragmentContainer tf1 = mock(TaskFragmentContainer.class);
- taskContainer.mContainers.add(tf1);
+ taskContainer.addTaskFragmentContainer(tf1);
assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
final Activity activity1 = mock(Activity.class);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java
new file mode 100644
index 000000000000..3278cdf1c337
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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 androidx.window.util;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link ExtensionHelper}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:ExtensionHelperTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ExtensionHelperTest {
+
+ private static final int MOCK_DISPLAY_HEIGHT = 1000;
+ private static final int MOCK_DISPLAY_WIDTH = 2000;
+ private static final int MOCK_FEATURE_LEFT = 100;
+ private static final int MOCK_FEATURE_RIGHT = 200;
+
+ private static final int[] ROTATIONS = {
+ Surface.ROTATION_0,
+ Surface.ROTATION_90,
+ Surface.ROTATION_180,
+ Surface.ROTATION_270
+ };
+
+ private static final DisplayInfo[] MOCK_DISPLAY_INFOS = {
+ getMockDisplayInfo(Surface.ROTATION_0),
+ getMockDisplayInfo(Surface.ROTATION_90),
+ getMockDisplayInfo(Surface.ROTATION_180),
+ getMockDisplayInfo(Surface.ROTATION_270),
+ };
+
+ @Test
+ public void testRotateRectToDisplayRotation() {
+ for (int rotation : ROTATIONS) {
+ final Rect expectedResult = getExpectedFeatureRectAfterRotation(rotation);
+ // The method should return correctly rotated Rect even if the requested rotation value
+ // differs from the rotation in DisplayInfo. This is because the WindowConfiguration is
+ // not always synced with DisplayInfo.
+ for (DisplayInfo displayInfo : MOCK_DISPLAY_INFOS) {
+ final Rect rect = getMockFeatureRect();
+ ExtensionHelper.rotateRectToDisplayRotation(displayInfo, rotation, rect);
+ assertEquals(
+ "Result Rect should equal to expected for rotation: " + rotation
+ + "; displayInfo: " + displayInfo,
+ expectedResult, rect);
+ }
+ }
+ }
+
+ @NonNull
+ private static DisplayInfo getMockDisplayInfo(@Surface.Rotation int rotation) {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.rotation = rotation;
+ if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+ displayInfo.logicalWidth = MOCK_DISPLAY_WIDTH;
+ displayInfo.logicalHeight = MOCK_DISPLAY_HEIGHT;
+ } else {
+ displayInfo.logicalWidth = MOCK_DISPLAY_HEIGHT;
+ displayInfo.logicalHeight = MOCK_DISPLAY_WIDTH;
+ }
+ return displayInfo;
+ }
+
+ @NonNull
+ private static Rect getMockFeatureRect() {
+ return new Rect(MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT);
+ }
+
+ @NonNull
+ private static Rect getExpectedFeatureRectAfterRotation(@Surface.Rotation int rotation) {
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ return new Rect(
+ MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT);
+ case Surface.ROTATION_90:
+ return new Rect(0, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT,
+ MOCK_DISPLAY_HEIGHT, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT);
+ case Surface.ROTATION_180:
+ return new Rect(MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT, 0,
+ MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT);
+ case Surface.ROTATION_270:
+ return new Rect(0, MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT,
+ MOCK_FEATURE_RIGHT);
+ default:
+ throw new IllegalArgumentException("Unknown rotation value: " + rotation);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index b232555c64dd..e9abc7e522d5 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -42,16 +42,19 @@ filegroup {
filegroup {
name: "wm_shell_util-sources",
srcs: [
- "src/com/android/wm/shell/util/**/*.java",
+ "src/com/android/wm/shell/animation/Interpolators.java",
+ "src/com/android/wm/shell/animation/PhysicsAnimator.kt",
+ "src/com/android/wm/shell/common/bubbles/*.kt",
+ "src/com/android/wm/shell/common/bubbles/*.java",
+ "src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt",
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
- "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
"src/com/android/wm/shell/common/TransactionPool.java",
- "src/com/android/wm/shell/common/bubbles/*.java",
"src/com/android/wm/shell/common/TriangleShape.java",
- "src/com/android/wm/shell/animation/Interpolators.java",
+ "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
"src/com/android/wm/shell/pip/PipContentOverlay.java",
"src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
- "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
+ "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
+ "src/com/android/wm/shell/util/**/*.java",
],
path: "src",
}
@@ -147,6 +150,7 @@ android_library {
],
static_libs: [
"androidx.appcompat_appcompat",
+ "androidx.core_core-animation",
"androidx.arch.core_core-runtime",
"androidx-constraintlayout_constraintlayout",
"androidx.dynamicanimation_dynamicanimation",
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml
new file mode 100644
index 000000000000..d99d64d8da20
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true">
+ <ripple android:color="#99999999">
+ <item android:drawable="@drawable/bubble_manage_menu_bg" />
+ </ripple>
+ </item>
+ <item android:drawable="@drawable/bubble_manage_menu_bg" />
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
new file mode 100644
index 000000000000..02b707568cd0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M180,840Q156,840 138,822Q120,804 120,780L120,180Q120,156 138,138Q156,120 180,120L780,120Q804,120 822,138Q840,156 840,180L840,780Q840,804 822,822Q804,840 780,840L180,840ZM180,780L780,780Q780,780 780,780Q780,780 780,780L780,277L180,277L180,780Q180,780 180,780Q180,780 180,780Z" />
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index 5d7771366bec..ce242751c172 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -13,13 +13,20 @@
~ 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">
- <group android:translateY="8.0">
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="128dp"
+ android:height="4dp"
+ android:viewportWidth="128"
+ android:viewportHeight="4"
+ >
+ <group>
+ <clip-path
+ android:pathData="M2 0H126C127.105 0 128 0.895431 128 2C128 3.10457 127.105 4 126 4H2C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0Z"
+ />
<path
- android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+ android:pathData="M0 0V4H128V0"
+ android:fillColor="@android:color/black"
+ />
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
new file mode 100644
index 000000000000..f4508464883d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M18.59,16.41L20,15L12,7L4,15L5.41,16.41L12,9.83"
+ android:fillColor="#5F6368"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml
new file mode 100644
index 000000000000..6e4752c9d27d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="@color/compat_controls_background"
+ android:strokeAlpha="0.8"
+ android:fillAlpha="0.8"
+ android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
+ <group
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:fillColor="@color/compat_controls_text"
+ android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml
new file mode 100644
index 000000000000..141a1ce60b8e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/compat_background_ripple">
+ <item android:drawable="@drawable/user_aspect_ratio_settings_button"/>
+</ripple> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
new file mode 100644
index 000000000000..a0a06f1b3721
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<com.android.wm.shell.common.bubbles.BubblePopupView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginHorizontal="@dimen/bubble_popup_margin_horizontal"
+ android:layout_marginTop="@dimen/bubble_popup_margin_top"
+ android:elevation="@dimen/bubble_manage_menu_elevation"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <ImageView
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:tint="?android:attr/colorAccent"
+ android:contentDescription="@null"
+ android:src="@drawable/pip_ic_settings"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:maxWidth="@dimen/bubble_popup_content_max_width"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/bubble_bar_education_manage_title"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:maxWidth="@dimen/bubble_popup_content_max_width"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAlignment="center"
+ android:text="@string/bubble_bar_education_manage_text"/>
+
+</com.android.wm.shell.common.bubbles.BubblePopupView> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
new file mode 100644
index 000000000000..ddcd5c60d9c8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
+ android:gravity="center_vertical"
+ android:paddingStart="@dimen/bubble_menu_padding"
+ android:paddingEnd="@dimen/bubble_menu_padding"
+ android:background="@drawable/bubble_manage_menu_row">
+
+ <ImageView
+ android:id="@+id/bubble_bar_menu_item_icon"
+ android:layout_width="@dimen/bubble_bar_manage_menu_item_icon_size"
+ android:layout_height="@dimen/bubble_bar_manage_menu_item_icon_size"
+ android:contentDescription="@null"/>
+
+ <TextView
+ android:id="@+id/bubble_bar_menu_item_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+</com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
new file mode 100644
index 000000000000..82e5aee41ff2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<com.android.wm.shell.bubbles.bar.BubbleBarMenuView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:minWidth="@dimen/bubble_bar_manage_menu_min_width"
+ android:orientation="vertical"
+ android:elevation="@dimen/bubble_manage_menu_elevation"
+ android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top"
+ android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding"
+ android:paddingBottom="@dimen/bubble_bar_manage_menu_padding"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/bubble_bar_manage_menu_bubble_section"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:paddingStart="14dp"
+ android:paddingEnd="12dp"
+ android:background="@drawable/bubble_manage_menu_section"
+ android:elevation="@dimen/bubble_manage_menu_elevation">
+
+ <ImageView
+ android:id="@+id/bubble_bar_manage_menu_bubble_icon"
+ android:layout_width="@dimen/bubble_menu_icon_size"
+ android:layout_height="@dimen/bubble_menu_icon_size"
+ android:contentDescription="@null" />
+
+ <TextView
+ android:id="@+id/bubble_bar_manage_menu_bubble_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_weight="1"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+ <ImageView
+ android:id="@+id/bubble_bar_manage_menu_dismiss_icon"
+ android:layout_width="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
+ android:layout_height="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
+ android:layout_marginStart="8dp"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_expand_less"
+ app:tint="?android:attr/textColorPrimary" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/bubble_bar_manage_menu_actions_section"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginTop="@dimen/bubble_bar_manage_menu_section_spacing"
+ android:background="@drawable/bubble_manage_menu_bg"
+ android:elevation="@dimen/bubble_manage_menu_elevation" />
+
+</com.android.wm.shell.bubbles.bar.BubbleBarMenuView> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index dfaeeeb81c07..257fe1544bbb 100644
--- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -55,7 +55,7 @@
<include android:id="@+id/size_compat_hint"
android:visibility="gone"
- android:layout_width="@dimen/size_compat_hint_width"
+ android:layout_width="@dimen/compat_hint_width"
android:layout_height="wrap_content"
layout="@layout/compat_mode_hint"/>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index fb1980a52601..7e0c2071dc86 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -78,6 +78,19 @@
android:layout_weight="1"/>
<ImageButton
+ android:id="@+id/maximize_window"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:padding="9dp"
+ android:layout_marginEnd="8dp"
+ android:contentDescription="@string/maximize_button_text"
+ android:src="@drawable/decor_desktop_mode_maximize_button_dark"
+ android:scaleType="fitCenter"
+ android:gravity="end"
+ android:background="@null"
+ android:tint="@color/desktop_mode_caption_maximize_button_dark"/>
+
+ <ImageButton
android:id="@+id/close_window"
android:layout_width="40dp"
android:layout_height="40dp"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index 1d6864c152c2..d93e9ba32105 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -25,9 +25,9 @@
<ImageButton
android:id="@+id/caption_handle"
- android:layout_width="176dp"
+ android:layout_width="128dp"
android:layout_height="42dp"
- android:paddingHorizontal="24dp"
+ android:paddingVertical="19dp"
android:contentDescription="@string/handle_text"
android:src="@drawable/decor_handle_dark"
tools:tint="@color/desktop_mode_caption_handle_bar_dark"
diff --git a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
new file mode 100644
index 000000000000..433d8546ece0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.wm.shell.compatui.UserAspectRatioSettingsLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="bottom|end">
+
+ <include android:id="@+id/user_aspect_ratio_settings_hint"
+ android:visibility="gone"
+ android:layout_width="@dimen/compat_hint_width"
+ android:layout_height="wrap_content"
+ layout="@layout/compat_mode_hint"/>
+
+ <ImageButton
+ android:id="@+id/user_aspect_ratio_settings_button"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/compat_button_margin"
+ android:layout_marginBottom="@dimen/compat_button_margin"
+ android:src="@drawable/user_aspect_ratio_settings_button_ripple"
+ android:background="@android:color/transparent"
+ android:contentDescription="@string/user_aspect_ratio_settings_button_description"/>
+
+</com.android.wm.shell.compatui.UserAspectRatioSettingsLayout>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 171a6b2fe5fb..b2ec98bc1b15 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -30,6 +30,9 @@
<color name="bubbles_light">#FFFFFF</color>
<color name="bubbles_dark">@color/GM2_grey_800</color>
<color name="bubbles_icon_tint">@color/GM2_grey_700</color>
+ <color name="bubble_bar_expanded_view_handle_light">#EBffffff</color>
+ <color name="bubble_bar_expanded_view_handle_dark">#99000000</color>
+ <color name="bubble_bar_expanded_view_menu_close">#DC362E</color>
<!-- PiP -->
<color name="pip_custom_close_bg">#D93025</color>
@@ -61,6 +64,8 @@
<color name="desktop_mode_caption_expand_button_dark">#48473A</color>
<color name="desktop_mode_caption_close_button_light">#EFF1F2</color>
<color name="desktop_mode_caption_close_button_dark">#1C1C17</color>
+ <color name="desktop_mode_caption_maximize_button_light">#EFF1F2</color>
+ <color name="desktop_mode_caption_maximize_button_dark">#1C1C17</color>
<color name="desktop_mode_caption_app_name_light">#EFF1F2</color>
<color name="desktop_mode_caption_app_name_dark">#1C1C17</color>
<color name="desktop_mode_caption_menu_text_color">#191C1D</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 2be34c90a661..99526de56e4e 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -226,10 +226,40 @@
<dimen name="bubble_user_education_padding_end">58dp</dimen>
<!-- Padding between the bubble and the user education text. -->
<dimen name="bubble_user_education_stack_padding">16dp</dimen>
- <!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. -->
- <dimen name="bubblebar_size">72dp</dimen>
- <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. -->
- <dimen name="bubblebar_expanded_view_menu_size">16dp</dimen>
+ <!-- Max width for the bubble popup view. -->
+ <dimen name="bubble_popup_content_max_width">300dp</dimen>
+ <!-- Horizontal margin for the bubble popup view. -->
+ <dimen name="bubble_popup_margin_horizontal">32dp</dimen>
+ <!-- Top margin for the bubble popup view. -->
+ <dimen name="bubble_popup_margin_top">16dp</dimen>
+ <!-- Width for the bubble popup view arrow. -->
+ <dimen name="bubble_popup_arrow_width">12dp</dimen>
+ <!-- Height for the bubble popup view arrow. -->
+ <dimen name="bubble_popup_arrow_height">10dp</dimen>
+ <!-- Corner radius for the bubble popup view arrow. -->
+ <dimen name="bubble_popup_arrow_corner_radius">2dp</dimen>
+ <!-- Padding for the bubble popup view contents. -->
+ <dimen name="bubble_popup_padding">24dp</dimen>
+ <!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
+ <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
+ <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
+ <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
+ <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
+ <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen>
+ <!-- Minimum width of the bubble bar manage menu. -->
+ <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
+ <!-- Size of the dismiss icon in the bubble bar manage menu. -->
+ <dimen name="bubble_bar_manage_menu_dismiss_icon_size">16dp</dimen>
+ <!-- Padding of the bubble bar manage menu, provides space for menu shadows -->
+ <dimen name="bubble_bar_manage_menu_padding">8dp</dimen>
+ <!-- Top padding of the bubble bar manage menu -->
+ <dimen name="bubble_bar_manage_menu_padding_top">2dp</dimen>
+ <!-- Spacing between sections of the bubble bar manage menu -->
+ <dimen name="bubble_bar_manage_menu_section_spacing">2dp</dimen>
+ <!-- Height of an item in the bubble bar manage menu. -->
+ <dimen name="bubble_bar_manage_menu_item_height">52dp</dimen>
+ <!-- Size of the icons in the bubble bar manage menu. -->
+ <dimen name="bubble_bar_manage_menu_item_icon_size">20dp</dimen>
<!-- Bottom and end margin for compat buttons. -->
<dimen name="compat_button_margin">24dp</dimen>
@@ -244,8 +274,8 @@
+ compat_button_margin - compat_hint_corner_radius - compat_hint_point_width / 2). -->
<dimen name="compat_hint_padding_end">7dp</dimen>
- <!-- The width of the size compat hint. -->
- <dimen name="size_compat_hint_width">188dp</dimen>
+ <!-- The width of the compat hint. -->
+ <dimen name="compat_hint_width">188dp</dimen>
<!-- The width of the camera compat hint. -->
<dimen name="camera_compat_hint_width">143dp</dimen>
@@ -398,7 +428,15 @@
<!-- The radius of the caption menu shadow. -->
<dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
- <dimen name="freeform_resize_handle">30dp</dimen>
+ <dimen name="freeform_resize_handle">15dp</dimen>
<dimen name="freeform_resize_corner">44dp</dimen>
+
+ <!-- The width of the area at the sides of the screen where a freeform task will transition to
+ split select if dragged until the touch input is within the range. -->
+ <dimen name="desktop_mode_transition_area_width">32dp</dimen>
+
+ <!-- The height of the area at the top of the screen where a freeform task will transition to
+ fullscreen if dragged until the top bound of the task is within the area. -->
+ <dimen name="desktop_mode_transition_area_height">16dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index b192fdf245e2..00c63d70d3a0 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -163,6 +163,11 @@
<!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
<string name="bubble_overflow_empty_subtitle">Recent bubbles and dismissed bubbles will appear here</string>
+ <!-- Title text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=60]-->
+ <string name="bubble_bar_education_manage_title">Control bubbles anytime</string>
+ <!-- Descriptive text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=80]-->
+ <string name="bubble_bar_education_manage_text">Tap here to manage which apps and conversations can bubble</string>
+
<!-- [CHAR LIMIT=100] Notification Importance title -->
<string name="notification_bubble_title">Bubble</string>
@@ -173,7 +178,13 @@
<string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
<!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
- <string name="restart_button_description">Tap to restart this app for a better view.</string>
+ <string name="restart_button_description">Tap to restart this app for a better view</string>
+
+ <!-- Tooltip text of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] -->
+ <string name="user_aspect_ratio_settings_button_hint">Change this app\'s aspect ratio in Settings</string>
+
+ <!-- Content description of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] -->
+ <string name="user_aspect_ratio_settings_button_description">Change aspect ratio</string>
<!-- Description of the camera compat button for applying stretched issues treatment in the hint for
compatibility control. [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 9aac694e41bf..04795768aefc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -103,7 +103,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
/** Whether this task listener supports compat UI. */
default boolean supportCompatUI() {
- // All TaskListeners should support compat UI except PIP.
+ // All TaskListeners should support compat UI except PIP and StageCoordinator.
return true;
}
/** Attaches a child window surface to the task surface. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 39f861de1ba0..5cf9175073c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -50,7 +50,7 @@ class ActivityEmbeddingAnimationAdapter {
final SurfaceControl mLeash;
/** Area in absolute coordinate that the animation surface shouldn't go beyond. */
@NonNull
- private final Rect mWholeAnimationBounds = new Rect();
+ final Rect mWholeAnimationBounds = new Rect();
/**
* Area in absolute coordinate that should represent all the content to show for this window.
* This should be the end bounds for opening window, and start bounds for closing window in case
@@ -229,20 +229,7 @@ class ActivityEmbeddingAnimationAdapter {
mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
-
- // The following applies an inverse scale to the clip-rect so that it crops "after" the
- // scale instead of before.
- mVecs[1] = mVecs[2] = 0;
- mVecs[0] = mVecs[3] = 1;
- mTransformation.getMatrix().mapVectors(mVecs);
- mVecs[0] = 1.f / mVecs[0];
- mVecs[3] = 1.f / mVecs[3];
- final Rect clipRect = mTransformation.getClipRect();
- mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
- mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
- mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
- mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
- t.setCrop(mLeash, mRect);
+ t.setWindowCrop(mLeash, mWholeAnimationBounds.width(), mWholeAnimationBounds.height());
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 1793a3d0feb4..4640106b5f1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -26,7 +26,6 @@ import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
-import android.view.animation.ClipRectAnimation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.ScaleAnimation;
@@ -189,14 +188,6 @@ class ActivityEmbeddingAnimationSpec {
startBounds.top - endBounds.top, 0);
endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
endSet.addAnimation(endTranslate);
- // The end leash is resizing, we should update the window crop based on the clip rect.
- final Rect startClip = new Rect(startBounds);
- final Rect endClip = new Rect(endBounds);
- startClip.offsetTo(0, 0);
- endClip.offsetTo(0, 0);
- final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
- clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
- endSet.addAnimation(clipAnim);
endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
parentBounds.height());
endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
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 57d374b2b8f5..06ce37148eaf 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
@@ -186,6 +186,6 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
if (callback == null) {
throw new IllegalStateException("No finish callback found");
}
- callback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ callback.onTransitionFinished(null /* wct */);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
index 798250de89d0..26edd7d2268b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
@@ -117,6 +117,20 @@ public class FlingAnimationUtils {
* @param endValue the end value of the animator
* @param velocity the current velocity of the motion
*/
+ public void apply(androidx.core.animation.Animator animator,
+ float currValue, float endValue, float velocity) {
+ apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
+ }
+
+ /**
+ * Applies the interpolator and length to the animator, such that the fling animation is
+ * consistent with the finger motion.
+ *
+ * @param animator the animator to apply
+ * @param currValue the current value
+ * @param endValue the end value of the animator
+ * @param velocity the current velocity of the motion
+ */
public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
float velocity) {
apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
@@ -152,6 +166,24 @@ public class FlingAnimationUtils {
* @param maxDistance the maximum distance for this interaction; the maximum animation length
* gets multiplied by the ratio between the actual distance and this value
*/
+ public void apply(androidx.core.animation.Animator animator,
+ float currValue, float endValue, float velocity, float maxDistance) {
+ AnimatorProperties properties = getProperties(currValue, endValue, velocity, maxDistance);
+ animator.setDuration(properties.mDuration);
+ animator.setInterpolator(properties.getInterpolator());
+ }
+
+ /**
+ * Applies the interpolator and length to the animator, such that the fling animation is
+ * consistent with the finger motion.
+ *
+ * @param animator the animator to apply
+ * @param currValue the current value
+ * @param endValue the end value of the animator
+ * @param velocity the current velocity of the motion
+ * @param maxDistance the maximum distance for this interaction; the maximum animation length
+ * gets multiplied by the ratio between the actual distance and this value
+ */
public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
float velocity, float maxDistance) {
AnimatorProperties properties = getProperties(currValue, endValue, velocity,
@@ -367,6 +399,11 @@ public class FlingAnimationUtils {
private static class AnimatorProperties {
Interpolator mInterpolator;
long mDuration;
+
+ /** Get an AndroidX interpolator wrapper of the current mInterpolator */
+ public androidx.core.animation.Interpolator getInterpolator() {
+ return mInterpolator::getInterpolation;
+ }
}
/** Builder for {@link #FlingAnimationUtils}. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index edefe9e3ab06..74a243d34642 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -371,7 +371,15 @@ class CrossActivityAnimation {
@Override
public void onBackCancelled() {
- mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation);
+ mProgressAnimator.onBackCancelled(() -> {
+ // mProgressAnimator can reach finish stage earlier than mLeavingProgressSpring,
+ // and if we release all animation leash first, the leavingProgressSpring won't
+ // able to update the animation anymore, which cause flicker.
+ // Here should force update the closing animation target to the final stage before
+ // release it.
+ setLeavingProgress(0);
+ finishAnimation();
+ });
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 102f2cb4b8d0..9a2b81243861 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -60,7 +60,6 @@ import java.util.concurrent.Executor;
/**
* Encapsulates the data and UI elements of a bubble.
*/
-@VisibleForTesting
public class Bubble implements BubbleViewProvider {
private static final String TAG = "Bubble";
@@ -422,6 +421,7 @@ public class Bubble implements BubbleViewProvider {
}
if (mBubbleBarExpandedView != null) {
mBubbleBarExpandedView.cleanUpExpandedState();
+ mBubbleBarExpandedView = null;
}
if (mIntent != null) {
mIntent.unregisterCancelListener(mIntentCancelListener);
@@ -549,10 +549,10 @@ public class Bubble implements BubbleViewProvider {
/**
* Set visibility of bubble in the expanded state.
*
- * @param visibility {@code true} if the expanded bubble should be visible on the screen.
- *
- * Note that this contents visibility doesn't affect visibility at {@link android.view.View},
+ * <p>Note that this contents visibility doesn't affect visibility at {@link android.view.View},
* and setting {@code false} actually means rendering the expanded view in transparent.
+ *
+ * @param visibility {@code true} if the expanded bubble should be visible on the screen.
*/
@Override
public void setTaskViewVisibility(boolean visibility) {
@@ -851,11 +851,15 @@ public class Bubble implements BubbleViewProvider {
return mAppIntent;
}
- boolean isAppBubble() {
+ /**
+ * Returns whether this bubble is from an app versus a notification.
+ */
+ public boolean isAppBubble() {
return mIsAppBubble;
}
- Intent getSettingsIntent(final Context context) {
+ /** Creates open app settings intent */
+ public Intent getSettingsIntent(final Context context) {
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
final int uid = getUid(context);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index f235cd7e8a6e..8400ddec0af5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -25,7 +25,6 @@ import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
@@ -37,6 +36,7 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
import android.annotation.BinderThread;
@@ -64,7 +64,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.NotificationListenerService;
@@ -86,12 +85,14 @@ import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.bubbles.properties.BubbleProperties;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -143,16 +144,6 @@ public class BubbleController implements ConfigurationChangeListener,
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
- // TODO(b/256873975) Should use proper flag when available to shell/launcher
- /**
- * Whether bubbles are showing in the bubble bar from launcher. This is only available
- * on large screens and {@link BubbleController#isShowingAsBubbleBar()} should be used
- * to check all conditions that indicate if the bubble bar is in use.
- */
- private static final boolean BUBBLE_BAR_ENABLED =
- SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
-
-
/**
* Common interface to send updates to bubble views.
*/
@@ -195,6 +186,7 @@ public class BubbleController implements ConfigurationChangeListener,
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
private final IWindowManager mWmService;
+ private final BubbleProperties mBubbleProperties;
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
@@ -291,7 +283,8 @@ public class BubbleController implements ConfigurationChangeListener,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue,
- IWindowManager wmService) {
+ IWindowManager wmService,
+ BubbleProperties bubbleProperties) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
@@ -328,6 +321,7 @@ public class BubbleController implements ConfigurationChangeListener,
mDragAndDropController = dragAndDropController;
mSyncQueue = syncQueue;
mWmService = wmService;
+ mBubbleProperties = bubbleProperties;
shellInit.addInitCallback(this::onInit, this);
}
@@ -522,11 +516,14 @@ public class BubbleController implements ConfigurationChangeListener,
/**
* Sets a listener to be notified of bubble updates. This is used by launcher so that
* it may render bubbles in itself. Only one listener is supported.
+ *
+ * <p>If bubble bar is supported, bubble views will be updated to switch to bar mode.
*/
public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
- if (isShowingAsBubbleBar()) {
- // Only set the listener if bubble bar is showing.
+ if (canShowAsBubbleBar() && listener != null) {
+ // Only set the listener if we can show the bubble bar.
mBubbleStateListener = listener;
+ setUpBubbleViewsForMode();
sendInitialListenerUpdate();
} else {
mBubbleStateListener = null;
@@ -535,9 +532,15 @@ public class BubbleController implements ConfigurationChangeListener,
/**
* Unregisters the {@link Bubbles.BubbleStateListener}.
+ *
+ * <p>If there's an existing listener, then we're switching back to stack mode and bubble views
+ * will be updated accordingly.
*/
public void unregisterBubbleStateListener() {
- mBubbleStateListener = null;
+ if (mBubbleStateListener != null) {
+ mBubbleStateListener = null;
+ setUpBubbleViewsForMode();
+ }
}
/**
@@ -649,8 +652,12 @@ public class BubbleController implements ConfigurationChangeListener,
/** Whether bubbles are showing in the bubble bar. */
public boolean isShowingAsBubbleBar() {
- // TODO(b/269670598): should also check that we're in gesture nav
- return BUBBLE_BAR_ENABLED && mBubblePositioner.isLargeScreen();
+ return canShowAsBubbleBar() && mBubbleStateListener != null;
+ }
+
+ /** Whether the current configuration supports showing as bubble bar. */
+ private boolean canShowAsBubbleBar() {
+ return mBubbleProperties.isBubbleBarEnabled() && mBubblePositioner.isLargeScreen();
}
/** Whether this userId belongs to the current user. */
@@ -723,6 +730,7 @@ public class BubbleController implements ConfigurationChangeListener,
// TODO(b/273312602): consider foldables where we do need a stack view when folded
if (mLayerView == null) {
mLayerView = new BubbleBarLayerView(mContext, this);
+ mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
} else {
if (mStackView == null) {
@@ -777,12 +785,12 @@ public class BubbleController implements ConfigurationChangeListener,
try {
mAddedToWindowManager = true;
registerBroadcastReceiver();
- mBubbleData.getOverflow().initialize(this);
+ mBubbleData.getOverflow().initialize(this, isShowingAsBubbleBar());
// (TODO: b/273314541) some duplication in the inset listener
if (isShowingAsBubbleBar()) {
mWindowManager.addView(mLayerView, mWmLayoutParams);
mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
- if (!windowInsets.equals(mWindowInsets)) {
+ if (!windowInsets.equals(mWindowInsets) && mLayerView != null) {
mWindowInsets = windowInsets;
mBubblePositioner.update();
mLayerView.onDisplaySizeChanged();
@@ -792,7 +800,7 @@ public class BubbleController implements ConfigurationChangeListener,
} else {
mWindowManager.addView(mStackView, mWmLayoutParams);
mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
- if (!windowInsets.equals(mWindowInsets)) {
+ if (!windowInsets.equals(mWindowInsets) && mStackView != null) {
mWindowInsets = windowInsets;
mBubblePositioner.update();
mStackView.onDisplaySizeChanged();
@@ -814,13 +822,17 @@ public class BubbleController implements ConfigurationChangeListener,
* @param interceptBack whether back should be intercepted or not.
*/
void updateWindowFlagsForBackpress(boolean interceptBack) {
- if (mStackView != null && mAddedToWindowManager) {
+ if (mAddedToWindowManager) {
mWmLayoutParams.flags = interceptBack
? 0
: WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
- mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+ if (mStackView != null) {
+ mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+ } else if (mLayerView != null) {
+ mWindowManager.updateViewLayout(mLayerView, mWmLayoutParams);
+ }
}
}
@@ -997,9 +1009,7 @@ public class BubbleController implements ConfigurationChangeListener,
}
private void onNotificationPanelExpandedChanged(boolean expanded) {
- if (DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "onNotificationPanelExpandedChanged: expanded=" + expanded);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded);
if (mStackView != null && mStackView.isExpanded()) {
if (expanded) {
mStackView.stopMonitoringSwipeUpGesture();
@@ -1046,6 +1056,20 @@ public class BubbleController implements ConfigurationChangeListener,
mBubbleData.setExpanded(false /* expanded */);
}
+ /**
+ * Update expanded state when a single bubble is dragged in Launcher.
+ * Will be called only when bubble bar is expanded.
+ * @param bubbleKey key of the bubble to collapse/expand
+ * @param isBeingDragged whether the bubble is being dragged
+ */
+ public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
+ if (mBubbleData.getSelectedBubble() != null
+ && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) {
+ // Should collapse/expand only if equals to selected bubble.
+ mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged);
+ }
+ }
+
@VisibleForTesting
public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
@@ -1069,9 +1093,19 @@ public class BubbleController implements ConfigurationChangeListener,
* Expands and selects the provided bubble as long as it already exists in the stack or the
* overflow.
*
- * This is used by external callers (launcher).
+ * <p>This is used by external callers (launcher).
*/
- public void expandStackAndSelectBubbleFromLauncher(String key) {
+ @VisibleForTesting
+ public void expandStackAndSelectBubbleFromLauncher(String key, int bubbleBarOffsetX,
+ int bubbleBarOffsetY) {
+ mBubblePositioner.setBubbleBarPosition(bubbleBarOffsetX, bubbleBarOffsetY);
+
+ if (BubbleOverflow.KEY.equals(key)) {
+ mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
+ mLayerView.showExpandedView(mBubbleData.getOverflow());
+ return;
+ }
+
Bubble b = mBubbleData.getAnyBubbleWithkey(key);
if (b == null) {
return;
@@ -1224,6 +1258,13 @@ public class BubbleController implements ConfigurationChangeListener,
}
/**
+ * Dismiss bubble if it exists and remove it from the stack
+ */
+ public void dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason) {
+ mBubbleData.dismissBubbleWithKey(bubble.getKey(), reason);
+ }
+
+ /**
* Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot
* can be access via the supplied {@link SynchronousScreenCaptureListener#getBuffer()}
* asynchronously.
@@ -1263,7 +1304,9 @@ public class BubbleController implements ConfigurationChangeListener,
return;
}
mOverflowDataLoadNeeded = false;
- mDataRepository.loadBubbles(mCurrentUserId, (bubbles) -> {
+ List<UserInfo> users = mUserManager.getAliveUsers();
+ List<Integer> userIds = users.stream().map(userInfo -> userInfo.id).toList();
+ mDataRepository.loadBubbles(mCurrentUserId, userIds, (bubbles) -> {
bubbles.forEach(bubble -> {
if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) {
// if the bubble is already active, there's no need to push it to overflow
@@ -1282,6 +1325,50 @@ public class BubbleController implements ConfigurationChangeListener,
});
}
+ void setUpBubbleViewsForMode() {
+ mBubbleViewCallback = isShowingAsBubbleBar()
+ ? mBubbleBarViewCallback
+ : mBubbleStackViewCallback;
+
+ // reset the overflow so that it can be re-added later if needed.
+ if (mStackView != null) {
+ mStackView.resetOverflowView();
+ mStackView.removeAllViews();
+ }
+ // cleanup existing bubble views so they can be recreated later if needed.
+ mBubbleData.getBubbles().forEach(Bubble::cleanupViews);
+
+ // remove the current bubble container from window manager, null it out, and create a new
+ // container based on the current mode.
+ removeFromWindowManagerMaybe();
+ mLayerView = null;
+ mStackView = null;
+ ensureBubbleViewsAndWindowCreated();
+
+ // inflate bubble views
+ BubbleViewInfoTask.Callback callback = null;
+ if (!isShowingAsBubbleBar()) {
+ callback = b -> {
+ if (mStackView != null) {
+ mStackView.addBubble(b);
+ mStackView.setSelectedBubble(b);
+ } else {
+ Log.w(TAG, "Tried to add a bubble to the stack but the stack is null");
+ }
+ };
+ }
+ for (int i = mBubbleData.getBubbles().size() - 1; i >= 0; i--) {
+ Bubble bubble = mBubbleData.getBubbles().get(i);
+ bubble.inflate(callback,
+ mContext,
+ this,
+ mStackView,
+ mLayerView,
+ mBubbleIconFactory,
+ false /* skipInflation */);
+ }
+ }
+
/**
* Adds or updates a bubble associated with the provided notification entry.
*
@@ -1368,6 +1455,17 @@ public class BubbleController implements ConfigurationChangeListener,
}
}
+ /**
+ * Removes all the bubbles.
+ * <p>
+ * Must be called from the main thread.
+ */
+ @VisibleForTesting
+ @MainThread
+ public void removeAllBubbles(@Bubbles.DismissReason int reason) {
+ mBubbleData.dismissAll(reason);
+ }
+
private void onEntryAdded(BubbleEntry entry) {
if (canLaunchInTaskView(mContext, entry)) {
updateBubble(entry);
@@ -1742,7 +1840,7 @@ public class BubbleController implements ConfigurationChangeListener,
// Update the cached state for queries from SysUI
mImpl.mCachedState.update(update);
- if (isShowingAsBubbleBar() && mBubbleStateListener != null) {
+ if (isShowingAsBubbleBar()) {
BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate();
// Some updates aren't relevant to the bubble bar so check first.
if (bubbleBarUpdate.anythingChanged()) {
@@ -1850,7 +1948,7 @@ public class BubbleController implements ConfigurationChangeListener,
if (mStackView != null) {
mStackView.setVisibility(VISIBLE);
}
- if (mLayerView != null && isStackExpanded()) {
+ if (mLayerView != null) {
mLayerView.setVisibility(VISIBLE);
}
}
@@ -1864,10 +1962,17 @@ public class BubbleController implements ConfigurationChangeListener,
}
@VisibleForTesting
+ @Nullable
public BubbleStackView getStackView() {
return mStackView;
}
+ @VisibleForTesting
+ @Nullable
+ public BubbleBarLayerView getLayerView() {
+ return mLayerView;
+ }
+
/**
* Check if notification panel is in an expanded state.
* Makes a call to System UI process and delivers the result via {@code callback} on the
@@ -2006,27 +2111,30 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void registerBubbleListener(IBubblesListener listener) {
- mMainExecutor.execute(() -> {
- mListener.register(listener);
- });
+ mMainExecutor.execute(() -> mListener.register(listener));
}
@Override
public void unregisterBubbleListener(IBubblesListener listener) {
- mMainExecutor.execute(() -> mListener.unregister());
+ mMainExecutor.execute(mListener::unregister);
}
@Override
- public void showBubble(String key, boolean onLauncherHome) {
- mMainExecutor.execute(() -> {
- mBubblePositioner.setShowingInBubbleBar(onLauncherHome);
- mController.expandStackAndSelectBubbleFromLauncher(key);
- });
+ public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+ mMainExecutor.execute(
+ () -> mController.expandStackAndSelectBubbleFromLauncher(
+ key, bubbleBarOffsetX, bubbleBarOffsetY));
+ }
+
+ @Override
+ public void removeBubble(String key) {
+ mMainExecutor.execute(
+ () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE));
}
@Override
- public void removeBubble(String key, int reason) {
- // TODO (b/271466616) allow removals from launcher
+ public void removeAllBubbles() {
+ mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE));
}
@Override
@@ -2035,8 +2143,8 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void onTaskbarStateChanged(int newState) {
- // TODO (b/269670598)
+ public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
+ mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged));
}
}
@@ -2148,7 +2256,7 @@ public class BubbleController implements ConfigurationChangeListener,
pw.println(" suppressing: " + key);
}
- pw.print("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values());
+ pw.println("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index e37c785f15f5..df12999afc9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -17,7 +17,6 @@ package com.android.wm.shell.bubbles
import android.annotation.SuppressLint
import android.annotation.UserIdInt
-import android.content.Context
import android.content.pm.LauncherApps
import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
@@ -25,6 +24,8 @@ import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LA
import android.content.pm.UserInfo
import android.os.UserHandle
import android.util.Log
+import android.util.SparseArray
+import com.android.internal.annotations.VisibleForTesting
import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener
import com.android.wm.shell.bubbles.storage.BubbleEntity
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
@@ -33,19 +34,19 @@ import com.android.wm.shell.common.ShellExecutor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
-internal class BubbleDataRepository(
- context: Context,
+class BubbleDataRepository(
private val launcherApps: LauncherApps,
- private val mainExecutor: ShellExecutor
+ private val mainExecutor: ShellExecutor,
+ private val persistentRepository: BubblePersistentRepository,
) {
private val volatileRepository = BubbleVolatileRepository(launcherApps)
- private val persistentRepository = BubblePersistentRepository(context)
- private val ioScope = CoroutineScope(Dispatchers.IO)
+ private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var job: Job? = null
// For use in Bubble construction.
@@ -98,6 +99,44 @@ internal class BubbleDataRepository(
if (volatileRepository.sanitizeBubbles(userIds)) persistToDisk()
}
+ /**
+ * Removes all entities that don't have a user in the activeUsers list, if any entities were
+ * removed it persists the new list to disk.
+ */
+ @VisibleForTesting
+ fun filterForActiveUsersAndPersist(
+ activeUsers: List<Int>,
+ entitiesByUser: SparseArray<List<BubbleEntity>>
+ ): SparseArray<List<BubbleEntity>> {
+ val validEntitiesByUser = SparseArray<List<BubbleEntity>>()
+ var entitiesChanged = false
+ for (i in 0 until entitiesByUser.size()) {
+ val parentUserId = entitiesByUser.keyAt(i)
+ if (activeUsers.contains(parentUserId)) {
+ val validEntities = mutableListOf<BubbleEntity>()
+ // Check if each of the bubbles in the top-level user still has a valid user
+ // as it could belong to a profile and have a different id from the parent.
+ for (entity in entitiesByUser.get(parentUserId)) {
+ if (activeUsers.contains(entity.userId)) {
+ validEntities.add(entity)
+ } else {
+ entitiesChanged = true
+ }
+ }
+ if (validEntities.isNotEmpty()) {
+ validEntitiesByUser.put(parentUserId, validEntities)
+ }
+ } else {
+ entitiesChanged = true
+ }
+ }
+ if (entitiesChanged) {
+ persistToDisk(validEntitiesByUser)
+ return validEntitiesByUser
+ }
+ return entitiesByUser
+ }
+
private fun transform(bubbles: List<Bubble>): List<BubbleEntity> {
return bubbles.mapNotNull { b ->
BubbleEntity(
@@ -129,15 +168,18 @@ internal class BubbleDataRepository(
* Job C resumes and reaches yield() and is then cancelled
* Job D resumes and performs another blocking I/O
*/
- private fun persistToDisk() {
+ @VisibleForTesting
+ fun persistToDisk(
+ entitiesByUser: SparseArray<List<BubbleEntity>> = volatileRepository.bubbles
+ ) {
val prev = job
- job = ioScope.launch {
+ job = coroutineScope.launch {
// if there was an ongoing disk I/O operation, they can be cancelled
prev?.cancelAndJoin()
// check for cancellation before disk I/O
yield()
// save to disk
- persistentRepository.persistsToDisk(volatileRepository.bubbles)
+ persistentRepository.persistsToDisk(entitiesByUser)
}
}
@@ -148,7 +190,11 @@ internal class BubbleDataRepository(
* bubbles.
*/
@SuppressLint("WrongConstant")
- fun loadBubbles(userId: Int, cb: (List<Bubble>) -> Unit) = ioScope.launch {
+ fun loadBubbles(
+ userId: Int,
+ currentUsers: List<Int>,
+ cb: (List<Bubble>) -> Unit
+ ) = coroutineScope.launch {
/**
* Load BubbleEntity from disk.
* e.g.
@@ -159,7 +205,12 @@ internal class BubbleDataRepository(
* ]
*/
val entitiesByUser = persistentRepository.readFromDisk()
- val entities = entitiesByUser.get(userId) ?: return@launch
+
+ // Before doing anything, validate that the entities we loaded are valid & have an existing
+ // user.
+ val validEntitiesByUser = filterForActiveUsersAndPersist(currentUsers, entitiesByUser)
+
+ val entities = validEntitiesByUser.get(userId) ?: return@launch
volatileRepository.addBubbles(userId, entities)
/**
* Extract userId/packageName from these entities.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index dce6b56261ff..76662c47238f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -47,12 +47,16 @@ public class BubbleDebugConfig {
static final boolean DEBUG_USER_EDUCATION = false;
static final boolean DEBUG_POSITIONER = false;
public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
- static final boolean DEBUG_BUBBLE_GESTURE = false;
public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
private static final boolean FORCE_SHOW_USER_EDUCATION = false;
private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
"force_show_bubbles_user_education";
+ /**
+ * When set to true, bubbles user education flow never shows up.
+ */
+ private static final String FORCE_HIDE_USER_EDUCATION_SETTING =
+ "force_hide_bubbles_user_education";
/**
* @return whether we should force show user education for bubbles. Used for debugging & demos.
@@ -63,6 +67,14 @@ public class BubbleDebugConfig {
return FORCE_SHOW_USER_EDUCATION || forceShow;
}
+ /**
+ * @return whether we should never show user education for bubbles. Used in tests.
+ */
+ static boolean neverShowUserEducation(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ FORCE_HIDE_USER_EDUCATION_SETTING, 0) != 0;
+ }
+
static String formatBubblesString(List<Bubble> bubbles, BubbleViewProvider selected) {
StringBuilder sb = new StringBuilder();
for (Bubble bubble : bubbles) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
new file mode 100644
index 000000000000..e57f02c71e44
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.util.Log
+import androidx.core.content.edit
+import com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
+
+/** Manages bubble education flags. Provides convenience methods to check the education state */
+class BubbleEducationController(private val context: Context) {
+ private val prefs = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+
+ /** Whether the user has seen the stack education */
+ @get:JvmName(name = "hasSeenStackEducation")
+ var hasSeenStackEducation: Boolean
+ get() = prefs.getBoolean(PREF_STACK_EDUCATION, false)
+ set(value) = prefs.edit { putBoolean(PREF_STACK_EDUCATION, value) }
+
+ /** Whether the user has seen the expanded view "manage" menu education */
+ @get:JvmName(name = "hasSeenManageEducation")
+ var hasSeenManageEducation: Boolean
+ get() = prefs.getBoolean(PREF_MANAGED_EDUCATION, false)
+ set(value) = prefs.edit { putBoolean(PREF_MANAGED_EDUCATION, value) }
+
+ /** Whether education view should show for the collapsed stack. */
+ fun shouldShowStackEducation(bubble: BubbleViewProvider?): Boolean {
+ val shouldShow = bubble != null &&
+ bubble.isConversationBubble && // show education for conversation bubbles only
+ (!hasSeenStackEducation || BubbleDebugConfig.forceShowUserEducation(context))
+ logDebug("Show stack edu: $shouldShow")
+ return shouldShow
+ }
+
+ /** Whether the educational view should show for the expanded view "manage" menu. */
+ fun shouldShowManageEducation(bubble: BubbleViewProvider?): Boolean {
+ val shouldShow = bubble != null &&
+ bubble.isConversationBubble && // show education for conversation bubbles only
+ (!hasSeenManageEducation || BubbleDebugConfig.forceShowUserEducation(context))
+ logDebug("Show manage edu: $shouldShow")
+ return shouldShow
+ }
+
+ private fun logDebug(message: String) {
+ if (DEBUG_USER_EDUCATION) {
+ Log.d(TAG, message)
+ }
+ }
+
+ companion object {
+ private val TAG = if (TAG_WITH_CLASS_NAME) "BubbleEducationController" else TAG_BUBBLES
+ const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
+ const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
+ }
+}
+
+/** Convenience extension method to check if the bubble is a conversation bubble */
+private val BubbleViewProvider.isConversationBubble: Boolean
+ get() = if (this is Bubble) isConversation else false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index e1a3f3a1ac5d..e6986012dd88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -312,9 +312,13 @@ public class BubbleExpandedView extends LinearLayout {
+ " bubble=" + getBubbleKey());
}
if (mBubble != null) {
- // Must post because this is called from a binder thread.
- post(() -> mController.removeBubble(
- mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED));
+ mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ }
+ if (mTaskView != null) {
+ // Release the surface
+ mTaskView.release();
+ removeView(mTaskView);
+ mTaskView = null;
}
}
@@ -948,9 +952,9 @@ public class BubbleExpandedView extends LinearLayout {
mTaskView.onLocationChanged();
}
if (mIsOverflow) {
- post(() -> {
- mOverflowView.show();
- });
+ // post this to the looper so that the view has a chance to be laid out before it can
+ // calculate row and column sizes correctly.
+ post(() -> mOverflowView.show());
}
}
@@ -1058,8 +1062,10 @@ public class BubbleExpandedView extends LinearLayout {
}
/**
- * Cleans up anything related to the task and {@code TaskView}. If this view should be reused
- * after this method is called, then
+ * Cleans up anything related to the task. The TaskView itself is released after the task
+ * has been removed.
+ *
+ * If this view should be reused after this method is called, then
* {@link #initialize(BubbleController, BubbleStackView, boolean)} must be invoked first.
*/
public void cleanUpExpandedState() {
@@ -1081,10 +1087,7 @@ public class BubbleExpandedView extends LinearLayout {
}
}
if (mTaskView != null) {
- // Release the surface & other task view related things
- mTaskView.release();
- removeView(mTaskView);
- mTaskView = null;
+ mTaskView.setVisibility(GONE);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index df7f5b4f0150..df19757203eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -44,6 +44,7 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var expandedView: BubbleExpandedView?
+ private var bubbleBarExpandedView: BubbleBarExpandedView? = null
private var overflowBtn: BadgedImageView?
init {
@@ -53,19 +54,26 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
}
/** Call before use and again if cleanUpExpandedState was called. */
- fun initialize(controller: BubbleController) {
- createExpandedView()
- getExpandedView()?.initialize(controller, controller.stackView, true /* isOverflow */)
+ fun initialize(controller: BubbleController, forBubbleBar: Boolean) {
+ if (forBubbleBar) {
+ createBubbleBarExpandedView().initialize(controller, true /* isOverflow */)
+ } else {
+ createExpandedView()
+ .initialize(controller, controller.stackView, true /* isOverflow */)
+ }
}
fun cleanUpExpandedState() {
expandedView?.cleanUpExpandedState()
expandedView = null
+ bubbleBarExpandedView?.cleanUpExpandedState()
+ bubbleBarExpandedView = null
}
fun update() {
updateResources()
getExpandedView()?.applyThemeAttrs()
+ getBubbleBarExpandedView()?.applyThemeAttrs()
// Apply inset and new style to fresh icon drawable.
getIconView()?.setIconImageResource(R.drawable.bubble_ic_overflow_button)
updateBtnTheme()
@@ -151,26 +159,39 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
overflowBtn?.updateDotVisibility(true /* animate */)
}
- fun createExpandedView(): BubbleExpandedView? {
- expandedView =
+ /** Creates the expanded view for bubbles showing in the stack view. */
+ private fun createExpandedView(): BubbleExpandedView {
+ val view =
inflater.inflate(
R.layout.bubble_expanded_view,
null /* root */,
false /* attachToRoot */
) as BubbleExpandedView
- expandedView?.applyThemeAttrs()
+ view.applyThemeAttrs()
+ expandedView = view
updateResources()
- return expandedView
+ return view
}
override fun getExpandedView(): BubbleExpandedView? {
return expandedView
}
- override fun getBubbleBarExpandedView(): BubbleBarExpandedView? {
- return null
+ /** Creates the expanded view for bubbles showing in the bubble bar. */
+ private fun createBubbleBarExpandedView(): BubbleBarExpandedView {
+ val view =
+ inflater.inflate(
+ R.layout.bubble_bar_expanded_view,
+ null, /* root */
+ false /* attachToRoot*/
+ ) as BubbleBarExpandedView
+ view.applyThemeAttrs()
+ bubbleBarExpandedView = view
+ return view
}
+ override fun getBubbleBarExpandedView(): BubbleBarExpandedView? = bubbleBarExpandedView
+
override fun getDotColor(): Int {
return dotColor
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt
new file mode 100644
index 000000000000..bdb09e11d5ad
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles
+
+import android.graphics.Color
+import com.android.wm.shell.R
+import com.android.wm.shell.common.bubbles.BubblePopupDrawable
+import com.android.wm.shell.common.bubbles.BubblePopupView
+
+/**
+ * A convenience method to setup the [BubblePopupView] with the correct config using local resources
+ */
+fun BubblePopupView.setup() {
+ val attrs =
+ context.obtainStyledAttributes(
+ intArrayOf(
+ com.android.internal.R.attr.materialColorSurface,
+ android.R.attr.dialogCornerRadius
+ )
+ )
+
+ val res = context.resources
+ val config =
+ BubblePopupDrawable.Config(
+ color = attrs.getColor(0, Color.WHITE),
+ cornerRadius = attrs.getDimension(1, 0f),
+ contentPadding = res.getDimensionPixelSize(R.dimen.bubble_popup_padding),
+ arrowWidth = res.getDimension(R.dimen.bubble_popup_arrow_width),
+ arrowHeight = res.getDimension(R.dimen.bubble_popup_arrow_height),
+ arrowRadius = res.getDimension(R.dimen.bubble_popup_arrow_corner_radius)
+ )
+ attrs.recycle()
+ setupBackground(config)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index d101b0c4d7e8..ea7053d8ee49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -102,10 +103,7 @@ public class BubblePositioner {
private int[] mPaddings = new int[4];
private boolean mShowingInBubbleBar;
- private boolean mBubblesOnHome;
- private int mBubbleBarSize;
- private int mBubbleBarHomeAdjustment;
- private final PointF mBubbleBarPosition = new PointF();
+ private final Point mBubbleBarPosition = new Point();
public BubblePositioner(Context context, WindowManager windowManager) {
mContext = context;
@@ -166,11 +164,9 @@ public class BubblePositioner {
mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
- mBubbleBarHomeAdjustment = mExpandedViewPadding / 2;
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleOffscreenAmount = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
- mBubbleBarSize = res.getDimensionPixelSize(R.dimen.bubblebar_size);
if (mShowingInBubbleBar) {
mExpandedViewLargeScreenWidth = isLandscape()
@@ -657,22 +653,58 @@ public class BubblePositioner {
}
/**
- * @return the stack position to use if we don't have a saved location or if user education
- * is being shown.
+ * Returns whether the {@link #getRestingPosition()} is equal to the default start position
+ * initialized for bubbles, if {@code true} this means the user hasn't moved the bubble
+ * from the initial start position (or they haven't received a bubble yet).
+ */
+ public boolean hasUserModifiedDefaultPosition() {
+ PointF defaultStart = getDefaultStartPosition();
+ return mRestingStackPosition != null
+ && !mRestingStackPosition.equals(defaultStart);
+ }
+
+ /**
+ * Returns the stack position to use if we don't have a saved location or if user education
+ * is being shown, for a normal bubble.
*/
public PointF getDefaultStartPosition() {
- // Start on the left if we're in LTR, right otherwise.
- final boolean startOnLeft =
- mContext.getResources().getConfiguration().getLayoutDirection()
- != LAYOUT_DIRECTION_RTL;
- final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset(
- R.dimen.bubble_stack_starting_offset_y);
- // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
- return new BubbleStackView.RelativeStackPosition(
- startOnLeft,
- startingVerticalOffset / mPositionRect.height())
- .getAbsolutePositionInRegion(getAllowableStackPositionRegion(
- 1 /* default starts with 1 bubble */));
+ return getDefaultStartPosition(false /* isAppBubble */);
+ }
+
+ /**
+ * The stack position to use if we don't have a saved location or if user education
+ * is being shown.
+ *
+ * @param isAppBubble whether this start position is for an app bubble or not.
+ */
+ public PointF getDefaultStartPosition(boolean isAppBubble) {
+ final int layoutDirection = mContext.getResources().getConfiguration().getLayoutDirection();
+ // Normal bubbles start on the left if we're in LTR, right otherwise.
+ // TODO (b/294284894): update language around "app bubble" here
+ // App bubbles start on the right in RTL, left otherwise.
+ final boolean startOnLeft = isAppBubble
+ ? layoutDirection == LAYOUT_DIRECTION_RTL
+ : layoutDirection != LAYOUT_DIRECTION_RTL;
+ final RectF allowableStackPositionRegion = getAllowableStackPositionRegion(
+ 1 /* default starts with 1 bubble */);
+ if (isLargeScreen()) {
+ // We want the stack to be visually centered on the edge, so we need to base it
+ // of a rect that includes insets.
+ final float desiredY = mScreenRect.height() / 2f - (mBubbleSize / 2f);
+ final float offset = desiredY / mScreenRect.height();
+ return new BubbleStackView.RelativeStackPosition(
+ startOnLeft,
+ offset)
+ .getAbsolutePositionInRegion(allowableStackPositionRegion);
+ } else {
+ final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset(
+ R.dimen.bubble_stack_starting_offset_y);
+ // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
+ return new BubbleStackView.RelativeStackPosition(
+ startOnLeft,
+ startingVerticalOffset / mPositionRect.height())
+ .getAbsolutePositionInRegion(allowableStackPositionRegion);
+ }
}
/**
@@ -723,27 +755,36 @@ public class BubblePositioner {
}
/**
- * Sets whether bubbles are showing on launcher home, in which case positions are different.
+ * Sets the position of the bubble bar in screen coordinates.
+ *
+ * @param offsetX the offset of the bubble bar from the edge of the screen on the X axis
+ * @param offsetY the offset of the bubble bar from the edge of the screen on the Y axis
*/
- public void setBubblesOnHome(boolean bubblesOnHome) {
- mBubblesOnHome = bubblesOnHome;
+ public void setBubbleBarPosition(int offsetX, int offsetY) {
+ mBubbleBarPosition.set(
+ getAvailableRect().width() - offsetX,
+ getAvailableRect().height() + mInsets.top + mInsets.bottom - offsetY);
}
/**
* How wide the expanded view should be when showing from the bubble bar.
*/
- public int getExpandedViewWidthForBubbleBar() {
- return mExpandedViewLargeScreenWidth;
+ public int getExpandedViewWidthForBubbleBar(boolean isOverflow) {
+ return isOverflow ? mOverflowWidth : mExpandedViewLargeScreenWidth;
}
/**
* How tall the expanded view should be when showing from the bubble bar.
*/
- public int getExpandedViewHeightForBubbleBar() {
- return getAvailableRect().height()
- - mBubbleBarSize
- - mExpandedViewPadding * 2
- - getBubbleBarHomeAdjustment();
+ public int getExpandedViewHeightForBubbleBar(boolean isOverflow) {
+ return isOverflow
+ ? mOverflowHeight
+ : getExpandedViewBottomForBubbleBar() - mInsets.top - mExpandedViewPadding;
+ }
+
+ /** The bottom position of the expanded view when showing above the bubble bar. */
+ public int getExpandedViewBottomForBubbleBar() {
+ return mBubbleBarPosition.y - mExpandedViewPadding;
}
/**
@@ -756,19 +797,7 @@ public class BubblePositioner {
/**
* Returns the on screen co-ordinates of the bubble bar.
*/
- public PointF getBubbleBarPosition() {
- mBubbleBarPosition.set(getAvailableRect().width() - mBubbleBarSize,
- getAvailableRect().height() - mBubbleBarSize
- - mExpandedViewPadding - getBubbleBarHomeAdjustment());
+ public Point getBubbleBarPosition() {
return mBubbleBarPosition;
}
-
- /**
- * When bubbles are shown on launcher home, there's an extra bit of padding that needs to
- * be applied between the expanded view and the bubble bar. This returns the adjustment value
- * if bubbles are showing on home.
- */
- private int getBubbleBarHomeAdjustment() {
- return mBubblesOnHome ? mBubbleBarHomeAdjustment : 0;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 68fea41e134e..52c9bf8462ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -21,11 +21,11 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -46,7 +46,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
-import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
import android.view.Choreographer;
@@ -75,6 +74,7 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
@@ -88,6 +88,8 @@ import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
import com.android.wm.shell.bubbles.animation.StackAnimationController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.bubbles.RelativeTouchListener;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import java.io.PrintWriter;
@@ -105,10 +107,6 @@ import java.util.stream.Collectors;
*/
public class BubbleStackView extends FrameLayout
implements ViewTreeObserver.OnComputeInternalInsetsListener {
-
- public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE =
- SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true);
-
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
/** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
@@ -133,7 +131,7 @@ public class BubbleStackView extends FrameLayout
private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
- private static final float SCRIM_ALPHA = 0.6f;
+ private static final float SCRIM_ALPHA = 0.32f;
/** Minimum alpha value for scrim when alpha is being changed via drag */
private static final float MIN_SCRIM_ALPHA_FOR_DRAG = 0.2f;
@@ -307,6 +305,7 @@ public class BubbleStackView extends FrameLayout
String bubblesOnScreen = BubbleDebugConfig.formatBubblesString(
getBubblesOnScreen(), getExpandedBubble());
+ pw.print(" stack visibility : "); pw.println(getVisibility());
pw.print(" bubbles on screen: "); pw.println(bubblesOnScreen);
pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress);
pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing());
@@ -968,6 +967,8 @@ public class BubbleStackView extends FrameLayout
mBubbleContainer.bringToFront();
mBubbleOverflow = mBubbleData.getOverflow();
+
+ resetOverflowView();
mBubbleContainer.addView(mBubbleOverflow.getIconView(),
mBubbleContainer.getChildCount() /* index */,
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
@@ -1179,6 +1180,7 @@ public class BubbleStackView extends FrameLayout
removeView(mDismissView);
}
mDismissView = new DismissView(getContext());
+ DismissViewUtils.setup(mDismissView);
int elevation = getResources().getDimensionPixelSize(R.dimen.bubble_elevation);
addView(mDismissView);
@@ -1282,6 +1284,12 @@ public class BubbleStackView extends FrameLayout
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
Log.d(TAG, "Show manage edu: " + shouldShow);
}
+ if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
+ if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
+ Log.d(TAG, "Want to show manage edu, but it is forced hidden");
+ }
+ return false;
+ }
return shouldShow;
}
@@ -1314,6 +1322,12 @@ public class BubbleStackView extends FrameLayout
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
Log.d(TAG, "Show stack edu: " + shouldShow);
}
+ if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
+ if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
+ Log.d(TAG, "Want to show stack edu, but it is forced hidden");
+ }
+ return false;
+ }
return shouldShow;
}
@@ -1494,9 +1508,6 @@ public class BubbleStackView extends FrameLayout
getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- if (mBubbleOverflow != null) {
- mBubbleOverflow.cleanUpExpandedState();
- }
}
@Override
@@ -1764,13 +1775,26 @@ public class BubbleStackView extends FrameLayout
return;
}
+ if (firstBubble && bubble.isAppBubble() && !mPositioner.hasUserModifiedDefaultPosition()) {
+ // TODO (b/294284894): update language around "app bubble" here
+ // If it's an app bubble and we don't have a previous resting position, update the
+ // controllers to use the default position for the app bubble (it'd be different from
+ // the position initialized with the controllers originally).
+ PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
+ mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition);
+ mStackAnimationController.setStackPosition(startPosition);
+ mExpandedAnimationController.setCollapsePoint(startPosition);
+ // Set the translation x so that this bubble will animate in from the same side they
+ // expand / collapse on.
+ bubble.getIconView().setTranslationX(startPosition.x);
+ } else if (firstBubble) {
+ mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
+ }
+
mBubbleContainer.addView(bubble.getIconView(), 0,
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
mPositioner.getBubbleSize()));
- if (firstBubble) {
- mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
- }
// Set the dot position to the opposite of the side the stack is resting on, since the stack
// resting slightly off-screen would result in the dot also being off-screen.
bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */);
@@ -2019,9 +2043,7 @@ public class BubbleStackView extends FrameLayout
* Monitor for swipe up gesture that is used to collapse expanded view
*/
void startMonitoringSwipeUpGesture() {
- if (DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "startMonitoringSwipeUpGesture");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
if (isGestureNavEnabled()) {
@@ -2041,9 +2063,7 @@ public class BubbleStackView extends FrameLayout
* Stop monitoring for swipe up gesture
*/
void stopMonitoringSwipeUpGesture() {
- if (DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "stopMonitoringSwipeUpGesture");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
}
@@ -3411,6 +3431,19 @@ public class BubbleStackView extends FrameLayout
}
/**
+ * Removes the overflow view from the stack. This allows for re-adding it later to a new stack.
+ */
+ void resetOverflowView() {
+ BadgedImageView overflowIcon = mBubbleOverflow.getIconView();
+ if (overflowIcon != null) {
+ PhysicsAnimationLayout parent = (PhysicsAnimationLayout) overflowIcon.getParent();
+ if (parent != null) {
+ parent.removeViewNoAnimation(overflowIcon);
+ }
+ }
+ }
+
+ /**
* Holds some commonly queried information about the stack.
*/
public static class StackViewState {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 7a5815994dd0..da4a9898a44c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -15,6 +15,7 @@
*/
package com.android.wm.shell.bubbles;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -110,6 +111,9 @@ public class BubbleTaskViewHelper {
try {
options.setTaskAlwaysOnTop(true);
options.setLaunchedFromBubble(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
Intent fillInIntent = new Intent();
// Apply flags to make behaviour match documentLaunchMode=always.
@@ -117,11 +121,19 @@ public class BubbleTaskViewHelper {
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
if (mBubble.isAppBubble()) {
- PendingIntent pi = PendingIntent.getActivity(mContext, 0,
- mBubble.getAppBubbleIntent(),
- PendingIntent.FLAG_MUTABLE,
- null);
- mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+ Context context =
+ mContext.createContextAsUser(
+ mBubble.getUser(), Context.CONTEXT_RESTRICTED);
+ PendingIntent pi = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ mBubble.getAppBubbleIntent()
+ .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+ /* options= */ null);
+ mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
+ launchBounds);
} else if (mBubble.hasMetadataShortcutId()) {
options.setApplyActivityFlagsForBubbles(true);
mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 8ab9841ff0c2..39e3180ffe2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -104,7 +104,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
@Override
protected BubbleViewInfo doInBackground(Void... voids) {
- if (mController.get().isShowingAsBubbleBar()) {
+ if (!verifyState()) {
+ // If we're in an inconsistent state, then switched modes and should just bail now.
+ return null;
+ }
+ if (mLayerView.get() != null) {
return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(),
mLayerView.get(), mIconFactory, mBubble, mSkipInflation);
} else {
@@ -118,7 +122,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
if (isCancelled() || viewInfo == null) {
return;
}
+
mMainExecutor.execute(() -> {
+ if (!verifyState()) {
+ return;
+ }
mBubble.setViewInfo(viewInfo);
if (mCallback != null) {
mCallback.onBubbleViewsReady(mBubble);
@@ -126,6 +134,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
});
}
+ private boolean verifyState() {
+ if (mController.get().isShowingAsBubbleBar()) {
+ return mLayerView.get() != null;
+ } else {
+ return mStackView.get() != null;
+ }
+ }
+
/**
* Info necessary to render a bubble.
*/
@@ -160,39 +176,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
LayoutInflater inflater = LayoutInflater.from(c);
info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
- info.bubbleBarExpandedView.initialize(controller);
+ info.bubbleBarExpandedView.initialize(controller, false /* isOverflow */);
}
- if (b.getShortcutInfo() != null) {
- info.shortcutInfo = b.getShortcutInfo();
- }
-
- // App name & app icon
- PackageManager pm = BubbleController.getPackageManagerForUser(c,
- b.getUser().getIdentifier());
- ApplicationInfo appInfo;
- Drawable badgedIcon;
- Drawable appIcon;
- try {
- appInfo = pm.getApplicationInfo(
- b.getPackageName(),
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (appInfo != null) {
- info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
- }
- appIcon = pm.getApplicationIcon(b.getPackageName());
- badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
- } catch (PackageManager.NameNotFoundException exception) {
- // If we can't find package... don't think we should show the bubble.
- Log.w(TAG, "Unable to find package: " + b.getPackageName());
+ if (!populateCommonInfo(info, c, b, iconFactory)) {
+ // if we failed to update common fields return null
return null;
}
- info.rawBadgeBitmap = iconFactory.getBadgeBitmap(badgedIcon, false).icon;
-
return info;
}
@@ -215,66 +206,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
info.expandedView.initialize(controller, stackView, false /* isOverflow */);
}
- if (b.getShortcutInfo() != null) {
- info.shortcutInfo = b.getShortcutInfo();
- }
-
- // App name & app icon
- PackageManager pm = BubbleController.getPackageManagerForUser(c,
- b.getUser().getIdentifier());
- ApplicationInfo appInfo;
- Drawable badgedIcon;
- Drawable appIcon;
- try {
- appInfo = pm.getApplicationInfo(
- b.getPackageName(),
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (appInfo != null) {
- info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
- }
- appIcon = pm.getApplicationIcon(b.getPackageName());
- badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
- } catch (PackageManager.NameNotFoundException exception) {
- // If we can't find package... don't think we should show the bubble.
- Log.w(TAG, "Unable to find package: " + b.getPackageName());
+ if (!populateCommonInfo(info, c, b, iconFactory)) {
+ // if we failed to update common fields return null
return null;
}
- // Badged bubble image
- Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
- b.getIcon());
- if (bubbleDrawable == null) {
- // Default to app icon
- bubbleDrawable = appIcon;
- }
-
- BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
- b.isImportantConversation());
- info.badgeBitmap = badgeBitmapInfo.icon;
- // Raw badge bitmap never includes the important conversation ring
- info.rawBadgeBitmap = b.isImportantConversation()
- ? iconFactory.getBadgeBitmap(badgedIcon, false).icon
- : badgeBitmapInfo.icon;
-
- float[] bubbleBitmapScale = new float[1];
- info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
-
- // Dot color & placement
- Path iconPath = PathParser.createPathFromPathData(
- c.getResources().getString(com.android.internal.R.string.config_icon_mask));
- Matrix matrix = new Matrix();
- float scale = bubbleBitmapScale[0];
- float radius = DEFAULT_PATH_SIZE / 2f;
- matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
- radius /* pivot y */);
- iconPath.transform(matrix);
- info.dotPath = iconPath;
- info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
- Color.WHITE, WHITE_SCRIM_ALPHA);
-
// Flyout
info.flyoutMessage = b.getFlyoutMessage();
if (info.flyoutMessage != null) {
@@ -285,6 +221,83 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
}
}
+ /**
+ * Modifies the given {@code info} object and populates common fields in it.
+ *
+ * <p>This method returns {@code true} if the update was successful and {@code false} otherwise.
+ * Callers should assume that the info object is unusable if the update was unsuccessful.
+ */
+ private static boolean populateCommonInfo(
+ BubbleViewInfo info, Context c, Bubble b, BubbleIconFactory iconFactory) {
+ if (b.getShortcutInfo() != null) {
+ info.shortcutInfo = b.getShortcutInfo();
+ }
+
+ // App name & app icon
+ PackageManager pm = BubbleController.getPackageManagerForUser(c,
+ b.getUser().getIdentifier());
+ ApplicationInfo appInfo;
+ Drawable badgedIcon;
+ Drawable appIcon;
+ try {
+ appInfo = pm.getApplicationInfo(
+ b.getPackageName(),
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (appInfo != null) {
+ info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
+ }
+ appIcon = pm.getApplicationIcon(b.getPackageName());
+ badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
+ } catch (PackageManager.NameNotFoundException exception) {
+ // If we can't find package... don't think we should show the bubble.
+ Log.w(TAG, "Unable to find package: " + b.getPackageName());
+ return false;
+ }
+
+ Drawable bubbleDrawable = null;
+ try {
+ // Badged bubble image
+ bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
+ b.getIcon());
+ } catch (Exception e) {
+ // If we can't create the icon we'll default to the app icon
+ Log.w(TAG, "Exception creating icon for the bubble: " + b.getKey());
+ }
+
+ if (bubbleDrawable == null) {
+ // Default to app icon
+ bubbleDrawable = appIcon;
+ }
+
+ BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
+ b.isImportantConversation());
+ info.badgeBitmap = badgeBitmapInfo.icon;
+ // Raw badge bitmap never includes the important conversation ring
+ info.rawBadgeBitmap = b.isImportantConversation()
+ ? iconFactory.getBadgeBitmap(badgedIcon, false).icon
+ : badgeBitmapInfo.icon;
+
+ float[] bubbleBitmapScale = new float[1];
+ info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
+
+ // Dot color & placement
+ Path iconPath = PathParser.createPathFromPathData(
+ c.getResources().getString(com.android.internal.R.string.config_icon_mask));
+ Matrix matrix = new Matrix();
+ float scale = bubbleBitmapScale[0];
+ float radius = DEFAULT_PATH_SIZE / 2f;
+ matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+ radius /* pivot y */);
+ iconPath.transform(matrix);
+ info.dotPath = iconPath;
+ info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
+ Color.WHITE, WHITE_SCRIM_ALPHA);
+ return true;
+ }
+
@Nullable
static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) {
Objects.requireNonNull(context);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 4d329dd5d446..759246eb285d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -60,9 +60,11 @@ public interface Bubbles {
DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
- DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED})
+ DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED,
+ DISMISS_SWITCH_TO_STACK})
@Target({FIELD, LOCAL_VARIABLE, PARAMETER})
- @interface DismissReason {}
+ @interface DismissReason {
+ }
int DISMISS_USER_GESTURE = 1;
int DISMISS_AGED = 2;
@@ -80,6 +82,7 @@ public interface Bubbles {
int DISMISS_NO_BUBBLE_UP = 14;
int DISMISS_RELOAD_FROM_DISK = 15;
int DISMISS_USER_REMOVED = 16;
+ int DISMISS_SWITCH_TO_STACK = 17;
/** Returns a binder that can be passed to an external process to manipulate Bubbles. */
default IBubbles createExternalInterface() {
@@ -120,8 +123,8 @@ public interface Bubbles {
/**
* This method has different behavior depending on:
- * - if an app bubble exists
- * - if an app bubble is expanded
+ * - if an app bubble exists
+ * - if an app bubble is expanded
*
* If no app bubble exists, this will add and expand a bubble with the provided intent. The
* intent must be explicit (i.e. include a package name or fully qualified component class name)
@@ -135,13 +138,13 @@ public interface Bubbles {
* the bubble or bubble stack.
*
* Some notes:
- * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
- * tracked in b/273533235.
- * - Calling this method with a different intent than the existing app bubble will do nothing
+ * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+ * tracked in b/273533235.
+ * - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
- * @param user the {@link UserHandle} of the user to start this activity for.
- * @param icon the {@link Icon} to use for the bubble view.
+ * @param user the {@link UserHandle} of the user to start this activity for.
+ * @param icon the {@link Icon} to use for the bubble view.
*/
void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon);
@@ -172,13 +175,12 @@ public interface Bubbles {
* {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add
* {@link BubbleData#addSummaryToSuppress}.
*
- * @param entry the notification of the BubbleEntry should be removed.
- * @param children the list of child notification of the BubbleEntry from 1st param entry,
- * this will be null if entry does have no children.
+ * @param entry the notification of the BubbleEntry should be removed.
+ * @param children the list of child notification of the BubbleEntry from 1st param entry,
+ * this will be null if entry does have no children.
* @param removeCallback the remove callback for SystemUI side to remove notification, the int
* number should be list position of children list and use -1 for
* removing the parent notification.
- *
* @return true if we want to intercept the dismissal of the entry, else false.
*/
boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children,
@@ -200,9 +202,9 @@ public interface Bubbles {
/**
* Called when new notification entry updated.
*
- * @param entry the {@link BubbleEntry} by the notification.
+ * @param entry the {@link BubbleEntry} by the notification.
* @param shouldBubbleUp {@code true} if this notification should bubble up.
- * @param fromSystem {@code true} if this update is from NotificationManagerService.
+ * @param fromSystem {@code true} if this update is from NotificationManagerService.
*/
void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem);
@@ -218,7 +220,7 @@ public interface Bubbles {
* filtering and sorting. This is used to dismiss or create bubbles based on changes in
* permissions on the notification channel or the global setting.
*
- * @param rankingMap the updated ranking map from NotificationListenerService
+ * @param rankingMap the updated ranking map from NotificationListenerService
* @param entryDataByKey a map of ranking key to bubble entry and whether the entry should
* bubble up
*/
@@ -230,9 +232,9 @@ public interface Bubbles {
* Called when a notification channel is modified, in response to
* {@link NotificationListenerService#onNotificationChannelModified}.
*
- * @param pkg the package the notification channel belongs to.
- * @param user the user the notification channel belongs to.
- * @param channel the channel being modified.
+ * @param pkg the package the notification channel belongs to.
+ * @param user the user the notification channel belongs to.
+ * @param channel the channel being modified.
* @param modificationType the type of modification that occurred to the channel.
*/
void onNotificationChannelModified(
@@ -300,7 +302,7 @@ public interface Bubbles {
* Called when the expansion state of the bubble stack changes.
*
* @param isExpanding whether it's expanding or collapsing
- * @param key the notification key associated with bubble being expanded
+ * @param key the notification key associated with bubble being expanded
*/
void onBubbleExpandChanged(boolean isExpanding, String key);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
index 3a3a378e00d3..137568458e3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
@@ -18,10 +18,10 @@ package com.android.wm.shell.bubbles;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.content.Context;
import android.hardware.input.InputManager;
-import android.util.Log;
import android.view.Choreographer;
import android.view.InputChannel;
import android.view.InputEventReceiver;
@@ -29,6 +29,7 @@ import android.view.InputMonitor;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
/**
@@ -58,9 +59,7 @@ class BubblesNavBarGestureTracker {
* @param listener listener that is notified of touch events
*/
void start(MotionEventListener listener) {
- if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "start monitoring bubbles swipe up gesture");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "start monitoring bubbles swipe up gesture");
stopInternal();
@@ -76,9 +75,7 @@ class BubblesNavBarGestureTracker {
}
void stop() {
- if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "stop monitoring bubbles swipe up gesture");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "stop monitoring bubbles swipe up gesture");
stopInternal();
}
@@ -94,9 +91,7 @@ class BubblesNavBarGestureTracker {
}
private void onInterceptTouch() {
- if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "intercept touch event");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "intercept touch event");
if (mInputMonitor != null) {
mInputMonitor.pilferPointers();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
index 844526ca0f35..b7107f09b17f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
@@ -16,19 +16,20 @@
package com.android.wm.shell.bubbles;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.content.Context;
import android.graphics.PointF;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
+
/**
* Handles {@link MotionEvent}s for bubbles that begin in the nav bar area
*/
@@ -112,10 +113,8 @@ class BubblesNavBarMotionEventHandler {
private boolean isInGestureRegion(MotionEvent ev) {
// Only handles touch events beginning in navigation bar system gesture zone
if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) {
- if (DEBUG_BUBBLE_GESTURE) {
- Log.d(TAG, "handling touch y=" + ev.getY()
- + " navBarGestureZone=" + mPositioner.getNavBarGestureZone());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "handling touch x=%d y=%d navBarGestureZone=%s",
+ (int) ev.getX(), (int) ev.getY(), mPositioner.getNavBarGestureZone());
return true;
}
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
new file mode 100644
index 000000000000..ed3624035757
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+@file:JvmName("DismissViewUtils")
+
+package com.android.wm.shell.bubbles
+
+import com.android.wm.shell.R
+import com.android.wm.shell.common.bubbles.DismissView
+
+fun DismissView.setup() {
+ setup(DismissView.Config(
+ targetSizeResId = R.dimen.dismiss_circle_size,
+ iconSizeResId = R.dimen.dismiss_target_x_size,
+ bottomMarginResId = R.dimen.floating_dismiss_bottom_margin,
+ floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height,
+ floatingGradientColorResId = android.R.color.system_neutral1_900,
+ backgroundResId = R.drawable.dismiss_circle_background,
+ iconResId = R.drawable.pip_ic_close_white
+ ))
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 862e818a998b..4dda0688b790 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -29,12 +29,14 @@ interface IBubbles {
oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
- oneway void showBubble(in String key, in boolean onLauncherHome) = 3;
+ oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3;
- oneway void removeBubble(in String key, in int reason) = 4;
+ oneway void removeBubble(in String key) = 4;
- oneway void collapseBubbles() = 5;
+ oneway void removeAllBubbles() = 5;
- oneway void onTaskbarStateChanged(in int newState) = 6;
+ oneway void collapseBubbles() = 6;
+
+ oneway void onBubbleDrag(in String key, in boolean isBeingDragged) = 7;
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 33629f9f4622..4d7042bbb3d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -19,7 +19,6 @@ package com.android.wm.shell.bubbles.animation;
import static android.view.View.LAYOUT_DIRECTION_RTL;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
-import static com.android.wm.shell.bubbles.BubbleStackView.ENABLE_FLING_TO_DISMISS_BUBBLE;
import android.content.res.Resources;
import android.graphics.Path;
@@ -132,6 +131,16 @@ public class ExpandedAnimationController
private BubbleStackView mBubbleStackView;
+ /**
+ * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause
+ * the rest of the bubbles to animate to fill the gap.
+ */
+ private boolean mBubbleDraggedOutEnough = false;
+
+ /** End action to run when the lead bubble's expansion animation completes. */
+ @Nullable
+ private Runnable mLeadBubbleEndAction;
+
public ExpandedAnimationController(BubblePositioner positioner,
Runnable onBubbleAnimatedOutAction, BubbleStackView stackView) {
mPositioner = positioner;
@@ -142,14 +151,12 @@ public class ExpandedAnimationController
}
/**
- * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause
- * the rest of the bubbles to animate to fill the gap.
+ * Overrides the collapse location without actually collapsing the stack.
+ * @param point the new collapse location.
*/
- private boolean mBubbleDraggedOutEnough = false;
-
- /** End action to run when the lead bubble's expansion animation completes. */
- @Nullable
- private Runnable mLeadBubbleEndAction;
+ public void setCollapsePoint(PointF point) {
+ mCollapsePoint = point;
+ }
/**
* Animates expanding the bubbles into a row along the top of the screen, optionally running an
@@ -355,7 +362,6 @@ public class ExpandedAnimationController
mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
- mMagnetizedBubbleDraggingOut.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_BUBBLE);
}
private void springBubbleTo(View bubble, float x, float y) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
index beb1c5fa6c10..f3cc514d2972 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
@@ -327,6 +327,12 @@ public class PhysicsAnimationLayout extends FrameLayout {
addViewInternal(child, index, params, false /* isReorder */);
}
+ /** Removes the child view immediately. */
+ public void removeViewNoAnimation(View view) {
+ super.removeView(view);
+ view.setTag(R.id.physics_animator_tag, null);
+ }
+
@Override
public void removeView(View view) {
if (mController != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 5533842f2d89..aad268394305 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -17,7 +17,6 @@
package com.android.wm.shell.bubbles.animation;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
-import static com.android.wm.shell.bubbles.BubbleStackView.ENABLE_FLING_TO_DISMISS_BUBBLE;
import android.content.ContentResolver;
import android.content.res.Resources;
@@ -298,9 +297,6 @@ public class StackAnimationController extends
/** Whether the stack is on the left side of the screen. */
public boolean isStackOnLeftSide() {
- if (mLayout == null || !isStackPositionSet()) {
- return true; // Default to left, which is where it starts by default.
- }
return mPositioner.isStackOnLeft(mStackPosition);
}
@@ -1026,7 +1022,6 @@ public class StackAnimationController extends
};
mMagnetizedStack.setHapticsEnabled(true);
mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
- mMagnetizedStack.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_BUBBLE);
}
final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 23f65f943aa4..689323b725ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -21,12 +21,15 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
-import android.graphics.PointF;
+import android.graphics.Point;
import android.util.Log;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
@@ -110,7 +113,8 @@ public class BubbleBarAnimationHelper {
/**
* Animates the provided bubble's expanded view to the expanded state.
*/
- public void animateExpansion(BubbleViewProvider expandedBubble) {
+ public void animateExpansion(BubbleViewProvider expandedBubble,
+ @Nullable Runnable afterAnimation) {
mExpandedBubble = expandedBubble;
if (mExpandedBubble == null) {
return;
@@ -132,7 +136,7 @@ public class BubbleBarAnimationHelper {
bev.setVisibility(VISIBLE);
// Set the pivot point for the scale, so the view animates out from the bubble bar.
- PointF bubbleBarPosition = mPositioner.getBubbleBarPosition();
+ Point bubbleBarPosition = mPositioner.getBubbleBarPosition();
mExpandedViewContainerMatrix.setScale(
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
@@ -159,6 +163,9 @@ public class BubbleBarAnimationHelper {
bev.setAnimationMatrix(null);
updateExpandedView();
bev.setSurfaceZOrderedOnTop(false);
+ if (afterAnimation != null) {
+ afterAnimation.run();
+ }
})
.start();
}
@@ -208,6 +215,14 @@ public class BubbleBarAnimationHelper {
mExpandedViewAlphaAnimator.reverse();
}
+ /**
+ * Cancel current animations
+ */
+ public void cancelAnimations() {
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
+ mExpandedViewAlphaAnimator.cancel();
+ }
+
private void updateExpandedView() {
if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) {
Log.w(TAG, "Trying to update the expanded view without a bubble");
@@ -215,9 +230,10 @@ public class BubbleBarAnimationHelper {
}
BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView();
+ boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
final int padding = mPositioner.getBubbleBarExpandedViewPadding();
- final int width = mPositioner.getExpandedViewWidthForBubbleBar();
- final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+ final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+ final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams();
lp.width = width;
lp.height = height;
@@ -227,7 +243,8 @@ public class BubbleBarAnimationHelper {
} else {
bbev.setX(mPositioner.getAvailableRect().width() - width - padding);
}
- bbev.setY(mPositioner.getInsets().top + padding);
+ bbev.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
bbev.updateLocation();
+ bbev.maybeShowOverflow();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index b8f049becb6f..79f188ab2611 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -16,12 +16,16 @@
package com.android.wm.shell.bubbles.bar;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
+import android.graphics.Insets;
import android.graphics.Outline;
+import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
@@ -30,26 +34,48 @@ import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleOverflowContainerView;
import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
+import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.taskview.TaskView;
+import java.util.function.Supplier;
+
/**
* Expanded view of a bubble when it's part of the bubble bar.
*
* {@link BubbleController#isShowingAsBubbleBar()}
*/
public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener {
+ /**
+ * The expanded view listener notifying the {@link BubbleBarLayerView} about the internal
+ * actions and events
+ */
+ public interface Listener {
+ /** Called when the task view task is first created. */
+ void onTaskCreated();
+ /** Called when expanded view needs to un-bubble the given conversation */
+ void onUnBubbleConversation(String bubbleKey);
+ /** Called when expanded view task view back button pressed */
+ void onBackPressed();
+ }
private static final String TAG = BubbleBarExpandedView.class.getSimpleName();
private static final int INVALID_TASK_ID = -1;
private BubbleController mController;
+ private boolean mIsOverflow;
private BubbleTaskViewHelper mBubbleTaskViewHelper;
+ private BubbleBarMenuViewController mMenuViewController;
+ private @Nullable Supplier<Rect> mLayerBoundsSupplier;
+ private @Nullable Listener mListener;
- private HandleView mMenuView;
- private TaskView mTaskView;
+ private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
+ private @Nullable TaskView mTaskView;
+ private @Nullable BubbleOverflowContainerView mOverflowView;
+
+ private int mCaptionHeight;
- private int mMenuHeight;
private int mBackgroundColor;
private float mCornerRadius = 0f;
@@ -83,11 +109,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
super.onFinishInflate();
Context context = getContext();
setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
- mMenuHeight = context.getResources().getDimensionPixelSize(
- R.dimen.bubblebar_expanded_view_menu_size);
- mMenuView = new HandleView(context);
- addView(mMenuView);
-
+ mCaptionHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_height);
+ addView(mHandleView);
applyThemeAttrs();
setClipToOutline(true);
setOutlineProvider(new ViewOutlineProvider() {
@@ -98,23 +122,76 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
});
}
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ // Hide manage menu when view disappears
+ mMenuViewController.hideMenu(false /* animated */);
+ }
+
/** Set the BubbleController on the view, must be called before doing anything else. */
- public void initialize(BubbleController controller) {
+ public void initialize(BubbleController controller, boolean isOverflow) {
mController = controller;
- mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
- /* listener= */ this,
- /* viewParent= */ this);
- mTaskView = mBubbleTaskViewHelper.getTaskView();
- addView(mTaskView);
- mTaskView.setEnableSurfaceClipping(true);
- mTaskView.setCornerRadius(mCornerRadius);
+ mIsOverflow = isOverflow;
+
+ if (mIsOverflow) {
+ mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
+ R.layout.bubble_overflow_container, null /* root */);
+ mOverflowView.setBubbleController(mController);
+ addView(mOverflowView);
+ } else {
+
+ mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
+ /* listener= */ this,
+ /* viewParent= */ this);
+ mTaskView = mBubbleTaskViewHelper.getTaskView();
+ addView(mTaskView);
+ mTaskView.setEnableSurfaceClipping(true);
+ mTaskView.setCornerRadius(mCornerRadius);
+
+ // Handle view needs to draw on top of task view.
+ bringChildToFront(mHandleView);
+ }
+ mMenuViewController = new BubbleBarMenuViewController(mContext, this);
+ mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
+ @Override
+ public void onMenuVisibilityChanged(boolean visible) {
+ setObscured(visible);
+ }
+
+ @Override
+ public void onUnBubbleConversation(Bubble bubble) {
+ if (mListener != null) {
+ mListener.onUnBubbleConversation(bubble.getKey());
+ }
+ }
+
+ @Override
+ public void onOpenAppSettings(Bubble bubble) {
+ mController.collapseStack();
+ mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser());
+ }
+
+ @Override
+ public void onDismissBubble(Bubble bubble) {
+ mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
+ }
+ });
+ mHandleView.setOnClickListener(view -> {
+ mMenuViewController.showMenu(true /* animated */);
+ });
+ }
+
+ public BubbleBarHandleView getHandleView() {
+ return mHandleView;
}
// TODO (b/275087636): call this when theme/config changes
- void applyThemeAttrs() {
+ /** Updates the view based on the current theme. */
+ public void applyThemeAttrs() {
boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
mContext.getResources());
- final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+ final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
android.R.attr.dialogCornerRadius,
android.R.attr.colorBackgroundFloating});
mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
@@ -123,14 +200,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
ta.recycle();
- mMenuView.setCornerRadius(mCornerRadius);
- mMenuHeight = getResources().getDimensionPixelSize(
- R.dimen.bubblebar_expanded_view_menu_size);
+ mCaptionHeight = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_height);
if (mTaskView != null) {
mTaskView.setCornerRadius(mCornerRadius);
- mTaskView.setElevation(150);
- updateMenuColor();
+ updateHandleColor(true /* animated */);
}
}
@@ -138,35 +213,36 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
-
- // Add corner radius here so that the menu extends behind the rounded corners of TaskView.
- int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height);
- measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
+ int menuViewHeight = Math.min(mCaptionHeight, height);
+ measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
MeasureSpec.getMode(heightMeasureSpec)));
if (mTaskView != null) {
- int taskViewHeight = height - menuViewHeight;
- measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
+ measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height,
MeasureSpec.getMode(heightMeasureSpec)));
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- // Drag handle above
- final int dragHandleBottom = t + mMenuView.getMeasuredHeight();
- mMenuView.layout(l, t, r, dragHandleBottom);
+ super.onLayout(changed, l, t, r, b);
+ final int captionBottom = t + mCaptionHeight;
if (mTaskView != null) {
- // Subtract radius so that the menu extends behind the rounded corners of TaskView.
- mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r,
- dragHandleBottom + mTaskView.getMeasuredHeight());
+ mTaskView.layout(l, t, r,
+ t + mTaskView.getMeasuredHeight());
+ mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
}
+ // Handle draws on top of task view in the caption area.
+ mHandleView.layout(l, t, r, captionBottom);
}
@Override
public void onTaskCreated() {
setContentVisibility(true);
- updateMenuColor();
+ updateHandleColor(false /* animated */);
+ if (mListener != null) {
+ mListener.onTaskCreated();
+ }
}
@Override
@@ -176,7 +252,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
public void onBackPressed() {
- mController.collapseStack();
+ if (mListener == null) return;
+ mListener.onBackPressed();
}
/** Cleans up task view, should be called when the bubble is no longer active. */
@@ -187,11 +264,25 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
}
mBubbleTaskViewHelper.cleanUpTaskView();
}
+ mMenuViewController.hideMenu(false /* animated */);
+ }
+
+ /**
+ * Hides the current modal menu view or collapses the bubble stack.
+ * Called from {@link BubbleBarLayerView}
+ */
+ public void hideMenuOrCollapse() {
+ if (mMenuViewController.isMenuVisible()) {
+ mMenuViewController.hideMenu(/* animated = */ true);
+ } else {
+ mController.collapseStack();
+ }
}
- /** Updates the bubble shown in this task view. */
+ /** Updates the bubble shown in the expanded view. */
public void update(Bubble bubble) {
mBubbleTaskViewHelper.update(bubble);
+ mMenuViewController.updateMenu(bubble);
}
/** The task id of the activity shown in the task view, if it exists. */
@@ -199,12 +290,39 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID;
}
+ /** Sets layer bounds supplier used for obscured touchable region of task view */
+ void setLayerBoundsSupplier(@Nullable Supplier<Rect> supplier) {
+ mLayerBoundsSupplier = supplier;
+ }
+
+ /** Sets expanded view listener */
+ void setListener(@Nullable Listener listener) {
+ mListener = listener;
+ }
+
+ /** Sets whether the view is obscured by some modal view */
+ void setObscured(boolean obscured) {
+ if (mTaskView == null || mLayerBoundsSupplier == null) return;
+ // Updates the obscured touchable region for the task surface.
+ mTaskView.setObscuredTouchRect(obscured ? mLayerBoundsSupplier.get() : null);
+ }
+
/**
* Call when the location or size of the view has changed to update TaskView.
*/
public void updateLocation() {
- if (mTaskView == null) return;
- mTaskView.onLocationChanged();
+ if (mTaskView != null) {
+ mTaskView.onLocationChanged();
+ }
+ }
+
+ /** Shows the expanded view for the overflow if it exists. */
+ void maybeShowOverflow() {
+ if (mOverflowView != null) {
+ // post this to the looper so that the view has a chance to be laid out before it can
+ // calculate row and column sizes correctly.
+ post(() -> mOverflowView.show());
+ }
}
/** Sets the alpha of the task view. */
@@ -218,17 +336,21 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
}
}
- /** Updates the menu bar to be the status bar color specified by the app. */
- private void updateMenuColor() {
- if (mTaskView == null) return;
- ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo();
- final int taskBgColor = info.taskDescription.getStatusBarColor();
- final int color = Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
- if (color != -1) {
- mMenuView.setBackgroundColor(color);
- } else {
- mMenuView.setBackgroundColor(mBackgroundColor);
+ /**
+ * Updates the handle color based on the task view status bar or background color; if those
+ * are transparent it defaults to the background color pulled from system theme attributes.
+ */
+ private void updateHandleColor(boolean animated) {
+ if (mTaskView == null || mTaskView.getTaskInfo() == null) return;
+ int color = mBackgroundColor;
+ ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription;
+ if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) {
+ color = taskDescription.getStatusBarColor();
+ } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) {
+ color = taskDescription.getBackgroundColor();
}
+ final boolean isRegionDark = Color.luminance(color) <= 0.5;
+ mHandleView.updateHandleColor(isRegionDark, animated);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
new file mode 100644
index 000000000000..2b7a0706b4de
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Outline;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import androidx.annotation.ColorInt;
+import androidx.core.content.ContextCompat;
+
+import com.android.wm.shell.R;
+
+/**
+ * Handle view to show at the top of a bubble bar expanded view.
+ */
+public class BubbleBarHandleView extends View {
+ private static final long COLOR_CHANGE_DURATION = 120;
+
+ // The handle view is currently rendered as 3 evenly spaced dots.
+ private int mDotSize;
+ private int mDotSpacing;
+ // Path used to draw the dots
+ private final Path mPath = new Path();
+
+ private @ColorInt int mHandleLightColor;
+ private @ColorInt int mHandleDarkColor;
+ private @Nullable ObjectAnimator mColorChangeAnim;
+
+ public BubbleBarHandleView(Context context) {
+ this(context, null /* attrs */);
+ }
+
+ public BubbleBarHandleView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0 /* defStyleAttr */);
+ }
+
+ public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+ }
+
+ public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mDotSize = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_dot_size);
+ mDotSpacing = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_dot_spacing);
+ mHandleLightColor = ContextCompat.getColor(getContext(),
+ R.color.bubble_bar_expanded_view_handle_light);
+ mHandleDarkColor = ContextCompat.getColor(getContext(),
+ R.color.bubble_bar_expanded_view_handle_dark);
+
+ setClipToOutline(true);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ final int handleCenterX = view.getWidth() / 2;
+ final int handleCenterY = view.getHeight() / 2;
+ final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2;
+ final int handleLeft = handleCenterX - handleTotalWidth / 2;
+ final int handleTop = handleCenterY - mDotSize / 2;
+ final int handleBottom = handleTop + mDotSize;
+ RectF dot1 = new RectF(
+ handleLeft, handleTop,
+ handleLeft + mDotSize, handleBottom);
+ RectF dot2 = new RectF(
+ dot1.right + mDotSpacing, handleTop,
+ dot1.right + mDotSpacing + mDotSize, handleBottom
+ );
+ RectF dot3 = new RectF(
+ dot2.right + mDotSpacing, handleTop,
+ dot2.right + mDotSpacing + mDotSize, handleBottom
+ );
+ mPath.reset();
+ mPath.addOval(dot1, Path.Direction.CW);
+ mPath.addOval(dot2, Path.Direction.CW);
+ mPath.addOval(dot3, Path.Direction.CW);
+ outline.setPath(mPath);
+ }
+ });
+ }
+
+ /**
+ * Updates the handle color.
+ *
+ * @param isRegionDark Whether the background behind the handle is dark, and thus the handle
+ * should be light (and vice versa).
+ * @param animated Whether to animate the change, or apply it immediately.
+ */
+ public void updateHandleColor(boolean isRegionDark, boolean animated) {
+ int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor;
+ if (mColorChangeAnim != null) {
+ mColorChangeAnim.cancel();
+ }
+ if (animated) {
+ mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor);
+ mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mColorChangeAnim = null;
+ }
+ });
+ mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION);
+ mColorChangeAnim.start();
+ } else {
+ setBackgroundColor(newColor);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index b1a725b6e5c4..8f11253290ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -24,14 +24,18 @@ import android.content.Context;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
+import java.util.function.Consumer;
+
/**
* Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window
* manager to display bubbles. However, it is only used when bubbles are being displayed in
@@ -48,11 +52,13 @@ public class BubbleBarLayerView extends FrameLayout
private final BubbleController mBubbleController;
private final BubblePositioner mPositioner;
private final BubbleBarAnimationHelper mAnimationHelper;
+ private final BubbleEducationViewController mEducationViewController;
private final View mScrimView;
@Nullable
private BubbleViewProvider mExpandedBubble;
private BubbleBarExpandedView mExpandedView;
+ private @Nullable Consumer<String> mUnBubbleConversationCallback;
// TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
/** Whether the expanded view is displaying on the left of the screen or not. */
@@ -64,6 +70,10 @@ public class BubbleBarLayerView extends FrameLayout
private final Region mTouchableRegion = new Region();
private final Rect mTempRect = new Rect();
+ // Used to ensure touch target size for the menu shown on a bubble expanded view
+ private TouchDelegate mHandleTouchDelegate;
+ private final Rect mHandleTouchBounds = new Rect();
+
public BubbleBarLayerView(Context context, BubbleController controller) {
super(context);
mBubbleController = controller;
@@ -71,6 +81,10 @@ public class BubbleBarLayerView extends FrameLayout
mAnimationHelper = new BubbleBarAnimationHelper(context,
this, mPositioner);
+ mEducationViewController = new BubbleEducationViewController(context, (boolean visible) -> {
+ if (mExpandedView == null) return;
+ mExpandedView.setObscured(visible);
+ });
mScrimView = new View(getContext());
mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -81,9 +95,7 @@ public class BubbleBarLayerView extends FrameLayout
mScrimView.setBackgroundDrawable(new ColorDrawable(
getResources().getColor(android.R.color.system_neutral1_1000)));
- setOnClickListener(view -> {
- mBubbleController.collapseStack();
- });
+ setOnClickListener(view -> hideMenuOrCollapse());
}
@Override
@@ -99,6 +111,7 @@ public class BubbleBarLayerView extends FrameLayout
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
if (mExpandedView != null) {
+ mEducationViewController.hideManageEducation(/* animated = */ false);
removeView(mExpandedView);
mExpandedView = null;
}
@@ -141,17 +154,55 @@ public class BubbleBarLayerView extends FrameLayout
mExpandedView = null;
}
if (mExpandedView == null) {
+ if (expandedView.getParent() != null) {
+ // Expanded view might be animating collapse and is still attached
+ // Cancel current animations and remove from parent
+ mAnimationHelper.cancelAnimations();
+ removeView(expandedView);
+ }
mExpandedBubble = b;
mExpandedView = expandedView;
- final int width = mPositioner.getExpandedViewWidthForBubbleBar();
- final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+ boolean isOverflowExpanded = b.getKey().equals(BubbleOverflow.KEY);
+ final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+ final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
mExpandedView.setVisibility(GONE);
+ mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
+ mExpandedView.setLayerBoundsSupplier(() -> new Rect(0, 0, getWidth(), getHeight()));
+ mExpandedView.setListener(new BubbleBarExpandedView.Listener() {
+ @Override
+ public void onTaskCreated() {
+ mEducationViewController.maybeShowManageEducation(b, mExpandedView);
+ }
+
+ @Override
+ public void onUnBubbleConversation(String bubbleKey) {
+ if (mUnBubbleConversationCallback != null) {
+ mUnBubbleConversationCallback.accept(bubbleKey);
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ hideMenuOrCollapse();
+ }
+ });
+
addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
}
mIsExpanded = true;
mBubbleController.getSysuiProxy().onStackExpandChanged(true);
- mAnimationHelper.animateExpansion(mExpandedBubble);
+ mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
+ if (mExpandedView == null) return;
+ // Touch delegate for the menu
+ BubbleBarHandleView view = mExpandedView.getHandleView();
+ view.getBoundsOnScreen(mHandleTouchBounds);
+ mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop();
+ mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
+ mExpandedView.getHandleView());
+ setTouchDelegate(mHandleTouchDelegate);
+ });
+
showScrim(true);
}
@@ -159,18 +210,38 @@ public class BubbleBarLayerView extends FrameLayout
public void collapse() {
mIsExpanded = false;
final BubbleBarExpandedView viewToRemove = mExpandedView;
+ mEducationViewController.hideManageEducation(/* animated = */ true);
mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
+ setTouchDelegate(null);
showScrim(false);
}
+ /** Sets the function to call to un-bubble the given conversation. */
+ public void setUnBubbleConversationCallback(
+ @Nullable Consumer<String> unBubbleConversationCallback) {
+ mUnBubbleConversationCallback = unBubbleConversationCallback;
+ }
+
+ /** Hides the current modal education/menu view, expanded view or collapses the bubble stack */
+ private void hideMenuOrCollapse() {
+ if (mEducationViewController.isManageEducationVisible()) {
+ mEducationViewController.hideManageEducation(/* animated = */ true);
+ } else if (isExpanded() && mExpandedView != null) {
+ mExpandedView.hideMenuOrCollapse();
+ } else {
+ mBubbleController.collapseStack();
+ }
+ }
+
/** Updates the expanded view size and position. */
private void updateExpandedView() {
if (mExpandedView == null) return;
+ boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
final int padding = mPositioner.getBubbleBarExpandedViewPadding();
- final int width = mPositioner.getExpandedViewWidthForBubbleBar();
- final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+ final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+ final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
FrameLayout.LayoutParams lp = (LayoutParams) mExpandedView.getLayoutParams();
lp.width = width;
lp.height = height;
@@ -180,7 +251,7 @@ public class BubbleBarLayerView extends FrameLayout
} else {
mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
}
- mExpandedView.setY(mPositioner.getInsets().top + padding);
+ mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
mExpandedView.updateLocation();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
new file mode 100644
index 000000000000..00b977721bea
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Bubble bar expanded view menu item view to display menu action details
+ */
+public class BubbleBarMenuItemView extends LinearLayout {
+ private ImageView mImageView;
+ private TextView mTextView;
+
+ public BubbleBarMenuItemView(Context context) {
+ this(context, null /* attrs */);
+ }
+
+ public BubbleBarMenuItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0 /* defStyleAttr */);
+ }
+
+ public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+ }
+
+ public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mImageView = findViewById(R.id.bubble_bar_menu_item_icon);
+ mTextView = findViewById(R.id.bubble_bar_menu_item_title);
+ }
+
+ /**
+ * Update menu item with the details and tint color
+ */
+ void update(Icon icon, String title, @ColorInt int tint) {
+ if (tint == Color.TRANSPARENT) {
+ final TypedArray typedArray = getContext().obtainStyledAttributes(
+ new int[]{android.R.attr.textColorPrimary});
+ mTextView.setTextColor(typedArray.getColor(0, Color.BLACK));
+ } else {
+ icon.setTint(tint);
+ mTextView.setTextColor(tint);
+ }
+
+ mImageView.setImageIcon(icon);
+ mTextView.setText(title);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
new file mode 100644
index 000000000000..211fe0d48e43
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
+
+import java.util.ArrayList;
+
+/**
+ * Bubble bar expanded view menu
+ */
+public class BubbleBarMenuView extends LinearLayout {
+ private ViewGroup mBubbleSectionView;
+ private ViewGroup mActionsSectionView;
+ private ImageView mBubbleIconView;
+ private TextView mBubbleTitleView;
+
+ public BubbleBarMenuView(Context context) {
+ this(context, null /* attrs */);
+ }
+
+ public BubbleBarMenuView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0 /* defStyleAttr */);
+ }
+
+ public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+ }
+
+ public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mBubbleSectionView = findViewById(R.id.bubble_bar_manage_menu_bubble_section);
+ mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
+ mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
+ mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
+ }
+
+ /** Update menu details with bubble info */
+ void updateInfo(Bubble bubble) {
+ if (bubble.getIcon() != null) {
+ mBubbleIconView.setImageIcon(bubble.getIcon());
+ } else {
+ mBubbleIconView.setImageBitmap(bubble.getBubbleIcon());
+ }
+ mBubbleTitleView.setText(bubble.getTitle());
+ }
+
+ /**
+ * Update menu action items views
+ * @param actions used to populate menu item views
+ */
+ void updateActions(ArrayList<MenuAction> actions) {
+ mActionsSectionView.removeAllViews();
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+
+ for (MenuAction action : actions) {
+ BubbleBarMenuItemView itemView = (BubbleBarMenuItemView) inflater.inflate(
+ R.layout.bubble_bar_menu_item, mActionsSectionView, false);
+ itemView.update(action.mIcon, action.mTitle, action.mTint);
+ itemView.setOnClickListener(action.mOnClick);
+ mActionsSectionView.addView(itemView);
+ }
+ }
+
+ /** Sets on close menu listener */
+ void setOnCloseListener(Runnable onClose) {
+ mBubbleSectionView.setOnClickListener(view -> {
+ onClose.run();
+ });
+ }
+
+ /**
+ * Overridden to proxy to section views alpha.
+ * @implNote
+ * If animate alpha on the parent (menu container) view, section view shadows get distorted.
+ * To prevent distortion and artifacts alpha changes applied directly on the section views.
+ */
+ @Override
+ public void setAlpha(float alpha) {
+ mBubbleSectionView.setAlpha(alpha);
+ mActionsSectionView.setAlpha(alpha);
+ }
+
+ /**
+ * Overridden to proxy section view alpha value.
+ * @implNote
+ * The assumption is that both section views have the same alpha value
+ */
+ @Override
+ public float getAlpha() {
+ return mBubbleSectionView.getAlpha();
+ }
+
+ /**
+ * Menu action details used to create menu items
+ */
+ static class MenuAction {
+ private Icon mIcon;
+ private @ColorInt int mTint;
+ private String mTitle;
+ private OnClickListener mOnClick;
+
+ MenuAction(Icon icon, String title, OnClickListener onClick) {
+ this(icon, title, Color.TRANSPARENT, onClick);
+ }
+
+ MenuAction(Icon icon, String title, @ColorInt int tint, OnClickListener onClick) {
+ this.mIcon = icon;
+ this.mTitle = title;
+ this.mTint = tint;
+ this.mOnClick = onClick;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
new file mode 100644
index 000000000000..81e7582e0dba
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.content.ContextCompat;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.Bubble;
+
+import java.util.ArrayList;
+
+/**
+ * Manages bubble bar expanded view menu presentation and animations
+ */
+class BubbleBarMenuViewController {
+ private static final float MENU_INITIAL_SCALE = 0.5f;
+ private final Context mContext;
+ private final ViewGroup mRootView;
+ private @Nullable Listener mListener;
+ private @Nullable Bubble mBubble;
+ private @Nullable BubbleBarMenuView mMenuView;
+ /** A transparent view used to intercept touches to collapse menu when presented */
+ private @Nullable View mScrimView;
+ private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator;
+ private PhysicsAnimator.SpringConfig mMenuSpringConfig;
+
+ BubbleBarMenuViewController(Context context, ViewGroup rootView) {
+ mContext = context;
+ mRootView = rootView;
+ mMenuSpringConfig = new PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+ }
+
+ /** Tells if the menu is visible or being animated */
+ boolean isMenuVisible() {
+ return mMenuView != null && mMenuView.getVisibility() == View.VISIBLE;
+ }
+
+ /** Sets menu actions listener */
+ void setListener(@Nullable Listener listener) {
+ mListener = listener;
+ }
+
+ /** Update menu with bubble */
+ void updateMenu(@NonNull Bubble bubble) {
+ mBubble = bubble;
+ }
+
+ /**
+ * Show bubble bar expanded view menu
+ * @param animated if should animate transition
+ */
+ void showMenu(boolean animated) {
+ if (mMenuView == null || mScrimView == null) {
+ setupMenu();
+ }
+ cancelAnimations();
+ mMenuView.setVisibility(View.VISIBLE);
+ mScrimView.setVisibility(View.VISIBLE);
+ Runnable endActions = () -> {
+ mMenuView.getChildAt(0).requestAccessibilityFocus();
+ if (mListener != null) {
+ mListener.onMenuVisibilityChanged(true /* isShown */);
+ }
+ };
+ if (animated) {
+ animateTransition(true /* show */, endActions);
+ } else {
+ endActions.run();
+ }
+ }
+
+ /**
+ * Hide bubble bar expanded view menu
+ * @param animated if should animate transition
+ */
+ void hideMenu(boolean animated) {
+ if (mMenuView == null || mScrimView == null) return;
+ cancelAnimations();
+ Runnable endActions = () -> {
+ mMenuView.setVisibility(View.GONE);
+ mScrimView.setVisibility(View.GONE);
+ if (mListener != null) {
+ mListener.onMenuVisibilityChanged(false /* isShown */);
+ }
+ };
+ if (animated) {
+ animateTransition(false /* show */, endActions);
+ } else {
+ endActions.run();
+ }
+ }
+
+ /**
+ * Animate show/hide menu transition
+ * @param show if should show or hide the menu
+ * @param endActions will be called when animation ends
+ */
+ private void animateTransition(boolean show, Runnable endActions) {
+ if (mMenuView == null) return;
+ mMenuAnimator = PhysicsAnimator.getInstance(mMenuView);
+ mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig);
+ mMenuAnimator
+ .spring(DynamicAnimation.ALPHA, show ? 1f : 0f)
+ .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE)
+ .withEndActions(() -> {
+ mMenuAnimator = null;
+ endActions.run();
+ })
+ .start();
+ }
+
+ /** Cancel running animations */
+ private void cancelAnimations() {
+ if (mMenuAnimator != null) {
+ mMenuAnimator.cancel();
+ mMenuAnimator = null;
+ }
+ }
+
+ /** Sets up and inflate menu views */
+ private void setupMenu() {
+ // Menu view setup
+ mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate(
+ R.layout.bubble_bar_menu_view, mRootView, false);
+ mMenuView.setAlpha(0f);
+ mMenuView.setPivotY(0f);
+ mMenuView.setScaleY(MENU_INITIAL_SCALE);
+ mMenuView.setOnCloseListener(() -> hideMenu(true /* animated */));
+ if (mBubble != null) {
+ mMenuView.updateInfo(mBubble);
+ mMenuView.updateActions(createMenuActions(mBubble));
+ }
+ // Scrim view setup
+ mScrimView = new View(mContext);
+ mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mScrimView.setOnClickListener(view -> hideMenu(true /* animated */));
+ // Attach to root view
+ mRootView.addView(mScrimView);
+ mRootView.addView(mMenuView);
+ }
+
+ /**
+ * Creates menu actions to populate menu view
+ * @param bubble used to create actions depending on bubble type
+ */
+ private ArrayList<BubbleBarMenuView.MenuAction> createMenuActions(Bubble bubble) {
+ ArrayList<BubbleBarMenuView.MenuAction> menuActions = new ArrayList<>();
+ Resources resources = mContext.getResources();
+
+ if (bubble.isConversation()) {
+ // Don't bubble conversation action
+ menuActions.add(new BubbleBarMenuView.MenuAction(
+ Icon.createWithResource(mContext, R.drawable.bubble_ic_stop_bubble),
+ resources.getString(R.string.bubbles_dont_bubble_conversation),
+ view -> {
+ hideMenu(true /* animated */);
+ if (mListener != null) {
+ mListener.onUnBubbleConversation(bubble);
+ }
+ }
+ ));
+ // Open settings action
+ Icon appIcon = bubble.getRawAppBadge() != null ? Icon.createWithBitmap(
+ bubble.getRawAppBadge()) : null;
+ menuActions.add(new BubbleBarMenuView.MenuAction(
+ appIcon,
+ resources.getString(R.string.bubbles_app_settings, bubble.getAppName()),
+ view -> {
+ hideMenu(true /* animated */);
+ if (mListener != null) {
+ mListener.onOpenAppSettings(bubble);
+ }
+ }
+ ));
+ }
+
+ // Dismiss bubble action
+ menuActions.add(new BubbleBarMenuView.MenuAction(
+ Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow),
+ resources.getString(R.string.bubble_dismiss_text),
+ ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_menu_close),
+ view -> {
+ hideMenu(true /* animated */);
+ if (mListener != null) {
+ mListener.onDismissBubble(bubble);
+ }
+ }
+ ));
+
+ return menuActions;
+ }
+
+ /**
+ * Bubble bar expanded view menu actions listener
+ */
+ interface Listener {
+ /**
+ * Called when manage menu is shown/hidden
+ * If animated will be called when animation ends
+ */
+ void onMenuVisibilityChanged(boolean visible);
+
+ /**
+ * Un-bubbles conversation and removes the bubble from the stack
+ * This conversation will not be bubbled with new messages
+ * @see com.android.wm.shell.bubbles.BubbleController
+ */
+ void onUnBubbleConversation(Bubble bubble);
+
+ /**
+ * Launches app notification bubble settings for the bubble with intent created in:
+ * {@code Bubble.getSettingsIntent}
+ */
+ void onOpenAppSettings(Bubble bubble);
+
+ /**
+ * Dismiss bubble and remove it from the bubble stack
+ */
+ void onDismissBubble(Bubble bubble);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
new file mode 100644
index 000000000000..7b39c6fd4059
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.view.doOnLayout
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.bubbles.BubbleEducationController
+import com.android.wm.shell.bubbles.BubbleViewProvider
+import com.android.wm.shell.bubbles.setup
+import com.android.wm.shell.common.bubbles.BubblePopupView
+
+/** Manages bubble education presentation and animation */
+class BubbleEducationViewController(private val context: Context, private val listener: Listener) {
+ interface Listener {
+ fun onManageEducationVisibilityChanged(isVisible: Boolean)
+ }
+
+ private var rootView: ViewGroup? = null
+ private var educationView: BubblePopupView? = null
+ private var animator: PhysicsAnimator<BubblePopupView>? = null
+
+ private val springConfig by lazy {
+ PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM,
+ SpringForce.DAMPING_RATIO_LOW_BOUNCY
+ )
+ }
+
+ private val controller by lazy { BubbleEducationController(context) }
+
+ /** Whether the education view is visible or being animated */
+ val isManageEducationVisible: Boolean
+ get() = educationView != null && rootView != null
+
+ /**
+ * Show manage bubble education if hasn't been shown before
+ *
+ * @param bubble the bubble used for the manage education check
+ * @param root the view to show manage education in
+ */
+ fun maybeShowManageEducation(bubble: BubbleViewProvider, root: ViewGroup) {
+ if (!controller.shouldShowManageEducation(bubble)) return
+ showManageEducation(root)
+ }
+
+ /**
+ * Hide the manage education view if visible
+ *
+ * @param animated whether should hide with animation
+ */
+ fun hideManageEducation(animated: Boolean) {
+ rootView?.let {
+ fun cleanUp() {
+ it.removeView(educationView)
+ rootView = null
+ listener.onManageEducationVisibilityChanged(isVisible = false)
+ }
+
+ if (animated) {
+ animateTransition(show = false, ::cleanUp)
+ } else {
+ cleanUp()
+ }
+ }
+ }
+
+ /**
+ * Show manage education with animation
+ *
+ * @param root the view to show manage education in
+ */
+ private fun showManageEducation(root: ViewGroup) {
+ hideManageEducation(animated = false)
+ if (educationView == null) {
+ val eduView = createEducationView(root)
+ educationView = eduView
+ animator = createAnimation(eduView)
+ }
+ root.addView(educationView)
+ rootView = root
+ animateTransition(show = true) {
+ controller.hasSeenManageEducation = true
+ listener.onManageEducationVisibilityChanged(isVisible = true)
+ }
+ }
+
+ /**
+ * Animate show/hide transition for the education view
+ *
+ * @param show whether to show or hide the view
+ * @param endActions a closure to be called when the animation completes
+ */
+ private fun animateTransition(show: Boolean, endActions: () -> Unit) {
+ animator?.let { animator ->
+ animator
+ .spring(DynamicAnimation.ALPHA, if (show) 1f else 0f)
+ .spring(DynamicAnimation.SCALE_X, if (show) 1f else EDU_SCALE_HIDDEN)
+ .spring(DynamicAnimation.SCALE_Y, if (show) 1f else EDU_SCALE_HIDDEN)
+ .withEndActions(endActions)
+ .start()
+ } ?: endActions()
+ }
+
+ private fun createEducationView(root: ViewGroup): BubblePopupView {
+ val view =
+ LayoutInflater.from(context).inflate(R.layout.bubble_bar_manage_education, root, false)
+ as BubblePopupView
+
+ return view.apply {
+ setup()
+ alpha = 0f
+ pivotY = 0f
+ scaleX = EDU_SCALE_HIDDEN
+ scaleY = EDU_SCALE_HIDDEN
+ doOnLayout { it.pivotX = it.width / 2f }
+ setOnClickListener { hideManageEducation(animated = true) }
+ }
+ }
+
+ private fun createAnimation(view: BubblePopupView): PhysicsAnimator<BubblePopupView> {
+ val animator = PhysicsAnimator.getInstance(view)
+ animator.setDefaultSpringConfig(springConfig)
+ return animator
+ }
+
+ companion object {
+ private const val EDU_SCALE_HIDDEN = 0.5f
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java
deleted file mode 100644
index 9ee8a9d98aa1..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.bubbles.bar;
-
-import android.content.Context;
-import android.view.Gravity;
-import android.widget.LinearLayout;
-
-/**
- * Handle / menu view to show at the top of a bubble bar expanded view.
- */
-public class HandleView extends LinearLayout {
-
- // TODO(b/273307221): implement the manage menu in this view.
- public HandleView(Context context) {
- super(context);
- setOrientation(LinearLayout.HORIZONTAL);
- setGravity(Gravity.CENTER);
- }
-
- /**
- * The menu extends past the top of the TaskView because of the rounded corners. This means
- * to center content in the menu we must subtract the radius (i.e. the amount of space covered
- * by TaskView).
- */
- public void setCornerRadius(float radius) {
- setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
new file mode 100644
index 000000000000..85aaa8ef585c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles.properties
+
+/**
+ * An interface for exposing bubble properties via flags which can be controlled easily in tests.
+ */
+interface BubbleProperties {
+ /**
+ * Whether bubble bar is enabled.
+ *
+ * When this is `true`, depending on additional factors, such as screen size and taskbar state,
+ * bubbles will be displayed in the bubble bar instead of floating.
+ *
+ * When this is `false`, bubbles will be floating.
+ */
+ val isBubbleBarEnabled: Boolean
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
new file mode 100644
index 000000000000..9d8b9a6f3260
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles.properties
+
+import android.os.SystemProperties
+
+/** Provides bubble properties in production. */
+object ProdBubbleProperties : BubbleProperties {
+
+ // TODO(b/256873975) Should use proper flag when available to shell/launcher
+ override val isBubbleBarEnabled =
+ SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
new file mode 100644
index 000000000000..81592c35e4ac
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * Controller to manage behavior of activities launched with
+ * [android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT].
+ */
+class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
+
+ /** Allows to temporarily disable launch adjacent handling */
+ var launchAdjacentEnabled: Boolean = true
+ set(value) {
+ if (field != value) {
+ KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value)
+ field = value
+ container?.let { c ->
+ if (value) {
+ enableContainer(c)
+ } else {
+ disableContainer((c))
+ }
+ }
+ }
+ }
+ private var container: WindowContainerToken? = null
+
+ /**
+ * Set [container] as the new launch adjacent flag root container.
+ *
+ * If launch adjacent handling is disabled through [setLaunchAdjacentEnabled], won't set the
+ * container until after it is enabled again.
+ *
+ * @see WindowContainerTransaction.setLaunchAdjacentFlagRoot
+ */
+ fun setLaunchAdjacentRoot(container: WindowContainerToken) {
+ KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container")
+ this.container = container
+ if (launchAdjacentEnabled) {
+ enableContainer(container)
+ }
+ }
+
+ /**
+ * Clear a container previously set through [setLaunchAdjacentRoot].
+ *
+ * Always clears the container, regardless of [launchAdjacentEnabled] value.
+ *
+ * @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot
+ */
+ fun clearLaunchAdjacentRoot() {
+ KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container")
+ container?.let {
+ disableContainer(it)
+ container = null
+ }
+ }
+
+ private fun enableContainer(container: WindowContainerToken) {
+ KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container")
+ val wct = WindowContainerTransaction()
+ wct.setLaunchAdjacentFlagRoot(container)
+ syncQueue.queue(wct)
+ }
+
+ private fun disableContainer(container: WindowContainerToken) {
+ KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container")
+ val wct = WindowContainerTransaction()
+ wct.clearLaunchAdjacentFlagRoot(container)
+ syncQueue.queue(wct)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
new file mode 100644
index 000000000000..7af038999797
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
@@ -0,0 +1 @@
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
index 21355a3efa2e..24608d651d06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
@@ -129,6 +129,11 @@ public class BubbleInfo implements Parcelable {
return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION) != 0;
}
+ /** Sets the flags for this bubble. */
+ public void setFlags(int flags) {
+ mFlags = flags;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
new file mode 100644
index 000000000000..1fd22d0a3505
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.bubbles
+
+import android.annotation.ColorInt
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Matrix
+import android.graphics.Outline
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.Drawable
+import kotlin.math.atan
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.properties.Delegates
+
+/** A drawable for the [BubblePopupView] that draws a popup background with a directional arrow */
+class BubblePopupDrawable(private val config: Config) : Drawable() {
+ /** The direction of the arrow in the popup drawable */
+ enum class ArrowDirection {
+ UP,
+ DOWN
+ }
+
+ /** The arrow position on the side of the popup bubble */
+ sealed class ArrowPosition {
+ object Start : ArrowPosition()
+ object Center : ArrowPosition()
+ object End : ArrowPosition()
+ class Custom(val value: Float) : ArrowPosition()
+ }
+
+ /** The configuration for drawable features */
+ data class Config(
+ @ColorInt val color: Int,
+ val cornerRadius: Float,
+ val contentPadding: Int,
+ val arrowWidth: Float,
+ val arrowHeight: Float,
+ val arrowRadius: Float
+ )
+
+ /**
+ * The direction of the arrow in the popup drawable. It affects the content padding and requires
+ * it to be updated in the view.
+ */
+ var arrowDirection: ArrowDirection by
+ Delegates.observable(ArrowDirection.UP) { _, _, _ -> requestPathUpdate() }
+
+ /**
+ * Arrow position along the X axis and its direction. The position is adjusted to the content
+ * corner radius when applied so it doesn't go into rounded corner area
+ */
+ var arrowPosition: ArrowPosition by
+ Delegates.observable(ArrowPosition.Center) { _, _, _ -> requestPathUpdate() }
+
+ private val path = Path()
+ private val paint = Paint()
+ private var shouldUpdatePath = true
+
+ init {
+ paint.color = config.color
+ paint.style = Paint.Style.FILL
+ paint.isAntiAlias = true
+ }
+
+ override fun draw(canvas: Canvas) {
+ updatePathIfNeeded()
+ canvas.drawPath(path, paint)
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ requestPathUpdate()
+ }
+
+ /** Should be applied to the view padding if arrow direction changes */
+ override fun getPadding(padding: Rect): Boolean {
+ padding.set(
+ config.contentPadding,
+ config.contentPadding,
+ config.contentPadding,
+ config.contentPadding
+ )
+ when (arrowDirection) {
+ ArrowDirection.UP -> padding.top += config.arrowHeight.toInt()
+ ArrowDirection.DOWN -> padding.bottom += config.arrowHeight.toInt()
+ }
+ return true
+ }
+
+ override fun getOutline(outline: Outline) {
+ updatePathIfNeeded()
+ outline.setPath(path)
+ }
+
+ override fun getOpacity(): Int {
+ return paint.alpha
+ }
+
+ override fun setAlpha(alpha: Int) {
+ paint.alpha = alpha
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ paint.colorFilter = colorFilter
+ }
+
+ /** Schedules path update for the next redraw */
+ private fun requestPathUpdate() {
+ shouldUpdatePath = true
+ }
+
+ /** Updates the path if required, when bounds or arrow direction/position changes */
+ private fun updatePathIfNeeded() {
+ if (shouldUpdatePath) {
+ updatePath()
+ shouldUpdatePath = false
+ }
+ }
+
+ /** Updates the path value using the current bounds, config, arrow direction and position */
+ private fun updatePath() {
+ if (bounds.isEmpty) return
+ // Reset the path state
+ path.reset()
+ // The content rect where the filled rounded rect will be drawn
+ val contentRect = RectF(bounds)
+ when (arrowDirection) {
+ ArrowDirection.UP -> {
+ // Add rounded arrow pointing up to the path
+ addRoundedArrowPositioned(path, arrowPosition)
+ // Inset content rect by the arrow size from the top
+ contentRect.top += config.arrowHeight
+ }
+ ArrowDirection.DOWN -> {
+ val matrix = Matrix()
+ // Flip the path with the matrix to draw arrow pointing down
+ matrix.setScale(1f, -1f, bounds.width() / 2f, bounds.height() / 2f)
+ path.transform(matrix)
+ // Add rounded arrow with the flipped matrix applied, will point down
+ addRoundedArrowPositioned(path, arrowPosition)
+ // Restore the path matrix to the original state with inverted matrix
+ matrix.invert(matrix)
+ path.transform(matrix)
+ // Inset content rect by the arrow size from the bottom
+ contentRect.bottom -= config.arrowHeight
+ }
+ }
+ // Add the content area rounded rect
+ path.addRoundRect(contentRect, config.cornerRadius, config.cornerRadius, Path.Direction.CW)
+ }
+
+ /** Add a rounded arrow pointing up in the horizontal position on the canvas */
+ private fun addRoundedArrowPositioned(path: Path, position: ArrowPosition) {
+ val matrix = Matrix()
+ var translationX = positionValue(position) - config.arrowWidth / 2
+ // Offset to position between rounded corners of the content view
+ translationX = translationX.coerceIn(config.cornerRadius,
+ bounds.width() - config.cornerRadius - config.arrowWidth)
+ // Translate to add the arrow in the center horizontally
+ matrix.setTranslate(-translationX, 0f)
+ path.transform(matrix)
+ // Add rounded arrow
+ addRoundedArrow(path)
+ // Restore the path matrix to the original state with inverted matrix
+ matrix.invert(matrix)
+ path.transform(matrix)
+ }
+
+ /** Adds a rounded arrow pointing up to the path, can be flipped if needed */
+ private fun addRoundedArrow(path: Path) {
+ // Theta is half of the angle inside the triangle tip
+ val thetaTan = config.arrowWidth / (config.arrowHeight * 2f)
+ val theta = atan(thetaTan)
+ val thetaDeg = Math.toDegrees(theta.toDouble()).toFloat()
+ // The center Y value of the circle for the triangle tip
+ val tipCircleCenterY = config.arrowRadius / sin(theta)
+ // The length from triangle tip to intersection point with the circle
+ val tipIntersectionSideLength = config.arrowRadius / thetaTan
+ // The offset from the top to the point of intersection
+ val intersectionTopOffset = tipIntersectionSideLength * cos(theta)
+ // The offset from the center to the point of intersection
+ val intersectionCenterOffset = tipIntersectionSideLength * sin(theta)
+ // The center X of the triangle
+ val arrowCenterX = config.arrowWidth / 2f
+
+ // Set initial position in bottom left of the arrow
+ path.moveTo(0f, config.arrowHeight)
+ // Add the left side of the triangle
+ path.lineTo(arrowCenterX - intersectionCenterOffset, intersectionTopOffset)
+ // Add the arc from the left to the right side of the triangle
+ path.arcTo(
+ /* left = */ arrowCenterX - config.arrowRadius,
+ /* top = */ tipCircleCenterY - config.arrowRadius,
+ /* right = */ arrowCenterX + config.arrowRadius,
+ /* bottom = */ tipCircleCenterY + config.arrowRadius,
+ /* startAngle = */ 180 + thetaDeg,
+ /* sweepAngle = */ 180 - (2 * thetaDeg),
+ /* forceMoveTo = */ false
+ )
+ // Add the right side of the triangle
+ path.lineTo(config.arrowWidth, config.arrowHeight)
+ // Close the path
+ path.close()
+ }
+
+ /** The value of the arrow position provided the position and current bounds */
+ private fun positionValue(position: ArrowPosition): Float {
+ return when (position) {
+ is ArrowPosition.Start -> 0f
+ is ArrowPosition.Center -> bounds.width().toFloat() / 2f
+ is ArrowPosition.End -> bounds.width().toFloat()
+ is ArrowPosition.Custom -> position.value
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
new file mode 100644
index 000000000000..f8a4946bb5c5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.bubbles
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.widget.LinearLayout
+
+/** A popup container view that uses [BubblePopupDrawable] as a background */
+open class BubblePopupView
+@JvmOverloads
+constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
+ private var popupDrawable: BubblePopupDrawable? = null
+
+ /**
+ * Sets up the popup drawable with the config provided. Required to remove dependency on local
+ * resources
+ */
+ fun setupBackground(config: BubblePopupDrawable.Config) {
+ popupDrawable = BubblePopupDrawable(config)
+ background = popupDrawable
+ forceLayout()
+ }
+
+ /**
+ * Sets the arrow direction for the background drawable and updates the padding to fit the
+ * content inside of the popup drawable
+ */
+ fun setArrowDirection(direction: BubblePopupDrawable.ArrowDirection) {
+ popupDrawable?.let {
+ it.arrowDirection = direction
+ val padding = Rect()
+ if (it.getPadding(padding)) {
+ setPadding(padding.left, padding.top, padding.right, padding.bottom)
+ }
+ }
+ }
+
+ /** Sets the arrow position for the background drawable and triggers redraw */
+ fun setArrowPosition(position: BubblePopupDrawable.ArrowPosition) {
+ popupDrawable?.let {
+ it.arrowPosition = position
+ invalidate()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java
index e0c782d1675b..7c5bb211a4cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.common;
+package com.android.wm.shell.common.bubbles;
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import com.android.wm.shell.R;
+import androidx.annotation.DimenRes;
+import androidx.annotation.DrawableRes;
+import androidx.core.content.ContextCompat;
/**
* Circular view with a semitransparent, circular background with an 'X' inside it.
@@ -31,33 +32,44 @@ import com.android.wm.shell.R;
* This is used by both Bubbles and PIP as the dismiss target.
*/
public class DismissCircleView extends FrameLayout {
+ @DrawableRes int mBackgroundResId;
+ @DimenRes int mIconSizeResId;
private final ImageView mIconView = new ImageView(getContext());
public DismissCircleView(Context context) {
super(context);
- final Resources res = getResources();
-
- setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
-
- mIconView.setImageDrawable(res.getDrawable(R.drawable.pip_ic_close_white));
addView(mIconView);
-
- setViewSizes();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- final Resources res = getResources();
- setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
+ setBackground(ContextCompat.getDrawable(getContext(), mBackgroundResId));
+ setViewSizes();
+ }
+
+ /**
+ * Sets up view with the provided resource ids.
+ * Decouples resource dependency in order to be used externally (e.g. Launcher)
+ *
+ * @param backgroundResId drawable resource id of the circle background
+ * @param iconResId drawable resource id of the icon for the dismiss view
+ * @param iconSizeResId dimen resource id of the icon size
+ */
+ public void setup(@DrawableRes int backgroundResId, @DrawableRes int iconResId,
+ @DimenRes int iconSizeResId) {
+ mBackgroundResId = backgroundResId;
+ mIconSizeResId = iconSizeResId;
+
+ setBackground(ContextCompat.getDrawable(getContext(), backgroundResId));
+ mIconView.setImageDrawable(ContextCompat.getDrawable(getContext(), iconResId));
setViewSizes();
}
/** Retrieves the current dimensions for the icon and circle and applies them. */
private void setViewSizes() {
- final Resources res = getResources();
- final int iconSize = res.getDimensionPixelSize(R.dimen.dismiss_target_x_size);
+ final int iconSize = getResources().getDimensionPixelSize(mIconSizeResId);
mIconView.setLayoutParams(
new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
index 67ecb915e098..d275a0be8e93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
@@ -14,41 +14,73 @@
* limitations under the License.
*/
-package com.android.wm.shell.bubbles
+package com.android.wm.shell.common.bubbles
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.util.IntProperty
+import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowManager
import android.widget.FrameLayout
+import androidx.annotation.ColorRes
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
-import com.android.wm.shell.R
import com.android.wm.shell.animation.PhysicsAnimator
-import com.android.wm.shell.common.DismissCircleView
-/*
+/**
* View that handles interactions between DismissCircleView and BubbleStackView.
+ *
+ * @note [setup] method should be called after initialisation
*/
class DismissView(context: Context) : FrameLayout(context) {
+ /**
+ * The configuration is used to provide module specific resource ids
+ *
+ * @see [setup] method
+ */
+ data class Config(
+ /** dimen resource id of the dismiss target circle view size */
+ @DimenRes val targetSizeResId: Int,
+ /** dimen resource id of the icon size in the dismiss target */
+ @DimenRes val iconSizeResId: Int,
+ /** dimen resource id of the bottom margin for the dismiss target */
+ @DimenRes var bottomMarginResId: Int,
+ /** dimen resource id of the height for dismiss area gradient */
+ @DimenRes val floatingGradientHeightResId: Int,
+ /** color resource id of the dismiss area gradient color */
+ @ColorRes val floatingGradientColorResId: Int,
+ /** drawable resource id of the dismiss target background */
+ @DrawableRes val backgroundResId: Int,
+ /** drawable resource id of the icon for the dismiss target */
+ @DrawableRes val iconResId: Int
+ )
+
+ companion object {
+ private const val SHOULD_SETUP =
+ "The view isn't ready. Should be called after `setup`"
+ private val TAG = DismissView::class.simpleName
+ }
var circle = DismissCircleView(context)
var isShowing = false
- var targetSizeResId: Int
+ var config: Config? = null
private val animator = PhysicsAnimator.getInstance(circle)
private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
private val DISMISS_SCRIM_FADE_MS = 200L
private var wm: WindowManager =
context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
- private var gradientDrawable = createGradient()
+ private var gradientDrawable: GradientDrawable? = null
private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
object : IntProperty<GradientDrawable>("alpha") {
@@ -61,23 +93,41 @@ class DismissView(context: Context) : FrameLayout(context) {
}
init {
- setLayoutParams(LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
- Gravity.BOTTOM))
- updatePadding()
setClipToPadding(false)
setClipChildren(false)
setVisibility(View.INVISIBLE)
+ addView(circle)
+ }
+
+ /**
+ * Sets up view with the provided resource ids.
+ *
+ * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called
+ * with default params in module specific extension:
+ * @see [DismissView.setup] in DismissViewExt.kt
+ */
+ fun setup(config: Config) {
+ this.config = config
+
+ // Setup layout
+ layoutParams = LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ resources.getDimensionPixelSize(config.floatingGradientHeightResId),
+ Gravity.BOTTOM)
+ updatePadding()
+
+ // Setup gradient
+ gradientDrawable = createGradient(color = config.floatingGradientColorResId)
setBackgroundDrawable(gradientDrawable)
- targetSizeResId = R.dimen.dismiss_circle_size
- val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId)
- addView(circle, LayoutParams(targetSize, targetSize,
- Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL))
- // start with circle offscreen so it's animated up
- circle.setTranslationY(resources.getDimensionPixelSize(
- R.dimen.floating_dismiss_gradient_height).toFloat())
+ // Setup DismissCircleView
+ circle.setup(config.backgroundResId, config.iconResId, config.iconSizeResId)
+ val targetSize: Int = resources.getDimensionPixelSize(config.targetSizeResId)
+ circle.layoutParams = LayoutParams(targetSize, targetSize,
+ Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
+ // Initial position with circle offscreen so it's animated up
+ circle.translationY = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
+ .toFloat()
}
/**
@@ -85,6 +135,7 @@ class DismissView(context: Context) : FrameLayout(context) {
*/
fun show() {
if (isShowing) return
+ val gradientDrawable = checkExists(gradientDrawable) ?: return
isShowing = true
setVisibility(View.VISIBLE)
val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
@@ -104,6 +155,7 @@ class DismissView(context: Context) : FrameLayout(context) {
*/
fun hide() {
if (!isShowing) return
+ val gradientDrawable = checkExists(gradientDrawable) ?: return
isShowing = false
val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
gradientDrawable.alpha, 0)
@@ -124,18 +176,17 @@ class DismissView(context: Context) : FrameLayout(context) {
}
fun updateResources() {
+ val config = checkExists(config) ?: return
updatePadding()
- layoutParams.height = resources.getDimensionPixelSize(
- R.dimen.floating_dismiss_gradient_height)
-
- val targetSize = resources.getDimensionPixelSize(targetSizeResId)
+ layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
+ val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
circle.layoutParams.width = targetSize
circle.layoutParams.height = targetSize
circle.requestLayout()
}
- private fun createGradient(): GradientDrawable {
- val gradientColor = context.resources.getColor(android.R.color.system_neutral1_900)
+ private fun createGradient(@ColorRes color: Int): GradientDrawable {
+ val gradientColor = ContextCompat.getColor(context, color)
val alpha = 0.7f * 255
val gradientColorWithAlpha = Color.argb(alpha.toInt(),
Color.red(gradientColor),
@@ -150,10 +201,22 @@ class DismissView(context: Context) : FrameLayout(context) {
}
private fun updatePadding() {
+ val config = checkExists(config) ?: return
val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets()
val navInset = insets.getInsetsIgnoringVisibility(
WindowInsets.Type.navigationBars())
setPadding(0, 0, 0, navInset.bottom +
- resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin))
+ resources.getDimensionPixelSize(config.bottomMarginResId))
+ }
+
+ /**
+ * Checks if the value is set up and exists, if not logs an exception.
+ * Used for convenient logging in case `setup` wasn't called before
+ *
+ * @return value provided as argument
+ */
+ private fun <T>checkExists(value: T?): T? {
+ if (value == null) Log.e(TAG, SHOULD_SETUP)
+ return value
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
index ea9d065d5f53..cc37bd3a4589 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.bubbles
+package com.android.wm.shell.common.bubbles
import android.graphics.PointF
import android.view.MotionEvent
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
new file mode 100644
index 000000000000..a8743fbed5e0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.PointF
+import android.util.Size
+import com.android.wm.shell.R
+
+class LegacySizeSpecSource(
+ private val context: Context,
+ private val pipDisplayLayoutState: PipDisplayLayoutState
+) : SizeSpecSource {
+
+ private var mDefaultMinSize = 0
+ /** The absolute minimum an overridden size's edge can be */
+ private var mOverridableMinSize = 0
+ /** The preferred minimum (and default minimum) size specified by apps. */
+ private var mOverrideMinSize: Size? = null
+
+ private var mDefaultSizePercent = 0f
+ private var mMinimumSizePercent = 0f
+ private var mMaxAspectRatioForMinSize = 0f
+ private var mMinAspectRatioForMinSize = 0f
+
+ init {
+ reloadResources()
+ }
+
+ private fun reloadResources() {
+ val res: Resources = context.getResources()
+
+ mDefaultMinSize = res.getDimensionPixelSize(
+ R.dimen.default_minimal_size_pip_resizable_task)
+ mOverridableMinSize = res.getDimensionPixelSize(
+ R.dimen.overridable_minimal_size_pip_resizable_task)
+
+ mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent)
+ mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1)
+
+ mMaxAspectRatioForMinSize = res.getFloat(
+ R.dimen.config_pictureInPictureAspectRatioLimitForMinSize)
+ mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize
+ }
+
+ override fun onConfigurationChanged() {
+ reloadResources()
+ }
+
+ override fun getMaxSize(aspectRatio: Float): Size {
+ val insetBounds = pipDisplayLayoutState.insetBounds
+
+ val shorterLength: Int = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height())
+ val totalHorizontalPadding: Int = (insetBounds.left +
+ (getDisplayBounds().width() - insetBounds.right))
+ val totalVerticalPadding: Int = (insetBounds.top +
+ (getDisplayBounds().height() - insetBounds.bottom))
+
+ return if (aspectRatio > 1f) {
+ val maxWidth = Math.max(getDefaultSize(aspectRatio).width,
+ shorterLength - totalHorizontalPadding)
+ val maxHeight = (maxWidth / aspectRatio).toInt()
+ Size(maxWidth, maxHeight)
+ } else {
+ val maxHeight = Math.max(getDefaultSize(aspectRatio).height,
+ shorterLength - totalVerticalPadding)
+ val maxWidth = (maxHeight * aspectRatio).toInt()
+ Size(maxWidth, maxHeight)
+ }
+ }
+
+ override fun getDefaultSize(aspectRatio: Float): Size {
+ if (mOverrideMinSize != null) {
+ return getMinSize(aspectRatio)
+ }
+ val smallestDisplaySize: Int = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height())
+ val minSize = Math.max(getMinEdgeSize().toFloat(),
+ smallestDisplaySize * mDefaultSizePercent).toInt()
+ val width: Int
+ val height: Int
+ if (aspectRatio <= mMinAspectRatioForMinSize ||
+ aspectRatio > mMaxAspectRatioForMinSize) {
+ // Beyond these points, we can just use the min size as the shorter edge
+ if (aspectRatio <= 1) {
+ // Portrait, width is the minimum size
+ width = minSize
+ height = Math.round(width / aspectRatio)
+ } else {
+ // Landscape, height is the minimum size
+ height = minSize
+ width = Math.round(height * aspectRatio)
+ }
+ } else {
+ // Within these points, ensure that the bounds fit within the radius of the limits
+ // at the points
+ val widthAtMaxAspectRatioForMinSize: Float = mMaxAspectRatioForMinSize * minSize
+ val radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize.toFloat())
+ height = Math.round(Math.sqrt((radius * radius /
+ (aspectRatio * aspectRatio + 1)).toDouble())).toInt()
+ width = Math.round(height * aspectRatio)
+ }
+ return Size(width, height)
+ }
+
+ override fun getMinSize(aspectRatio: Float): Size {
+ if (mOverrideMinSize != null) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+ }
+ val shorterLength: Int = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height())
+ val minWidth: Int
+ val minHeight: Int
+ if (aspectRatio > 1f) {
+ minWidth = Math.min(getDefaultSize(aspectRatio).width.toFloat(),
+ shorterLength * mMinimumSizePercent).toInt()
+ minHeight = (minWidth / aspectRatio).toInt()
+ } else {
+ minHeight = Math.min(getDefaultSize(aspectRatio).height.toFloat(),
+ shorterLength * mMinimumSizePercent).toInt()
+ minWidth = (minHeight * aspectRatio).toInt()
+ }
+ return Size(minWidth, minHeight)
+ }
+
+ override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size {
+ val smallestSize = Math.min(size.width, size.height)
+ val minSize = Math.max(getMinEdgeSize(), smallestSize)
+ val width: Int
+ val height: Int
+ if (aspectRatio <= 1) {
+ // Portrait, width is the minimum size.
+ width = minSize
+ height = Math.round(width / aspectRatio)
+ } else {
+ // Landscape, height is the minimum size
+ height = minSize
+ width = Math.round(height * aspectRatio)
+ }
+ return Size(width, height)
+ }
+
+ private fun getDisplayBounds() = pipDisplayLayoutState.displayBounds
+
+ /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
+ override fun setOverrideMinSize(overrideMinSize: Size?) {
+ mOverrideMinSize = overrideMinSize
+ }
+
+ /** Returns the preferred minimal size specified by the activity in PIP. */
+ override fun getOverrideMinSize(): Size? {
+ val overrideMinSize = mOverrideMinSize ?: return null
+ return if (overrideMinSize.width < mOverridableMinSize ||
+ overrideMinSize.height < mOverridableMinSize) {
+ Size(mOverridableMinSize, mOverridableMinSize)
+ } else {
+ overrideMinSize
+ }
+ }
+
+ private fun getMinEdgeSize(): Int {
+ return if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize()
+ }
+
+ /**
+ * Returns the adjusted overridden min size if it is set; otherwise, returns null.
+ *
+ *
+ * Overridden min size needs to be adjusted in its own way while making sure that the target
+ * aspect ratio is maintained
+ *
+ * @param aspectRatio target aspect ratio
+ */
+ private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? {
+ val size = getOverrideMinSize() ?: return null
+ val sizeAspectRatio = size.width / size.height.toFloat()
+ return if (sizeAspectRatio > aspectRatio) {
+ // Size is wider, fix the width and increase the height
+ Size(size.width, (size.width / aspectRatio).toInt())
+ } else {
+ // Size is taller, fix the height and adjust the width.
+ Size((size.height * aspectRatio).toInt(), size.height)
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java
index f9332e4bdb2e..133242d15822 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import android.content.Context;
import android.content.res.Resources;
@@ -24,9 +24,6 @@ import android.util.ArraySet;
import android.view.Gravity;
import com.android.wm.shell.R;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import java.util.Set;
@@ -40,6 +37,7 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac
"persist.wm.debug.enable_pip_keep_clear_algorithm_gravity", false);
protected int mKeepClearAreasPadding;
+ private int mImeOffset;
public PhonePipKeepClearAlgorithm(Context context) {
reloadResources(context);
@@ -48,6 +46,7 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac
private void reloadResources(Context context) {
final Resources res = context.getResources();
mKeepClearAreasPadding = res.getDimensionPixelSize(R.dimen.pip_keep_clear_areas_padding);
+ mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
}
/**
@@ -61,7 +60,7 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac
Rect insets = new Rect();
pipBoundsAlgorithm.getInsetBounds(insets);
if (pipBoundsState.isImeShowing()) {
- insets.bottom -= pipBoundsState.getImeHeight();
+ insets.bottom -= (pipBoundsState.getImeHeight() + mImeOffset);
}
// if PiP is stashed we only adjust the vertical position if it's outside of insets and
// ignore all keep clear areas, since it's already on the side
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
new file mode 100644
index 000000000000..18c7bdd6d5ba
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.SystemProperties
+import android.util.Size
+import com.android.wm.shell.R
+import java.io.PrintWriter
+
+class PhoneSizeSpecSource(
+ private val context: Context,
+ private val pipDisplayLayoutState: PipDisplayLayoutState
+) : SizeSpecSource {
+ private var DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16
+
+ private var mDefaultMinSize = 0
+ /** The absolute minimum an overridden size's edge can be */
+ private var mOverridableMinSize = 0
+ /** The preferred minimum (and default minimum) size specified by apps. */
+ private var mOverrideMinSize: Size? = null
+
+
+ /** Default and minimum percentages for the PIP size logic. */
+ private val mDefaultSizePercent: Float
+ private val mMinimumSizePercent: Float
+
+ /** Aspect ratio that the PIP size spec logic optimizes for. */
+ private var mOptimizedAspectRatio = 0f
+
+ init {
+ mDefaultSizePercent = SystemProperties
+ .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat()
+ mMinimumSizePercent = SystemProperties
+ .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat()
+
+ reloadResources()
+ }
+
+ private fun reloadResources() {
+ val res: Resources = context.getResources()
+
+ mDefaultMinSize = res.getDimensionPixelSize(
+ R.dimen.default_minimal_size_pip_resizable_task)
+ mOverridableMinSize = res.getDimensionPixelSize(
+ R.dimen.overridable_minimal_size_pip_resizable_task)
+
+ val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio)
+ // make sure the optimized aspect ratio is valid with a default value to fall back to
+ mOptimizedAspectRatio = if (requestedOptAspRatio > 1) {
+ DEFAULT_OPTIMIZED_ASPECT_RATIO
+ } else {
+ requestedOptAspRatio
+ }
+ }
+
+ override fun onConfigurationChanged() {
+ reloadResources()
+ }
+
+ /**
+ * Calculates the max size of PIP.
+ *
+ * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
+ * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
+ * whole screen. A linear function is used to calculate these sizes.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the max size of the PIP
+ */
+ override fun getMaxSize(aspectRatio: Float): Size {
+ val insetBounds = pipDisplayLayoutState.insetBounds
+ val displayBounds = pipDisplayLayoutState.displayBounds
+
+ val totalHorizontalPadding: Int = (insetBounds.left +
+ (displayBounds.width() - insetBounds.right))
+ val totalVerticalPadding: Int = (insetBounds.top +
+ (displayBounds.height() - insetBounds.bottom))
+ val shorterLength: Int = Math.min(displayBounds.width() - totalHorizontalPadding,
+ displayBounds.height() - totalVerticalPadding)
+ var maxWidth: Int
+ val maxHeight: Int
+
+ // use the optimized max sizing logic only within a certain aspect ratio range
+ if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
+ // this formula and its derivation is explained in b/198643358#comment16
+ maxWidth = Math.round(mOptimizedAspectRatio * shorterLength +
+ shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1 + aspectRatio))
+ // make sure the max width doesn't go beyond shorter screen length after rounding
+ maxWidth = Math.min(maxWidth, shorterLength)
+ maxHeight = Math.round(maxWidth / aspectRatio)
+ } else {
+ if (aspectRatio > 1f) {
+ maxWidth = shorterLength
+ maxHeight = Math.round(maxWidth / aspectRatio)
+ } else {
+ maxHeight = shorterLength
+ maxWidth = Math.round(maxHeight * aspectRatio)
+ }
+ }
+ return Size(maxWidth, maxHeight)
+ }
+
+ /**
+ * Decreases the dimensions by a percentage relative to max size to get default size.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the default size of the PIP
+ */
+ override fun getDefaultSize(aspectRatio: Float): Size {
+ val minSize = getMinSize(aspectRatio)
+ if (mOverrideMinSize != null) {
+ return minSize
+ }
+ val maxSize = getMaxSize(aspectRatio)
+ val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent),
+ minSize.width)
+ val defaultHeight = Math.round(defaultWidth / aspectRatio)
+ return Size(defaultWidth, defaultHeight)
+ }
+
+ /**
+ * Decreases the dimensions by a certain percentage relative to max size to get min size.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the min size of the PIP
+ */
+ override fun getMinSize(aspectRatio: Float): Size {
+ // if there is an overridden min size provided, return that
+ if (mOverrideMinSize != null) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+ }
+ val maxSize = getMaxSize(aspectRatio)
+ var minWidth = Math.round(maxSize.width * mMinimumSizePercent)
+ var minHeight = Math.round(maxSize.height * mMinimumSizePercent)
+
+ // make sure the calculated min size is not smaller than the allowed default min size
+ if (aspectRatio > 1f) {
+ minHeight = Math.max(minHeight, mDefaultMinSize)
+ minWidth = Math.round(minHeight * aspectRatio)
+ } else {
+ minWidth = Math.max(minWidth, mDefaultMinSize)
+ minHeight = Math.round(minWidth / aspectRatio)
+ }
+ return Size(minWidth, minHeight)
+ }
+
+ /**
+ * Returns the size for target aspect ratio making sure new size conforms with the rules.
+ *
+ *
+ * Recalculates the dimensions such that the target aspect ratio is achieved, while
+ * maintaining the same maximum size to current size ratio.
+ *
+ * @param size current size
+ * @param aspectRatio target aspect ratio
+ */
+ override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size {
+ if (size == mOverrideMinSize) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+ }
+
+ val currAspectRatio = size.width.toFloat() / size.height
+
+ // getting the percentage of the max size that current size takes
+ val currentMaxSize = getMaxSize(currAspectRatio)
+ val currentPercent = size.width.toFloat() / currentMaxSize.width
+
+ // getting the max size for the target aspect ratio
+ val updatedMaxSize = getMaxSize(aspectRatio)
+ var width = Math.round(updatedMaxSize.width * currentPercent)
+ var height = Math.round(updatedMaxSize.height * currentPercent)
+
+ // adjust the dimensions if below allowed min edge size
+ val minEdgeSize =
+ if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize()
+
+ if (width < minEdgeSize && aspectRatio <= 1) {
+ width = minEdgeSize
+ height = Math.round(width / aspectRatio)
+ } else if (height < minEdgeSize && aspectRatio > 1) {
+ height = minEdgeSize
+ width = Math.round(height * aspectRatio)
+ }
+
+ // reduce the dimensions of the updated size to the calculated percentage
+ return Size(width, height)
+ }
+
+ /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
+ override fun setOverrideMinSize(overrideMinSize: Size?) {
+ mOverrideMinSize = overrideMinSize
+ }
+
+ /** Returns the preferred minimal size specified by the activity in PIP. */
+ override fun getOverrideMinSize(): Size? {
+ val overrideMinSize = mOverrideMinSize ?: return null
+ return if (overrideMinSize.width < mOverridableMinSize ||
+ overrideMinSize.height < mOverridableMinSize) {
+ Size(mOverridableMinSize, mOverridableMinSize)
+ } else {
+ overrideMinSize
+ }
+ }
+
+ /**
+ * Returns the adjusted overridden min size if it is set; otherwise, returns null.
+ *
+ *
+ * Overridden min size needs to be adjusted in its own way while making sure that the target
+ * aspect ratio is maintained
+ *
+ * @param aspectRatio target aspect ratio
+ */
+ private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? {
+ val size = getOverrideMinSize() ?: return null
+ val sizeAspectRatio = size.width / size.height.toFloat()
+ return if (sizeAspectRatio > aspectRatio) {
+ // Size is wider, fix the width and increase the height
+ Size(size.width, (size.width / aspectRatio).toInt())
+ } else {
+ // Size is taller, fix the height and adjust the width.
+ Size((size.height * aspectRatio).toInt(), size.height)
+ }
+ }
+
+ override fun dump(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize)
+ pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize)
+ pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize)
+ pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent)
+ pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent)
+ pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
new file mode 100644
index 000000000000..4abb35c2a428
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.pip
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.PackageManager
+import com.android.wm.shell.common.ShellExecutor
+
+class PipAppOpsListener(
+ private val mContext: Context,
+ private val mCallback: Callback,
+ private val mMainExecutor: ShellExecutor
+) {
+ private val mAppOpsManager: AppOpsManager = checkNotNull(
+ mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager)
+ private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName ->
+ try {
+ // Dismiss the PiP once the user disables the app ops setting for that package
+ val topPipActivityInfo = PipUtils.getTopPipActivity(mContext)
+ val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener
+ val userId = topPipActivityInfo.second
+ val appInfo = mContext.packageManager
+ .getApplicationInfoAsUser(packageName, 0, userId)
+ if (appInfo.packageName == componentName.packageName &&
+ mAppOpsManager.checkOpNoThrow(
+ AppOpsManager.OP_PICTURE_IN_PICTURE, appInfo.uid,
+ packageName
+ ) != AppOpsManager.MODE_ALLOWED
+ ) {
+ mMainExecutor.execute { mCallback.dismissPip() }
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ // Unregister the listener if the package can't be found
+ unregisterAppOpsListener()
+ }
+ }
+
+ fun onActivityPinned(packageName: String) {
+ // Register for changes to the app ops setting for this package while it is in PiP
+ registerAppOpsListener(packageName)
+ }
+
+ fun onActivityUnpinned() {
+ // Unregister for changes to the previously PiP'ed package
+ unregisterAppOpsListener()
+ }
+
+ private fun registerAppOpsListener(packageName: String) {
+ mAppOpsManager.startWatchingMode(
+ AppOpsManager.OP_PICTURE_IN_PICTURE, packageName,
+ mAppOpsChangedListener
+ )
+ }
+
+ private fun unregisterAppOpsListener() {
+ mAppOpsManager.stopWatchingMode(mAppOpsChangedListener)
+ }
+
+ /** Callback for PipAppOpsListener to request changes to the PIP window. */
+ interface Callback {
+ /** Dismisses the PIP window. */
+ fun dismissPip()
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
index f51eb5299dd9..a9f687fc9b2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,7 +28,6 @@ import android.util.Size;
import android.view.Gravity;
import com.android.wm.shell.R;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import java.io.PrintWriter;
@@ -41,7 +40,8 @@ public class PipBoundsAlgorithm {
private static final float INVALID_SNAP_FRACTION = -1f;
@NonNull private final PipBoundsState mPipBoundsState;
- @NonNull protected final PipSizeSpecHandler mPipSizeSpecHandler;
+ @NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState;
+ @NonNull protected final SizeSpecSource mSizeSpecSource;
private final PipSnapAlgorithm mSnapAlgorithm;
private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
@@ -53,11 +53,13 @@ public class PipBoundsAlgorithm {
public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm,
@NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
+ @NonNull SizeSpecSource sizeSpecSource) {
mPipBoundsState = pipBoundsState;
mSnapAlgorithm = pipSnapAlgorithm;
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mSizeSpecSource = sizeSpecSource;
reloadResources(context);
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
// resources as it would clobber mAspectRatio when entering PiP from fullscreen which
@@ -74,11 +76,6 @@ public class PipBoundsAlgorithm {
R.dimen.config_pictureInPictureDefaultAspectRatio);
mDefaultStackGravity = res.getInteger(
R.integer.config_defaultPictureInPictureGravity);
- final String screenEdgeInsetsDpString = res.getString(
- R.string.config_defaultPictureInPictureScreenEdgeInsets);
- final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
- ? Size.parseSize(screenEdgeInsetsDpString)
- : null;
mMinAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
mMaxAspectRatio = res.getFloat(
@@ -160,8 +157,8 @@ public class PipBoundsAlgorithm {
// If either dimension is smaller than the allowed minimum, adjust them
// according to mOverridableMinSize
return new Size(
- Math.max(windowLayout.minWidth, mPipSizeSpecHandler.getOverrideMinEdgeSize()),
- Math.max(windowLayout.minHeight, mPipSizeSpecHandler.getOverrideMinEdgeSize()));
+ Math.max(windowLayout.minWidth, getOverrideMinEdgeSize()),
+ Math.max(windowLayout.minHeight, getOverrideMinEdgeSize()));
}
return null;
}
@@ -203,7 +200,8 @@ public class PipBoundsAlgorithm {
*
* @return {@code false} if the given source is too small to use for the entering animation.
*/
- static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) {
+ public static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint,
+ Rect destinationBounds) {
return sourceRectHint != null
&& sourceRectHint.width() > destinationBounds.width()
&& sourceRectHint.height() > destinationBounds.height();
@@ -225,7 +223,7 @@ public class PipBoundsAlgorithm {
}
/**
- * @return whether the given {@param aspectRatio} is valid.
+ * @return whether the given aspectRatio is valid.
*/
public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
return Float.compare(mMinAspectRatio, aspectRatio) <= 0
@@ -255,10 +253,10 @@ public class PipBoundsAlgorithm {
final Size size;
if (useCurrentMinEdgeSize || useCurrentSize) {
// Use the existing size but adjusted to the new aspect ratio.
- size = mPipSizeSpecHandler.getSizeForAspectRatio(
+ size = mSizeSpecSource.getSizeForAspectRatio(
new Size(stackBounds.width(), stackBounds.height()), aspectRatio);
} else {
- size = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
+ size = mSizeSpecSource.getDefaultSize(aspectRatio);
}
final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
@@ -287,7 +285,7 @@ public class PipBoundsAlgorithm {
getInsetBounds(insetBounds);
// Calculate the default size
- defaultSize = mPipSizeSpecHandler.getDefaultSize(mDefaultAspectRatio);
+ defaultSize = mSizeSpecSource.getDefaultSize(mDefaultAspectRatio);
// Now that we have the default size, apply the snap fraction if valid or position the
// bounds using the default gravity.
@@ -309,7 +307,11 @@ public class PipBoundsAlgorithm {
* Populates the bounds on the screen that the PIP can be visible in.
*/
public void getInsetBounds(Rect outRect) {
- outRect.set(mPipSizeSpecHandler.getInsetBounds());
+ outRect.set(mPipDisplayLayoutState.getInsetBounds());
+ }
+
+ private int getOverrideMinEdgeSize() {
+ return mSizeSpecSource.getOverrideMinEdgeSize();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 9a775dff1f69..3b32b6c7b083 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -36,7 +36,6 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
@@ -87,7 +86,7 @@ public class PipBoundsState {
private int mStashOffset;
private @Nullable PipReentryState mPipReentryState;
private final LauncherState mLauncherState = new LauncherState();
- private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
+ private final @NonNull SizeSpecSource mSizeSpecSource;
private @Nullable ComponentName mLastPipComponentName;
private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
private boolean mIsImeShowing;
@@ -127,17 +126,20 @@ public class PipBoundsState {
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
- public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
mContext = context;
reloadResources();
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mSizeSpecSource = sizeSpecSource;
mPipDisplayLayoutState = pipDisplayLayoutState;
}
/** Reloads the resources. */
public void onConfigurationChanged() {
reloadResources();
+
+ // update the size spec resources upon config change too
+ mSizeSpecSource.onConfigurationChanged();
}
private void reloadResources() {
@@ -311,15 +313,18 @@ public class PipBoundsState {
return mPipDisplayLayoutState.getDisplayLayout();
}
+ /**
+ * Clears the PiP re-entry state.
+ */
@VisibleForTesting
- void clearReentryState() {
+ public void clearReentryState() {
mPipReentryState = null;
}
/** Sets the preferred size of PIP as specified by the activity in PIP mode. */
public void setOverrideMinSize(@Nullable Size overrideMinSize) {
final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize());
- mPipSizeSpecHandler.setOverrideMinSize(overrideMinSize);
+ mSizeSpecSource.setOverrideMinSize(overrideMinSize);
if (changed && mOnMinimalSizeChangeCallback != null) {
mOnMinimalSizeChangeCallback.run();
}
@@ -328,12 +333,12 @@ public class PipBoundsState {
/** Returns the preferred minimal size specified by the activity in PIP. */
@Nullable
public Size getOverrideMinSize() {
- return mPipSizeSpecHandler.getOverrideMinSize();
+ return mSizeSpecSource.getOverrideMinSize();
}
/** Returns the minimum edge size of the override minimum size, or 0 if not set. */
public int getOverrideMinEdgeSize() {
- return mPipSizeSpecHandler.getOverrideMinEdgeSize();
+ return mSizeSpecSource.getOverrideMinEdgeSize();
}
/** Get the state of the bounds in motion. */
@@ -397,11 +402,18 @@ public class PipBoundsState {
mNamedUnrestrictedKeepClearAreas.remove(name);
}
+
+ /**
+ * @return restricted keep clear areas.
+ */
@NonNull
public Set<Rect> getRestrictedKeepClearAreas() {
return mRestrictedKeepClearAreas;
}
+ /**
+ * @return unrestricted keep clear areas.
+ */
@NonNull
public Set<Rect> getUnrestrictedKeepClearAreas() {
if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
@@ -558,7 +570,11 @@ public class PipBoundsState {
}
}
- static final class PipReentryState {
+ /**
+ * Represents the state of pip to potentially restore upon reentry.
+ */
+ @VisibleForTesting
+ public static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
private final @Nullable Size mSize;
@@ -570,11 +586,11 @@ public class PipBoundsState {
}
@Nullable
- Size getSize() {
+ public Size getSize() {
return mSize;
}
- float getSnapFraction() {
+ public float getSnapFraction() {
return mSnapFraction;
}
@@ -613,5 +629,6 @@ public class PipBoundsState {
}
mLauncherState.dump(pw, innerPrefix);
mMotionBoundsState.dump(pw, innerPrefix);
+ mSizeSpecSource.dump(pw, innerPrefix);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
index 0f76af48199f..ed42117a55af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
@@ -14,14 +14,20 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
+
+import static com.android.wm.shell.common.pip.PipUtils.dpToPx;
import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.util.Size;
import android.view.Surface;
import androidx.annotation.NonNull;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.dagger.WMSingleton;
@@ -40,13 +46,51 @@ public class PipDisplayLayoutState {
private int mDisplayId;
@NonNull private DisplayLayout mDisplayLayout;
+ private Point mScreenEdgeInsets = null;
+
@Inject
public PipDisplayLayoutState(Context context) {
mContext = context;
mDisplayLayout = new DisplayLayout();
+ reloadResources();
+ }
+
+ /** Responds to configuration change. */
+ public void onConfigurationChanged() {
+ reloadResources();
+ }
+
+ private void reloadResources() {
+ Resources res = mContext.getResources();
+
+ final String screenEdgeInsetsDpString = res.getString(
+ R.string.config_defaultPictureInPictureScreenEdgeInsets);
+ final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
+ ? Size.parseSize(screenEdgeInsetsDpString)
+ : null;
+ mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
+ : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
+ dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
+ }
+
+ public Point getScreenEdgeInsets() {
+ return mScreenEdgeInsets;
+ }
+
+ /**
+ * Returns the inset bounds the PIP window can be visible in.
+ */
+ public Rect getInsetBounds() {
+ Rect insetBounds = new Rect();
+ Rect insets = getDisplayLayout().stableInsets();
+ insetBounds.set(insets.left + getScreenEdgeInsets().x,
+ insets.top + getScreenEdgeInsets().y,
+ getDisplayLayout().width() - insets.right - getScreenEdgeInsets().x,
+ getDisplayLayout().height() - insets.bottom - getScreenEdgeInsets().y);
+ return insetBounds;
}
- /** Update the display layout. */
+ /** Set the display layout. */
public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
mDisplayLayout.set(displayLayout);
}
@@ -87,5 +131,6 @@ public class PipDisplayLayoutState {
pw.println(prefix + TAG);
pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds());
+ pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java
index 5045cf905ee6..954233c04ed4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import android.graphics.Rect;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt
new file mode 100644
index 000000000000..427a555eee92
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.pip
+
+import android.annotation.DrawableRes
+import android.annotation.StringRes
+import android.app.PendingIntent
+import android.app.RemoteAction
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.drawable.Icon
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.MediaSessionManager
+import android.media.session.PlaybackState
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.UserHandle
+import com.android.wm.shell.R
+import java.util.function.Consumer
+
+/**
+ * Interfaces with the [MediaSessionManager] to compose the right set of actions to show (only
+ * if there are no actions from the PiP activity itself). The active media controller is only set
+ * when there is a media session from the top PiP activity.
+ */
+class PipMediaController(private val mContext: Context, private val mMainHandler: Handler) {
+ /**
+ * A listener interface to receive notification on changes to the media actions.
+ */
+ interface ActionListener {
+ /**
+ * Called when the media actions changed.
+ */
+ fun onMediaActionsChanged(actions: List<RemoteAction?>?)
+ }
+
+ /**
+ * A listener interface to receive notification on changes to the media metadata.
+ */
+ interface MetadataListener {
+ /**
+ * Called when the media metadata changed.
+ */
+ fun onMediaMetadataChanged(metadata: MediaMetadata?)
+ }
+
+ /**
+ * A listener interface to receive notification on changes to the media session token.
+ */
+ interface TokenListener {
+ /**
+ * Called when the media session token changed.
+ */
+ fun onMediaSessionTokenChanged(token: MediaSession.Token?)
+ }
+
+ private val mHandlerExecutor: HandlerExecutor = HandlerExecutor(mMainHandler)
+ private val mMediaSessionManager: MediaSessionManager?
+ private var mMediaController: MediaController? = null
+ private val mPauseAction: RemoteAction
+ private val mPlayAction: RemoteAction
+ private val mNextAction: RemoteAction
+ private val mPrevAction: RemoteAction
+ private val mMediaActionReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (mMediaController == null) {
+ // no active media session, bail early.
+ return
+ }
+ when (intent.action) {
+ ACTION_PLAY -> mMediaController!!.transportControls.play()
+ ACTION_PAUSE -> mMediaController!!.transportControls.pause()
+ ACTION_NEXT -> mMediaController!!.transportControls.skipToNext()
+ ACTION_PREV -> mMediaController!!.transportControls.skipToPrevious()
+ }
+ }
+ }
+ private val mPlaybackChangedListener: MediaController.Callback =
+ object : MediaController.Callback() {
+ override fun onPlaybackStateChanged(state: PlaybackState?) {
+ notifyActionsChanged()
+ }
+
+ override fun onMetadataChanged(metadata: MediaMetadata?) {
+ notifyMetadataChanged(metadata)
+ }
+ }
+ private val mSessionsChangedListener =
+ MediaSessionManager.OnActiveSessionsChangedListener { controllers: List<MediaController>? ->
+ resolveActiveMediaController(controllers)
+ }
+ private val mActionListeners = ArrayList<ActionListener>()
+ private val mMetadataListeners = ArrayList<MetadataListener>()
+ private val mTokenListeners = ArrayList<TokenListener>()
+
+ init {
+ val mediaControlFilter = IntentFilter()
+ mediaControlFilter.addAction(ACTION_PLAY)
+ mediaControlFilter.addAction(ACTION_PAUSE)
+ mediaControlFilter.addAction(ACTION_NEXT)
+ mediaControlFilter.addAction(ACTION_PREV)
+ mContext.registerReceiverForAllUsers(
+ mMediaActionReceiver, mediaControlFilter,
+ SYSTEMUI_PERMISSION, mMainHandler, Context.RECEIVER_EXPORTED
+ )
+
+ // Creates the standard media buttons that we may show.
+ mPauseAction = getDefaultRemoteAction(
+ R.string.pip_pause,
+ R.drawable.pip_ic_pause_white, ACTION_PAUSE
+ )
+ mPlayAction = getDefaultRemoteAction(
+ R.string.pip_play,
+ R.drawable.pip_ic_play_arrow_white, ACTION_PLAY
+ )
+ mNextAction = getDefaultRemoteAction(
+ R.string.pip_skip_to_next,
+ R.drawable.pip_ic_skip_next_white, ACTION_NEXT
+ )
+ mPrevAction = getDefaultRemoteAction(
+ R.string.pip_skip_to_prev,
+ R.drawable.pip_ic_skip_previous_white, ACTION_PREV
+ )
+ mMediaSessionManager = mContext.getSystemService(
+ MediaSessionManager::class.java
+ )
+ }
+
+ /**
+ * Handles when an activity is pinned.
+ */
+ fun onActivityPinned() {
+ // Once we enter PiP, try to find the active media controller for the top most activity
+ resolveActiveMediaController(
+ mMediaSessionManager!!.getActiveSessionsForUser(
+ null,
+ UserHandle.CURRENT
+ )
+ )
+ }
+
+ /**
+ * Adds a new media action listener.
+ */
+ fun addActionListener(listener: ActionListener) {
+ if (!mActionListeners.contains(listener)) {
+ mActionListeners.add(listener)
+ listener.onMediaActionsChanged(mediaActions)
+ }
+ }
+
+ /**
+ * Removes a media action listener.
+ */
+ fun removeActionListener(listener: ActionListener) {
+ listener.onMediaActionsChanged(emptyList<RemoteAction>())
+ mActionListeners.remove(listener)
+ }
+
+ /**
+ * Adds a new media metadata listener.
+ */
+ fun addMetadataListener(listener: MetadataListener) {
+ if (!mMetadataListeners.contains(listener)) {
+ mMetadataListeners.add(listener)
+ listener.onMediaMetadataChanged(mediaMetadata)
+ }
+ }
+
+ /**
+ * Removes a media metadata listener.
+ */
+ fun removeMetadataListener(listener: MetadataListener) {
+ listener.onMediaMetadataChanged(null)
+ mMetadataListeners.remove(listener)
+ }
+
+ /**
+ * Adds a new token listener.
+ */
+ fun addTokenListener(listener: TokenListener) {
+ if (!mTokenListeners.contains(listener)) {
+ mTokenListeners.add(listener)
+ listener.onMediaSessionTokenChanged(token)
+ }
+ }
+
+ /**
+ * Removes a token listener.
+ */
+ fun removeTokenListener(listener: TokenListener) {
+ listener.onMediaSessionTokenChanged(null)
+ mTokenListeners.remove(listener)
+ }
+
+ private val token: MediaSession.Token?
+ get() = if (mMediaController == null) {
+ null
+ } else mMediaController!!.sessionToken
+ private val mediaMetadata: MediaMetadata?
+ get() = if (mMediaController != null) mMediaController!!.metadata else null
+
+ private val mediaActions: List<RemoteAction?>
+ /**
+ * Gets the set of media actions currently available.
+ */
+ get() {
+ if (mMediaController == null) {
+ return emptyList<RemoteAction>()
+ }
+ // Cache the PlaybackState since it's a Binder call.
+ // Safe because mMediaController is guaranteed non-null here.
+ val playbackState: PlaybackState = mMediaController!!.playbackState
+ ?: return emptyList<RemoteAction>()
+ val mediaActions = ArrayList<RemoteAction?>()
+ val isPlaying = playbackState.isActive
+ val actions = playbackState.actions
+
+ // Prev action
+ mPrevAction.isEnabled =
+ actions and PlaybackState.ACTION_SKIP_TO_PREVIOUS != 0L
+ mediaActions.add(mPrevAction)
+
+ // Play/pause action
+ if (!isPlaying && actions and PlaybackState.ACTION_PLAY != 0L) {
+ mediaActions.add(mPlayAction)
+ } else if (isPlaying && actions and PlaybackState.ACTION_PAUSE != 0L) {
+ mediaActions.add(mPauseAction)
+ }
+
+ // Next action
+ mNextAction.isEnabled =
+ actions and PlaybackState.ACTION_SKIP_TO_NEXT != 0L
+ mediaActions.add(mNextAction)
+ return mediaActions
+ }
+
+ /** @return Default [RemoteAction] sends broadcast back to SysUI.
+ */
+ private fun getDefaultRemoteAction(
+ @StringRes titleAndDescription: Int,
+ @DrawableRes icon: Int,
+ action: String
+ ): RemoteAction {
+ val titleAndDescriptionStr = mContext.getString(titleAndDescription)
+ val intent = Intent(action)
+ intent.setPackage(mContext.packageName)
+ return RemoteAction(
+ Icon.createWithResource(mContext, icon),
+ titleAndDescriptionStr, titleAndDescriptionStr,
+ PendingIntent.getBroadcast(
+ mContext, 0 /* requestCode */, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ )
+ }
+
+ /**
+ * Re-registers the session listener for the current user.
+ */
+ fun registerSessionListenerForCurrentUser() {
+ mMediaSessionManager!!.removeOnActiveSessionsChangedListener(mSessionsChangedListener)
+ mMediaSessionManager.addOnActiveSessionsChangedListener(
+ null, UserHandle.CURRENT,
+ mHandlerExecutor, mSessionsChangedListener
+ )
+ }
+
+ /**
+ * Tries to find and set the active media controller for the top PiP activity.
+ */
+ private fun resolveActiveMediaController(controllers: List<MediaController>?) {
+ if (controllers != null) {
+ val topActivity = PipUtils.getTopPipActivity(mContext).first
+ if (topActivity != null) {
+ for (i in controllers.indices) {
+ val controller = controllers[i]
+ if (controller.packageName == topActivity.packageName) {
+ setActiveMediaController(controller)
+ return
+ }
+ }
+ }
+ }
+ setActiveMediaController(null)
+ }
+
+ /**
+ * Sets the active media controller for the top PiP activity.
+ */
+ private fun setActiveMediaController(controller: MediaController?) {
+ if (controller != mMediaController) {
+ if (mMediaController != null) {
+ mMediaController!!.unregisterCallback(mPlaybackChangedListener)
+ }
+ mMediaController = controller
+ controller?.registerCallback(mPlaybackChangedListener, mMainHandler)
+ notifyActionsChanged()
+ notifyMetadataChanged(mediaMetadata)
+ notifyTokenChanged(token)
+
+ // TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV)
+ }
+ }
+
+ /**
+ * Notifies all listeners that the actions have changed.
+ */
+ private fun notifyActionsChanged() {
+ if (mActionListeners.isNotEmpty()) {
+ val actions = mediaActions
+ mActionListeners.forEach(
+ Consumer { l: ActionListener -> l.onMediaActionsChanged(actions) })
+ }
+ }
+
+ /**
+ * Notifies all listeners that the metadata have changed.
+ */
+ private fun notifyMetadataChanged(metadata: MediaMetadata?) {
+ if (mMetadataListeners.isNotEmpty()) {
+ mMetadataListeners.forEach(Consumer { l: MetadataListener ->
+ l.onMediaMetadataChanged(
+ metadata
+ )
+ })
+ }
+ }
+
+ private fun notifyTokenChanged(token: MediaSession.Token?) {
+ if (mTokenListeners.isNotEmpty()) {
+ mTokenListeners.forEach(Consumer { l: TokenListener ->
+ l.onMediaSessionTokenChanged(
+ token
+ )
+ })
+ }
+ }
+
+ companion object {
+ private const val SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"
+ private const val ACTION_PLAY = "com.android.wm.shell.pip.PLAY"
+ private const val ACTION_PAUSE = "com.android.wm.shell.pip.PAUSE"
+ private const val ACTION_NEXT = "com.android.wm.shell.pip.NEXT"
+ private const val ACTION_PREV = "com.android.wm.shell.pip.PREV"
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java
index 23153be72890..02b3a8862085 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import android.graphics.Point;
import android.graphics.PointF;
@@ -35,7 +35,7 @@ public class PipPinchResizingAlgorithm {
private final PointF mTmpLastCentroid = new PointF();
/**
- * Updates {@param resizeBoundsOut} with the new bounds of the PIP, and returns the angle in
+ * Updates resizeBoundsOut with the new bounds of the PIP, and returns the angle in
* degrees that the PIP should be rotated.
*/
public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java
index dd30137813e5..007052ee012a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import android.graphics.Rect;
@@ -39,14 +39,14 @@ public class PipSnapAlgorithm {
}
/**
- * @return returns a fraction that describes where along the {@param movementBounds} the
- * {@param stackBounds} are. If the {@param stackBounds} are not currently on the
- * {@param movementBounds} exactly, then they will be snapped to the movement bounds.
+ * @return returns a fraction that describes where along the movementBounds the
+ * stackBounds are. If the stackBounds are not currently on the
+ * movementBounds exactly, then they will be snapped to the movement bounds.
* stashType dictates whether the PiP is stashed (off-screen) or not. If
* that's the case, we will have to do some math to calculate the snap fraction
* correctly.
*
- * The fraction is defined in a clockwise fashion against the {@param movementBounds}:
+ * The fraction is defined in a clockwise fashion against the movementBounds:
*
* 0 1
* 4 +---+ 1
@@ -58,10 +58,10 @@ public class PipSnapAlgorithm {
@PipBoundsState.StashType int stashType) {
final Rect tmpBounds = new Rect();
snapRectToClosestEdge(stackBounds, movementBounds, tmpBounds, stashType);
- final float widthFraction = (float) (tmpBounds.left - movementBounds.left) /
- movementBounds.width();
- final float heightFraction = (float) (tmpBounds.top - movementBounds.top) /
- movementBounds.height();
+ final float widthFraction = (float) (tmpBounds.left - movementBounds.left)
+ / movementBounds.width();
+ final float heightFraction = (float) (tmpBounds.top - movementBounds.top)
+ / movementBounds.height();
if (tmpBounds.top == movementBounds.top) {
return widthFraction;
} else if (tmpBounds.left == movementBounds.right) {
@@ -74,10 +74,10 @@ public class PipSnapAlgorithm {
}
/**
- * Moves the {@param stackBounds} along the {@param movementBounds} to the given snap fraction.
+ * Moves the stackBounds along the movementBounds to the given snap fraction.
* See {@link #getSnapFraction(Rect, Rect)}.
*
- * The fraction is define in a clockwise fashion against the {@param movementBounds}:
+ * The fraction is define in a clockwise fashion against the movementBounds:
*
* 0 1
* 4 +---+ 1
@@ -122,11 +122,11 @@ public class PipSnapAlgorithm {
}
/**
- * Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes
- * the new bounds out to {@param boundsOut}.
+ * Snaps the stackBounds to the closest edge of the movementBounds and writes
+ * the new bounds out to boundsOut.
*/
@VisibleForTesting
- void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
+ public void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
@PipBoundsState.StashType int stashType) {
int leftEdge = stackBounds.left;
if (stashType == STASH_TYPE_LEFT) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUiEventLogger.kt
index 3e5a19b69a59..642dacc425d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUiEventLogger.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,68 +13,59 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.wm.shell.common.pip
-package com.android.wm.shell.pip;
-
-import android.app.TaskInfo;
-import android.content.pm.PackageManager;
-
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
+import android.app.TaskInfo
+import android.content.pm.PackageManager
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
/**
* Helper class that ends PiP log to UiEvent, see also go/uievent
*/
-public class PipUiEventLogger {
-
- private static final int INVALID_PACKAGE_UID = -1;
-
- private final UiEventLogger mUiEventLogger;
- private final PackageManager mPackageManager;
-
- private String mPackageName;
- private int mPackageUid = INVALID_PACKAGE_UID;
-
- public PipUiEventLogger(UiEventLogger uiEventLogger, PackageManager packageManager) {
- mUiEventLogger = uiEventLogger;
- mPackageManager = packageManager;
- }
-
- public void setTaskInfo(TaskInfo taskInfo) {
- if (taskInfo != null && taskInfo.topActivity != null) {
- mPackageName = taskInfo.topActivity.getPackageName();
- mPackageUid = getUid(mPackageName, taskInfo.userId);
+class PipUiEventLogger(
+ private val mUiEventLogger: UiEventLogger,
+ private val mPackageManager: PackageManager
+) {
+ private var mPackageName: String? = null
+ private var mPackageUid = INVALID_PACKAGE_UID
+ fun setTaskInfo(taskInfo: TaskInfo?) {
+ if (taskInfo?.topActivity != null) {
+ // safe because topActivity is guaranteed non-null here
+ mPackageName = taskInfo.topActivity!!.packageName
+ mPackageUid = getUid(mPackageName!!, taskInfo.userId)
} else {
- mPackageName = null;
- mPackageUid = INVALID_PACKAGE_UID;
+ mPackageName = null
+ mPackageUid = INVALID_PACKAGE_UID
}
}
/**
* Sends log via UiEvent, reference go/uievent for how to debug locally
*/
- public void log(PipUiEventEnum event) {
+ fun log(event: PipUiEventEnum?) {
if (mPackageName == null || mPackageUid == INVALID_PACKAGE_UID) {
- return;
+ return
}
- mUiEventLogger.log(event, mPackageUid, mPackageName);
+ mUiEventLogger.log(event!!, mPackageUid, mPackageName)
}
- private int getUid(String packageName, int userId) {
- int uid = INVALID_PACKAGE_UID;
+ private fun getUid(packageName: String, userId: Int): Int {
+ var uid = INVALID_PACKAGE_UID
try {
uid = mPackageManager.getApplicationInfoAsUser(
- packageName, 0 /* ApplicationInfoFlags */, userId).uid;
- } catch (PackageManager.NameNotFoundException e) {
+ packageName, 0 /* ApplicationInfoFlags */, userId
+ ).uid
+ } catch (e: PackageManager.NameNotFoundException) {
// do nothing.
}
- return uid;
+ return uid
}
/**
* Enums for logging the PiP events to UiEvent
*/
- public enum PipUiEventEnum implements UiEventLogger.UiEventEnum {
+ enum class PipUiEventEnum(private val mId: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "Activity enters picture-in-picture mode")
PICTURE_IN_PICTURE_ENTER(603),
@@ -99,8 +90,10 @@ public class PipUiEventLogger {
@UiEvent(doc = "Hides picture-in-picture menu")
PICTURE_IN_PICTURE_HIDE_MENU(608),
- @UiEvent(doc = "Changes the aspect ratio of picture-in-picture window. This is inherited"
- + " from previous Tron-based logging and currently not in use.")
+ @UiEvent(
+ doc = "Changes the aspect ratio of picture-in-picture window. This is inherited" +
+ " from previous Tron-based logging and currently not in use."
+ )
PICTURE_IN_PICTURE_CHANGE_ASPECT_RATIO(609),
@UiEvent(doc = "User resize of the picture-in-picture window")
@@ -121,15 +114,12 @@ public class PipUiEventLogger {
@UiEvent(doc = "Closes PiP with app-provided close action")
PICTURE_IN_PICTURE_CUSTOM_CLOSE(1058);
- private final int mId;
-
- PipUiEventEnum(int id) {
- mId = id;
+ override fun getId(): Int {
+ return mId
}
+ }
- @Override
- public int getId() {
- return mId;
- }
+ companion object {
+ private const val INVALID_PACKAGE_UID = -1
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
new file mode 100644
index 000000000000..84feb03e6a40
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.pip
+
+import android.app.ActivityTaskManager
+import android.app.RemoteAction
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import android.content.Context
+import android.os.RemoteException
+import android.os.SystemProperties
+import android.util.DisplayMetrics
+import android.util.Log
+import android.util.Pair
+import android.util.TypedValue
+import android.window.TaskSnapshot
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import kotlin.math.abs
+
+/** A class that includes convenience methods. */
+object PipUtils {
+ private const val TAG = "PipUtils"
+
+ // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
+ private const val EPSILON = 1e-7
+ private const val ENABLE_PIP2_IMPLEMENTATION = "persist.wm.debug.enable_pip2_implementation"
+
+ /**
+ * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
+ * The component name may be null if no such activity exists.
+ */
+ @JvmStatic
+ fun getTopPipActivity(context: Context): Pair<ComponentName?, Int> {
+ try {
+ val sysUiPackageName = context.packageName
+ val pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo(
+ WindowConfiguration.WINDOWING_MODE_PINNED,
+ WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+ )
+ if (pinnedTaskInfo?.childTaskIds != null && pinnedTaskInfo.childTaskIds.isNotEmpty()) {
+ for (i in pinnedTaskInfo.childTaskNames.indices.reversed()) {
+ val cn = ComponentName.unflattenFromString(
+ pinnedTaskInfo.childTaskNames[i]
+ )
+ if (cn != null && cn.packageName != sysUiPackageName) {
+ return Pair(cn, pinnedTaskInfo.childTaskUserIds[i])
+ }
+ }
+ }
+ } catch (e: RemoteException) {
+ ProtoLog.w(
+ ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Unable to get pinned stack.", TAG
+ )
+ }
+ return Pair(null, 0)
+ }
+
+ /**
+ * @return the pixels for a given dp value.
+ */
+ @JvmStatic
+ fun dpToPx(dpValue: Float, dm: DisplayMetrics?): Int {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, dm).toInt()
+ }
+
+ /**
+ * @return true if the aspect ratios differ
+ */
+ @JvmStatic
+ fun aspectRatioChanged(aspectRatio1: Float, aspectRatio2: Float): Boolean {
+ return abs(aspectRatio1 - aspectRatio2) > EPSILON
+ }
+
+ /**
+ * Checks whether title, description and intent match.
+ * Comparing icons would be good, but using equals causes false negatives
+ */
+ @JvmStatic
+ fun remoteActionsMatch(action1: RemoteAction?, action2: RemoteAction?): Boolean {
+ if (action1 === action2) return true
+ if (action1 == null || action2 == null) return false
+ return action1.isEnabled == action2.isEnabled &&
+ action1.shouldShowIcon() == action2.shouldShowIcon() &&
+ action1.title == action2.title &&
+ action1.contentDescription == action2.contentDescription &&
+ action1.actionIntent == action2.actionIntent
+ }
+
+ /**
+ * Returns true if the actions in the lists match each other according to
+ * [ ][PipUtils.remoteActionsMatch], including their position.
+ */
+ @JvmStatic
+ fun remoteActionsChanged(list1: List<RemoteAction?>?, list2: List<RemoteAction?>?): Boolean {
+ if (list1 == null && list2 == null) {
+ return false
+ }
+ if (list1 == null || list2 == null) {
+ return true
+ }
+ if (list1.size != list2.size) {
+ return true
+ }
+ for (i in list1.indices) {
+ if (!remoteActionsMatch(list1[i], list2[i])) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /** @return [TaskSnapshot] for a given task id.
+ */
+ @JvmStatic
+ fun getTaskSnapshot(taskId: Int, isLowResolution: Boolean): TaskSnapshot? {
+ return if (taskId <= 0) null else try {
+ ActivityTaskManager.getService().getTaskSnapshot(
+ taskId, isLowResolution, false /* takeSnapshotIfNeeded */
+ )
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed to get task snapshot, taskId=$taskId", e)
+ null
+ }
+ }
+
+ @JvmStatic
+ val isPip2ExperimentEnabled: Boolean
+ get() = SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false)
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt
new file mode 100644
index 000000000000..7b3b9efb0de0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.util.Size
+import java.io.PrintWriter
+
+interface SizeSpecSource {
+ /** Returns max size allowed for the PIP window */
+ fun getMaxSize(aspectRatio: Float): Size
+
+ /** Returns default size for the PIP window */
+ fun getDefaultSize(aspectRatio: Float): Size
+
+ /** Returns min size allowed for the PIP window */
+ fun getMinSize(aspectRatio: Float): Size
+
+ /** Returns the adjusted size based on current size and target aspect ratio */
+ fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size
+
+ /** Overrides the minimum pip size requested by the app */
+ fun setOverrideMinSize(overrideMinSize: Size?)
+
+ /** Returns the minimum pip size requested by the app */
+ fun getOverrideMinSize(): Size?
+
+ /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
+ fun getOverrideMinEdgeSize(): Int {
+ val overrideMinSize = getOverrideMinSize() ?: return 0
+ return Math.min(overrideMinSize.width, overrideMinSize.height)
+ }
+
+ fun onConfigurationChanged() {}
+
+ /** Dumps the internal state of the size spec */
+ fun dump(pw: PrintWriter, prefix: String) {}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
index c76937de6669..ec2680085fb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
@@ -76,6 +76,9 @@ public class DividerHandleView extends View {
private int mCurrentHeight;
private AnimatorSet mAnimator;
private boolean mTouching;
+ private boolean mHovering;
+ private final int mHoveringWidth;
+ private final int mHoveringHeight;
public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -87,6 +90,8 @@ public class DividerHandleView extends View {
mCurrentHeight = mHeight;
mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
mTouchingHeight = mHeight > mWidth ? mHeight / 2 : mHeight;
+ mHoveringWidth = mWidth > mHeight ? ((int) (mWidth * 1.5f)) : mWidth;
+ mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
}
/** Sets touching state for this handle view. */
@@ -94,24 +99,32 @@ public class DividerHandleView extends View {
if (touching == mTouching) {
return;
}
+ setInputState(touching, animate, mTouchingWidth, mTouchingHeight);
+ mTouching = touching;
+ }
+
+ /** Sets hovering state for this handle view. */
+ public void setHovering(boolean hovering, boolean animate) {
+ if (hovering == mHovering) {
+ return;
+ }
+ setInputState(hovering, animate, mHoveringWidth, mHoveringHeight);
+ mHovering = hovering;
+ }
+
+ private void setInputState(boolean stateOn, boolean animate, int stateWidth, int stateHeight) {
if (mAnimator != null) {
mAnimator.cancel();
mAnimator = null;
}
if (!animate) {
- if (touching) {
- mCurrentWidth = mTouchingWidth;
- mCurrentHeight = mTouchingHeight;
- } else {
- mCurrentWidth = mWidth;
- mCurrentHeight = mHeight;
- }
+ mCurrentWidth = stateOn ? stateWidth : mWidth;
+ mCurrentHeight = stateOn ? stateHeight : mHeight;
invalidate();
} else {
- animateToTarget(touching ? mTouchingWidth : mWidth,
- touching ? mTouchingHeight : mHeight, touching);
+ animateToTarget(stateOn ? stateWidth : mWidth,
+ stateOn ? stateHeight : mHeight, stateOn);
}
- mTouching = touching;
}
private void animateToTarget(int targetWidth, int targetHeight, boolean touching) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
new file mode 100644
index 000000000000..1901e0bbe700
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.split;
+
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Calculates the snap targets and the snap position given a position and a velocity. All positions
+ * here are to be interpreted as the left/top edge of the divider rectangle.
+ *
+ * @hide
+ */
+public class DividerSnapAlgorithm {
+
+ private static final int MIN_FLING_VELOCITY_DP_PER_SECOND = 400;
+ private static final int MIN_DISMISS_VELOCITY_DP_PER_SECOND = 600;
+
+ /**
+ * 3 snap targets: left/top has 16:9 ratio (for videos), 1:1, and right/bottom has 16:9 ratio
+ */
+ private static final int SNAP_MODE_16_9 = 0;
+
+ /**
+ * 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
+ */
+ private static final int SNAP_FIXED_RATIO = 1;
+
+ /**
+ * 1 snap target: 1:1
+ */
+ private static final int SNAP_ONLY_1_1 = 2;
+
+ /**
+ * 1 snap target: minimized height, (1 - minimized height)
+ */
+ private static final int SNAP_MODE_MINIMIZED = 3;
+
+ private final float mMinFlingVelocityPxPerSecond;
+ private final float mMinDismissVelocityPxPerSecond;
+ private final int mDisplayWidth;
+ private final int mDisplayHeight;
+ private final int mDividerSize;
+ private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
+ private final Rect mInsets = new Rect();
+ private final int mSnapMode;
+ private final boolean mFreeSnapMode;
+ private final int mMinimalSizeResizableTask;
+ private final int mTaskHeightInMinimizedMode;
+ private final float mFixedRatio;
+ private boolean mIsHorizontalDivision;
+
+ /** The first target which is still splitting the screen */
+ private final SnapTarget mFirstSplitTarget;
+
+ /** The last target which is still splitting the screen */
+ private final SnapTarget mLastSplitTarget;
+
+ private final SnapTarget mDismissStartTarget;
+ private final SnapTarget mDismissEndTarget;
+ private final SnapTarget mMiddleTarget;
+
+ public static DividerSnapAlgorithm create(Context ctx, Rect insets) {
+ DisplayInfo displayInfo = new DisplayInfo();
+ ctx.getSystemService(DisplayManager.class).getDisplay(
+ Display.DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
+ int dividerWindowWidth = ctx.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_thickness);
+ int dividerInsets = ctx.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_insets);
+ return new DividerSnapAlgorithm(ctx.getResources(),
+ displayInfo.logicalWidth, displayInfo.logicalHeight,
+ dividerWindowWidth - 2 * dividerInsets,
+ ctx.getApplicationContext().getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_PORTRAIT,
+ insets);
+ }
+
+ public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
+ boolean isHorizontalDivision, Rect insets) {
+ this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
+ DOCKED_INVALID, false /* minimized */, true /* resizable */);
+ }
+
+ public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
+ boolean isHorizontalDivision, Rect insets, int dockSide) {
+ this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
+ dockSide, false /* minimized */, true /* resizable */);
+ }
+
+ public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
+ boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode,
+ boolean isHomeResizable) {
+ mMinFlingVelocityPxPerSecond =
+ MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
+ mMinDismissVelocityPxPerSecond =
+ MIN_DISMISS_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
+ mDividerSize = dividerSize;
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ mIsHorizontalDivision = isHorizontalDivision;
+ mInsets.set(insets);
+ mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED :
+ res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode);
+ mFreeSnapMode = res.getBoolean(
+ com.android.internal.R.bool.config_dockedStackDividerFreeSnapMode);
+ mFixedRatio = res.getFraction(
+ com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
+ mMinimalSizeResizableTask = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.default_minimal_size_resizable_task);
+ mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize(
+ com.android.internal.R.dimen.task_height_of_minimized_mode) : 0;
+ calculateTargets(isHorizontalDivision, dockSide);
+ mFirstSplitTarget = mTargets.get(1);
+ mLastSplitTarget = mTargets.get(mTargets.size() - 2);
+ mDismissStartTarget = mTargets.get(0);
+ mDismissEndTarget = mTargets.get(mTargets.size() - 1);
+ mMiddleTarget = mTargets.get(mTargets.size() / 2);
+ mMiddleTarget.isMiddleTarget = true;
+ }
+
+ /**
+ * @return whether it's feasible to enable split screen in the current configuration, i.e. when
+ * snapping in the middle both tasks are larger than the minimal task size.
+ */
+ public boolean isSplitScreenFeasible() {
+ int statusBarSize = mInsets.top;
+ int navBarSize = mIsHorizontalDivision ? mInsets.bottom : mInsets.right;
+ int size = mIsHorizontalDivision
+ ? mDisplayHeight
+ : mDisplayWidth;
+ int availableSpace = size - navBarSize - statusBarSize - mDividerSize;
+ return availableSpace / 2 >= mMinimalSizeResizableTask;
+ }
+
+ public SnapTarget calculateSnapTarget(int position, float velocity) {
+ return calculateSnapTarget(position, velocity, true /* hardDismiss */);
+ }
+
+ /**
+ * @param position the top/left position of the divider
+ * @param velocity current dragging velocity
+ * @param hardDismiss if set, make it a bit harder to get reach the dismiss targets
+ */
+ public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardDismiss) {
+ if (position < mFirstSplitTarget.position && velocity < -mMinDismissVelocityPxPerSecond) {
+ return mDismissStartTarget;
+ }
+ if (position > mLastSplitTarget.position && velocity > mMinDismissVelocityPxPerSecond) {
+ return mDismissEndTarget;
+ }
+ if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
+ return snap(position, hardDismiss);
+ }
+ if (velocity < 0) {
+ return mFirstSplitTarget;
+ } else {
+ return mLastSplitTarget;
+ }
+ }
+
+ public SnapTarget calculateNonDismissingSnapTarget(int position) {
+ SnapTarget target = snap(position, false /* hardDismiss */);
+ if (target == mDismissStartTarget) {
+ return mFirstSplitTarget;
+ } else if (target == mDismissEndTarget) {
+ return mLastSplitTarget;
+ } else {
+ return target;
+ }
+ }
+
+ public float calculateDismissingFraction(int position) {
+ if (position < mFirstSplitTarget.position) {
+ return 1f - (float) (position - getStartInset())
+ / (mFirstSplitTarget.position - getStartInset());
+ } else if (position > mLastSplitTarget.position) {
+ return (float) (position - mLastSplitTarget.position)
+ / (mDismissEndTarget.position - mLastSplitTarget.position - mDividerSize);
+ }
+ return 0f;
+ }
+
+ public SnapTarget getClosestDismissTarget(int position) {
+ if (position < mFirstSplitTarget.position) {
+ return mDismissStartTarget;
+ } else if (position > mLastSplitTarget.position) {
+ return mDismissEndTarget;
+ } else if (position - mDismissStartTarget.position
+ < mDismissEndTarget.position - position) {
+ return mDismissStartTarget;
+ } else {
+ return mDismissEndTarget;
+ }
+ }
+
+ public SnapTarget getFirstSplitTarget() {
+ return mFirstSplitTarget;
+ }
+
+ public SnapTarget getLastSplitTarget() {
+ return mLastSplitTarget;
+ }
+
+ public SnapTarget getDismissStartTarget() {
+ return mDismissStartTarget;
+ }
+
+ public SnapTarget getDismissEndTarget() {
+ return mDismissEndTarget;
+ }
+
+ private int getStartInset() {
+ if (mIsHorizontalDivision) {
+ return mInsets.top;
+ } else {
+ return mInsets.left;
+ }
+ }
+
+ private int getEndInset() {
+ if (mIsHorizontalDivision) {
+ return mInsets.bottom;
+ } else {
+ return mInsets.right;
+ }
+ }
+
+ private boolean shouldApplyFreeSnapMode(int position) {
+ if (!mFreeSnapMode) {
+ return false;
+ }
+ if (!isFirstSplitTargetAvailable() || !isLastSplitTargetAvailable()) {
+ return false;
+ }
+ return mFirstSplitTarget.position < position && position < mLastSplitTarget.position;
+ }
+
+ private SnapTarget snap(int position, boolean hardDismiss) {
+ if (shouldApplyFreeSnapMode(position)) {
+ return new SnapTarget(position, position, SnapTarget.FLAG_NONE);
+ }
+ int minIndex = -1;
+ float minDistance = Float.MAX_VALUE;
+ int size = mTargets.size();
+ for (int i = 0; i < size; i++) {
+ SnapTarget target = mTargets.get(i);
+ float distance = Math.abs(position - target.position);
+ if (hardDismiss) {
+ distance /= target.distanceMultiplier;
+ }
+ if (distance < minDistance) {
+ minIndex = i;
+ minDistance = distance;
+ }
+ }
+ return mTargets.get(minIndex);
+ }
+
+ private void calculateTargets(boolean isHorizontalDivision, int dockedSide) {
+ mTargets.clear();
+ int dividerMax = isHorizontalDivision
+ ? mDisplayHeight
+ : mDisplayWidth;
+ int startPos = -mDividerSize;
+ if (dockedSide == DOCKED_RIGHT) {
+ startPos += mInsets.left;
+ }
+ mTargets.add(new SnapTarget(startPos, startPos, SnapTarget.FLAG_DISMISS_START,
+ 0.35f));
+ switch (mSnapMode) {
+ case SNAP_MODE_16_9:
+ addRatio16_9Targets(isHorizontalDivision, dividerMax);
+ break;
+ case SNAP_FIXED_RATIO:
+ addFixedDivisionTargets(isHorizontalDivision, dividerMax);
+ break;
+ case SNAP_ONLY_1_1:
+ addMiddleTarget(isHorizontalDivision);
+ break;
+ case SNAP_MODE_MINIMIZED:
+ addMinimizedTarget(isHorizontalDivision, dockedSide);
+ break;
+ }
+ mTargets.add(new SnapTarget(dividerMax, dividerMax, SnapTarget.FLAG_DISMISS_END, 0.35f));
+ }
+
+ private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
+ int bottomPosition, int dividerMax) {
+ maybeAddTarget(topPosition, topPosition - getStartInset());
+ addMiddleTarget(isHorizontalDivision);
+ maybeAddTarget(bottomPosition,
+ dividerMax - getEndInset() - (bottomPosition + mDividerSize));
+ }
+
+ private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
+ int start = isHorizontalDivision ? mInsets.top : mInsets.left;
+ int end = isHorizontalDivision
+ ? mDisplayHeight - mInsets.bottom
+ : mDisplayWidth - mInsets.right;
+ int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
+ int topPosition = start + size;
+ int bottomPosition = end - size - mDividerSize;
+ addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax);
+ }
+
+ private void addRatio16_9Targets(boolean isHorizontalDivision, int dividerMax) {
+ int start = isHorizontalDivision ? mInsets.top : mInsets.left;
+ int end = isHorizontalDivision
+ ? mDisplayHeight - mInsets.bottom
+ : mDisplayWidth - mInsets.right;
+ int startOther = isHorizontalDivision ? mInsets.left : mInsets.top;
+ int endOther = isHorizontalDivision
+ ? mDisplayWidth - mInsets.right
+ : mDisplayHeight - mInsets.bottom;
+ float size = 9.0f / 16.0f * (endOther - startOther);
+ int sizeInt = (int) Math.floor(size);
+ int topPosition = start + sizeInt;
+ int bottomPosition = end - sizeInt - mDividerSize;
+ addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax);
+ }
+
+ /**
+ * Adds a target at {@param position} but only if the area with size of {@param smallerSize}
+ * meets the minimal size requirement.
+ */
+ private void maybeAddTarget(int position, int smallerSize) {
+ if (smallerSize >= mMinimalSizeResizableTask) {
+ mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ }
+ }
+
+ private void addMiddleTarget(boolean isHorizontalDivision) {
+ int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+ mInsets, mDisplayWidth, mDisplayHeight, mDividerSize);
+ mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ }
+
+ private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
+ // In portrait offset the position by the statusbar height, in landscape add the statusbar
+ // height as well to match portrait offset
+ int position = mTaskHeightInMinimizedMode + mInsets.top;
+ if (!isHorizontalDivision) {
+ if (dockedSide == DOCKED_LEFT) {
+ position += mInsets.left;
+ } else if (dockedSide == DOCKED_RIGHT) {
+ position = mDisplayWidth - position - mInsets.right - mDividerSize;
+ }
+ }
+ mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ }
+
+ public SnapTarget getMiddleTarget() {
+ return mMiddleTarget;
+ }
+
+ public SnapTarget getNextTarget(SnapTarget snapTarget) {
+ int index = mTargets.indexOf(snapTarget);
+ if (index != -1 && index < mTargets.size() - 1) {
+ return mTargets.get(index + 1);
+ }
+ return snapTarget;
+ }
+
+ public SnapTarget getPreviousTarget(SnapTarget snapTarget) {
+ int index = mTargets.indexOf(snapTarget);
+ if (index != -1 && index > 0) {
+ return mTargets.get(index - 1);
+ }
+ return snapTarget;
+ }
+
+ /**
+ * @return whether or not there are more than 1 split targets that do not include the two
+ * dismiss targets, used in deciding to display the middle target for accessibility
+ */
+ public boolean showMiddleSplitTargetForAccessibility() {
+ return (mTargets.size() - 2) > 1;
+ }
+
+ public boolean isFirstSplitTargetAvailable() {
+ return mFirstSplitTarget != mMiddleTarget;
+ }
+
+ public boolean isLastSplitTargetAvailable() {
+ return mLastSplitTarget != mMiddleTarget;
+ }
+
+ /**
+ * Cycles through all non-dismiss targets with a stepping of {@param increment}. It moves left
+ * if {@param increment} is negative and moves right otherwise.
+ */
+ public SnapTarget cycleNonDismissTarget(SnapTarget snapTarget, int increment) {
+ int index = mTargets.indexOf(snapTarget);
+ if (index != -1) {
+ SnapTarget newTarget = mTargets.get((index + mTargets.size() + increment)
+ % mTargets.size());
+ if (newTarget == mDismissStartTarget) {
+ return mLastSplitTarget;
+ } else if (newTarget == mDismissEndTarget) {
+ return mFirstSplitTarget;
+ } else {
+ return newTarget;
+ }
+ }
+ return snapTarget;
+ }
+
+ /**
+ * Represents a snap target for the divider.
+ */
+ public static class SnapTarget {
+ public static final int FLAG_NONE = 0;
+
+ /** If the divider reaches this value, the left/top task should be dismissed. */
+ public static final int FLAG_DISMISS_START = 1;
+
+ /** If the divider reaches this value, the right/bottom task should be dismissed */
+ public static final int FLAG_DISMISS_END = 2;
+
+ /** Position of this snap target. The right/bottom edge of the top/left task snaps here. */
+ public final int position;
+
+ /**
+ * Like {@link #position}, but used to calculate the task bounds which might be different
+ * from the stack bounds.
+ */
+ public final int taskPosition;
+
+ public final int flag;
+
+ public boolean isMiddleTarget;
+
+ /**
+ * Multiplier used to calculate distance to snap position. The lower this value, the harder
+ * it's to snap on this target
+ */
+ private final float distanceMultiplier;
+
+ public SnapTarget(int position, int taskPosition, int flag) {
+ this(position, taskPosition, flag, 1f);
+ }
+
+ public SnapTarget(int position, int taskPosition, int flag, float distanceMultiplier) {
+ this.position = position;
+ this.taskPosition = taskPosition;
+ this.flag = flag;
+ this.distanceMultiplier = distanceMultiplier;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 69f0bad4fb45..0b0c6937553b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -17,14 +17,19 @@
package com.android.wm.shell.common.split;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
+import android.provider.DeviceConfig;
import android.util.AttributeSet;
import android.util.Property;
import android.view.GestureDetector;
@@ -32,6 +37,7 @@ import android.view.InsetsController;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.MotionEvent;
+import android.view.PointerIcon;
import android.view.SurfaceControlViewHost;
import android.view.VelocityTracker;
import android.view.View;
@@ -46,7 +52,7 @@ import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
@@ -222,7 +228,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = insetsState.sourceAt(i);
if (source.getType() == WindowInsets.Type.navigationBars()
- && source.insetsRoundedCornerFrame()) {
+ && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
mTempRect.inset(source.calculateVisibleInsets(mTempRect));
}
}
@@ -270,6 +276,12 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
}
@Override
+ public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
+ return PointerIcon.getSystemIcon(getContext(),
+ isLandscape() ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW);
+ }
+
+ @Override
public boolean onTouch(View v, MotionEvent event) {
if (mSplitLayout == null || !mInteractive) {
return false;
@@ -371,6 +383,43 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
mViewHost.relayout(lp);
}
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
+ /* defaultValue = */ false)) {
+ return false;
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+ setHovering();
+ return true;
+ } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
+ releaseHovering();
+ return true;
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ void setHovering() {
+ mHandle.setHovering(true, true);
+ mHandle.animate()
+ .setInterpolator(Interpolators.TOUCH_RESPONSE)
+ .setDuration(TOUCH_ANIMATION_DURATION)
+ .translationZ(mTouchElevation)
+ .start();
+ }
+
+ @VisibleForTesting
+ void releaseHovering() {
+ mHandle.setHovering(false, true);
+ mHandle.animate()
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
+ .translationZ(0)
+ .start();
+ }
+
/**
* Set divider should interactive to user or not.
*
@@ -384,7 +433,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
"Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
from);
mInteractive = interactive;
- if (!mInteractive && mMoving) {
+ if (!mInteractive && hideHandle && mMoving) {
final int position = mSplitLayout.getDividePosition();
mSplitLayout.flingDividePosition(
mLastDraggingPosition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
new file mode 100644
index 000000000000..f25dfeafb32c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.split;
+
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import android.content.res.Resources;
+import android.graphics.Rect;
+
+/**
+ * Utility functions for docked stack divider used by both window manager and System UI.
+ *
+ * @hide
+ */
+public class DockedDividerUtils {
+
+ public static void calculateBoundsForPosition(int position, int dockSide, Rect outRect,
+ int displayWidth, int displayHeight, int dividerSize) {
+ outRect.set(0, 0, displayWidth, displayHeight);
+ switch (dockSide) {
+ case DOCKED_LEFT:
+ outRect.right = position;
+ break;
+ case DOCKED_TOP:
+ outRect.bottom = position;
+ break;
+ case DOCKED_RIGHT:
+ outRect.left = position + dividerSize;
+ break;
+ case DOCKED_BOTTOM:
+ outRect.top = position + dividerSize;
+ break;
+ }
+ sanitizeStackBounds(outRect, dockSide == DOCKED_LEFT || dockSide == DOCKED_TOP);
+ }
+
+ /**
+ * Makes sure that the bounds are always valid, i. e. they are at least one pixel high and wide.
+ *
+ * @param bounds The bounds to sanitize.
+ * @param topLeft Pass true if the bounds are at the top/left of the screen, false if they are
+ * at the bottom/right. This is used to determine in which direction to extend
+ * the bounds.
+ */
+ public static void sanitizeStackBounds(Rect bounds, boolean topLeft) {
+
+ // If the bounds are either on the top or left of the screen, rather move it further to the
+ // left/top to make it more offscreen. If they are on the bottom or right, push them off the
+ // screen by moving it even more to the bottom/right.
+ if (topLeft) {
+ if (bounds.left >= bounds.right) {
+ bounds.left = bounds.right - 1;
+ }
+ if (bounds.top >= bounds.bottom) {
+ bounds.top = bounds.bottom - 1;
+ }
+ } else {
+ if (bounds.right <= bounds.left) {
+ bounds.right = bounds.left + 1;
+ }
+ if (bounds.bottom <= bounds.top) {
+ bounds.bottom = bounds.top + 1;
+ }
+ }
+ }
+
+ public static int calculatePositionForBounds(Rect bounds, int dockSide, int dividerSize) {
+ switch (dockSide) {
+ case DOCKED_LEFT:
+ return bounds.right;
+ case DOCKED_TOP:
+ return bounds.bottom;
+ case DOCKED_RIGHT:
+ return bounds.left - dividerSize;
+ case DOCKED_BOTTOM:
+ return bounds.top - dividerSize;
+ default:
+ return 0;
+ }
+ }
+
+ public static int calculateMiddlePosition(boolean isHorizontalDivision, Rect insets,
+ int displayWidth, int displayHeight, int dividerSize) {
+ int start = isHorizontalDivision ? insets.top : insets.left;
+ int end = isHorizontalDivision
+ ? displayHeight - insets.bottom
+ : displayWidth - insets.right;
+ return start + (end - start) / 2 - dividerSize / 2;
+ }
+
+ public static int invertDockSide(int dockSide) {
+ switch (dockSide) {
+ case DOCKED_LEFT:
+ return DOCKED_RIGHT;
+ case DOCKED_TOP:
+ return DOCKED_BOTTOM;
+ case DOCKED_RIGHT:
+ return DOCKED_LEFT;
+ case DOCKED_BOTTOM:
+ return DOCKED_TOP;
+ default:
+ return DOCKED_INVALID;
+ }
+ }
+
+ /** Returns the inset distance from the divider window edge to the dividerview. */
+ public static int getDividerInsets(Resources res) {
+ return res.getDimensionPixelSize(com.android.internal.R.dimen.docked_stack_divider_insets);
+ }
+
+ /** Returns the size of the divider */
+ public static int getDividerSize(Resources res, int dividerInsets) {
+ final int windowWidth = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_thickness);
+ return windowWidth - 2 * dividerInsets;
+ }
+
+ /** Returns the docked-stack side */
+ public static int getDockSide(int displayWidth, int displayHeight) {
+ return displayWidth > displayHeight ? DOCKED_LEFT : DOCKED_TOP;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index a9ccdf6a156f..2b1037711249 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -323,7 +323,11 @@ public class SplitDecorManager extends WindowlessWindowManager {
}
}
if (mShown) {
- fadeOutDecor(()-> animFinishedCallback.accept(true));
+ fadeOutDecor(()-> {
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.accept(true);
+ }
+ });
} else {
// Decor surface is hidden so release it directly.
releaseDecor(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f70d3aec9ec8..755dba0c895f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -24,9 +24,10 @@ import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -58,8 +59,6 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.internal.policy.DockedDividerUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
@@ -593,9 +592,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
void flingDividePosition(int from, int to, int duration,
@Nullable Runnable flingFinishedCallback) {
if (from == to) {
- // No animation run, still callback to stop resizing.
- mSplitLayoutHandler.onLayoutSizeChanged(this);
-
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
@@ -666,10 +662,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
set.setDuration(FLING_SWITCH_DURATION);
set.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationStart(Animator animation) {
+ InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
+ mContext, getDividerLeash(), null /*tag*/);
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
mDividePosition = dividerPos;
updateBounds(mDividePosition);
finishCallback.accept(insets);
+ InteractionJankMonitorUtils.endTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
});
set.start();
@@ -725,10 +733,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return bounds.width() > bounds.height();
}
- public boolean isDensityChanged(int densityDpi) {
- return mDensity != densityDpi;
- }
-
/**
* Return if this layout is landscape.
*/
@@ -773,15 +777,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
boolean boundsChanged = false;
if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
- wct.setBounds(task1.token, mBounds1);
- wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
+ setTaskBounds(wct, task1, mBounds1);
mWinBounds1.set(mBounds1);
mWinToken1 = task1.token;
boundsChanged = true;
}
if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
- wct.setBounds(task2.token, mBounds2);
- wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
+ setTaskBounds(wct, task2, mBounds2);
mWinBounds2.set(mBounds2);
mWinToken2 = task2.token;
boundsChanged = true;
@@ -789,6 +791,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return boundsChanged;
}
+ /** Set bounds to the {@link WindowContainerTransaction} for single task. */
+ public void setTaskBounds(WindowContainerTransaction wct,
+ ActivityManager.RunningTaskInfo task, Rect bounds) {
+ wct.setBounds(task.token, bounds);
+ wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds));
+ }
+
private int getSmallestWidthDp(Rect bounds) {
mTempRect.set(bounds);
mTempRect.inset(getDisplayStableInsets(mContext));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index ef93a336305f..be1b9b1227de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.common.split;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+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;
@@ -60,7 +61,8 @@ public class SplitScreenConstants {
public static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
public static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
- {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW,
+ WINDOWING_MODE_FREEFORM};
/** Flag applied to a transition change to identify it as a divider bar for animation. */
public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 0289da916937..d7ea1c0c620d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -89,4 +89,9 @@ public class SplitScreenUtils {
int userId1, int userId2) {
return (packageName1 != null && packageName1.equals(packageName2)) && (userId1 == userId2);
}
+
+ /** Generates a common log message for split screen failures */
+ public static String splitFailureMessage(String caller, String reason) {
+ return "(" + caller + ") Splitscreen aborted: " + reason;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 62b0799618ac..953efa78326c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -16,12 +16,20 @@
package com.android.wm.shell.compatui;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
import android.app.TaskInfo.CameraCompatControlState;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -29,6 +37,7 @@ import android.util.SparseArray;
import android.view.Display;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.accessibility.AccessibilityManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -41,7 +50,6 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -55,6 +63,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -76,6 +85,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
private static final String TAG = "CompatUIController";
+ // The time to wait before education and button hiding
+ private static final int DISAPPEAR_DELAY_MS = 5000;
+
/** Whether the IME is shown on display id. */
private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1);
@@ -104,6 +116,13 @@ public class CompatUIController implements OnDisplaysChangedListener,
private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>();
/**
+ * The active user aspect ratio settings button layout if there is one (there can be at most
+ * one active).
+ */
+ @Nullable
+ private UserAspectRatioSettingsWindowManager mUserAspectRatioSettingsLayout;
+
+ /**
* The active Letterbox Education layout if there is one (there can be at most one active).
*
* <p>An active layout is a layout that is eligible to be shown for the associated task but
@@ -121,38 +140,67 @@ public class CompatUIController implements OnDisplaysChangedListener,
/** Avoid creating display context frequently for non-default display. */
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
+ @NonNull
private final Context mContext;
+ @NonNull
private final ShellController mShellController;
+ @NonNull
private final DisplayController mDisplayController;
+ @NonNull
private final DisplayInsetsController mDisplayInsetsController;
+ @NonNull
private final DisplayImeController mImeController;
+ @NonNull
private final SyncTransactionQueue mSyncQueue;
+ @NonNull
private final ShellExecutor mMainExecutor;
+ @NonNull
private final Lazy<Transitions> mTransitionsLazy;
+ @NonNull
private final DockStateReader mDockStateReader;
+ @NonNull
private final CompatUIConfiguration mCompatUIConfiguration;
// Only show each hint once automatically in the process life.
+ @NonNull
private final CompatUIHintsState mCompatUIHintsState;
+ @NonNull
private final CompatUIShellCommandHandler mCompatUIShellCommandHandler;
- private CompatUICallback mCallback;
+ @NonNull
+ private final Function<Integer, Integer> mDisappearTimeSupplier;
+
+ @Nullable
+ private CompatUICallback mCompatUICallback;
// Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
// be shown.
private boolean mKeyguardShowing;
- public CompatUIController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- DisplayController displayController,
- DisplayInsetsController displayInsetsController,
- DisplayImeController imeController,
- SyncTransactionQueue syncQueue,
- ShellExecutor mainExecutor,
- Lazy<Transitions> transitionsLazy,
- DockStateReader dockStateReader,
- CompatUIConfiguration compatUIConfiguration,
- CompatUIShellCommandHandler compatUIShellCommandHandler) {
+ /**
+ * The id of the task for the application we're currently attempting to show the user aspect
+ * ratio settings button for, or have most recently shown the button for.
+ */
+ private int mTopActivityTaskId;
+
+ /**
+ * Whether the user aspect ratio settings button has been shown for the current application
+ * associated with the task id stored in {@link CompatUIController#mTopActivityTaskId}.
+ */
+ private boolean mHasShownUserAspectRatioSettingsButton = false;
+
+ public CompatUIController(@NonNull Context context,
+ @NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
+ @NonNull DisplayController displayController,
+ @NonNull DisplayInsetsController displayInsetsController,
+ @NonNull DisplayImeController imeController,
+ @NonNull SyncTransactionQueue syncQueue,
+ @NonNull ShellExecutor mainExecutor,
+ @NonNull Lazy<Transitions> transitionsLazy,
+ @NonNull DockStateReader dockStateReader,
+ @NonNull CompatUIConfiguration compatUIConfiguration,
+ @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
+ @NonNull AccessibilityManager accessibilityManager) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -165,6 +213,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
mDockStateReader = dockStateReader;
mCompatUIConfiguration = compatUIConfiguration;
mCompatUIShellCommandHandler = compatUIShellCommandHandler;
+ mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
+ DISAPPEAR_DELAY_MS, flags);
shellInit.addInitCallback(this::onInit, this);
}
@@ -175,9 +225,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
mCompatUIShellCommandHandler.onInit();
}
- /** Sets the callback for UI interactions. */
- public void setCompatUICallback(CompatUICallback callback) {
- mCallback = callback;
+ /** Sets the callback for Compat UI interactions. */
+ public void setCompatUICallback(@NonNull CompatUICallback compatUiCallback) {
+ mCompatUICallback = compatUiCallback;
}
/**
@@ -187,11 +237,16 @@ public class CompatUIController implements OnDisplaysChangedListener,
* @param taskInfo {@link TaskInfo} task the activity is in.
* @param taskListener listener to handle the Task Surface placement.
*/
- public void onCompatInfoChanged(TaskInfo taskInfo,
+ public void onCompatInfoChanged(@NonNull TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (taskInfo != null && !taskInfo.topActivityInSizeCompat) {
mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
}
+
+ if (taskInfo != null && taskListener != null) {
+ updateActiveTaskInfo(taskInfo);
+ }
+
if (taskInfo.configuration == null || taskListener == null) {
// Null token means the current foreground activity is not in compatibility mode.
removeLayouts(taskInfo.taskId);
@@ -203,6 +258,18 @@ public class CompatUIController implements OnDisplaysChangedListener,
createOrUpdateRestartDialogLayout(taskInfo, taskListener);
if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+ // The user aspect ratio button should not be handled when a new TaskInfo is
+ // sent because of a double tap or when in multi-window mode.
+ if (taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ if (mUserAspectRatioSettingsLayout != null) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ }
+ return;
+ }
+ if (!taskInfo.isFromLetterboxDoubleTap) {
+ createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
+ }
}
}
@@ -272,6 +339,46 @@ public class CompatUIController implements OnDisplaysChangedListener,
forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId())));
}
+ /**
+ * Invoked when a new task is created or the info of an existing task has changed. Updates the
+ * shown status of the user aspect ratio settings button and the task id it relates to.
+ */
+ void updateActiveTaskInfo(@NonNull TaskInfo taskInfo) {
+ // If the activity belongs to the task we are currently tracking, don't update any variables
+ // as they are still relevant. Else, if the activity is visible and focused (the one the
+ // user can see and is using), the user aspect ratio button can potentially be displayed so
+ // start tracking the buttons visibility for this task.
+ if (mTopActivityTaskId != taskInfo.taskId && !taskInfo.isTopActivityTransparent
+ && taskInfo.isVisible && taskInfo.isFocused) {
+ mTopActivityTaskId = taskInfo.taskId;
+ setHasShownUserAspectRatioSettingsButton(false);
+ }
+ }
+
+ /**
+ * Informs the system that the user aspect ratio button has been displayed for the application
+ * associated with the task id in {@link CompatUIController#mTopActivityTaskId}.
+ */
+ void setHasShownUserAspectRatioSettingsButton(boolean state) {
+ mHasShownUserAspectRatioSettingsButton = state;
+ }
+
+ /**
+ * Returns whether the user aspect ratio settings button has been show for the application
+ * associated with the task id in {@link CompatUIController#mTopActivityTaskId}.
+ */
+ boolean hasShownUserAspectRatioSettingsButton() {
+ return mHasShownUserAspectRatioSettingsButton;
+ }
+
+ /**
+ * Returns the task id of the application we are currently attempting to show, of have most
+ * recently shown, the user aspect ratio settings button for.
+ */
+ int getTopActivityTaskId() {
+ return mTopActivityTaskId;
+ }
+
private boolean showOnDisplay(int displayId) {
return !mKeyguardShowing && !isImeShowingOnDisplay(displayId);
}
@@ -280,8 +387,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
return mDisplaysWithIme.contains(displayId);
}
- private void createOrUpdateCompatLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateCompatLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId);
if (layout != null) {
if (layout.needsToBeRecreated(taskInfo, taskListener)) {
@@ -314,7 +421,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
return new CompatUIWindowManager(context,
- taskInfo, mSyncQueue, mCallback, taskListener,
+ taskInfo, mSyncQueue, mCompatUICallback, taskListener,
mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState,
mCompatUIConfiguration, this::onRestartButtonClicked);
}
@@ -328,12 +435,12 @@ public class CompatUIController implements OnDisplaysChangedListener,
mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
onCompatInfoChanged(taskInfoState.first, taskInfoState.second);
} else {
- mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
+ mCompatUICallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
}
}
- private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateLetterboxEduLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mActiveLetterboxEduLayout != null) {
if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
mActiveLetterboxEduLayout.release();
@@ -342,6 +449,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
if (!mActiveLetterboxEduLayout.updateCompatInfo(taskInfo, taskListener,
showOnDisplay(mActiveLetterboxEduLayout.getDisplayId()))) {
// The layout is no longer eligible to be shown, clear active layout.
+ mActiveLetterboxEduLayout.release();
mActiveLetterboxEduLayout = null;
}
return;
@@ -371,19 +479,13 @@ public class CompatUIController implements OnDisplaysChangedListener,
ShellTaskOrganizer.TaskListener taskListener) {
return new LetterboxEduWindowManager(context, taskInfo,
mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
- mTransitionsLazy.get(), this::onLetterboxEduDismissed, mDockStateReader,
- mCompatUIConfiguration);
+ mTransitionsLazy.get(),
+ stateInfo -> createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second),
+ mDockStateReader, mCompatUIConfiguration);
}
- private void onLetterboxEduDismissed(
- Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
- mActiveLetterboxEduLayout = null;
- // We need to update the UI
- createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second);
- }
-
- private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateRestartDialogLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
RestartDialogWindowManager layout =
mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
if (layout != null) {
@@ -428,7 +530,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
private void onRestartDialogCallback(
Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId);
- mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
+ mCompatUICallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
}
private void onRestartDialogDismissCallback(
@@ -437,8 +539,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
onCompatInfoChanged(stateInfo.first, stateInfo.second);
}
- private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo,
- ShellTaskOrganizer.TaskListener taskListener) {
+ private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mActiveReachabilityEduLayout != null) {
if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
mActiveReachabilityEduLayout.release();
@@ -448,6 +550,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener,
showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) {
// The layout is no longer eligible to be shown, remove from active layouts.
+ mActiveReachabilityEduLayout.release();
mActiveReachabilityEduLayout = null;
}
return;
@@ -478,14 +581,76 @@ public class CompatUIController implements OnDisplaysChangedListener,
ShellTaskOrganizer.TaskListener taskListener) {
return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue,
taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
- mCompatUIConfiguration, mMainExecutor);
+ mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed,
+ mDisappearTimeSupplier);
+ }
+
+ private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo,
+ @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+ // We need to update the UI otherwise it will not be shown until the user relaunches the app
+ createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
+ }
+
+ private void createOrUpdateUserAspectRatioSettingsLayout(@NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ if (mUserAspectRatioSettingsLayout != null) {
+ if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ } else {
+ // UI already exists, update the UI layout.
+ if (!mUserAspectRatioSettingsLayout.updateCompatInfo(taskInfo, taskListener,
+ showOnDisplay(mUserAspectRatioSettingsLayout.getDisplayId()))) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ }
+ return;
+ }
+ }
+
+ // Create a new UI layout.
+ final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+ if (context == null) {
+ return;
+ }
+ final UserAspectRatioSettingsWindowManager newLayout =
+ createUserAspectRatioSettingsWindowManager(context, taskInfo, taskListener);
+ if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) {
+ // The new layout is eligible to be shown, add it the active layouts.
+ mUserAspectRatioSettingsLayout = newLayout;
+ }
+ }
+
+ @VisibleForTesting
+ @NonNull
+ UserAspectRatioSettingsWindowManager createUserAspectRatioSettingsWindowManager(
+ @NonNull Context context, @NonNull TaskInfo taskInfo,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ return new UserAspectRatioSettingsWindowManager(context, taskInfo, mSyncQueue,
+ taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
+ mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor,
+ mDisappearTimeSupplier, this::hasShownUserAspectRatioSettingsButton,
+ this::setHasShownUserAspectRatioSettingsButton);
}
+ private void launchUserAspectRatioSettings(
+ @NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+ final Intent intent = new Intent(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ final ComponentName appComponent = taskInfo.topActivity;
+ if (appComponent != null) {
+ final Uri packageUri = Uri.parse("package:" + appComponent.getPackageName());
+ intent.setData(packageUri);
+ }
+ final UserHandle userHandle = UserHandle.of(taskInfo.userId);
+ mContext.startActivityAsUser(intent, userHandle);
+ }
private void removeLayouts(int taskId) {
- final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
- if (layout != null) {
- layout.release();
+ final CompatUIWindowManager compatLayout = mActiveCompatLayouts.get(taskId);
+ if (compatLayout != null) {
+ compatLayout.release();
mActiveCompatLayouts.remove(taskId);
}
@@ -506,6 +671,12 @@ public class CompatUIController implements OnDisplaysChangedListener,
mActiveReachabilityEduLayout.release();
mActiveReachabilityEduLayout = null;
}
+
+ if (mUserAspectRatioSettingsLayout != null
+ && mUserAspectRatioSettingsLayout.getTaskId() == taskId) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ }
}
private Context getOrCreateDisplayContext(int displayId) {
@@ -561,6 +732,10 @@ public class CompatUIController implements OnDisplaysChangedListener,
if (mActiveReachabilityEduLayout != null && condition.test(mActiveReachabilityEduLayout)) {
callback.accept(mActiveReachabilityEduLayout);
}
+ if (mUserAspectRatioSettingsLayout != null && condition.test(
+ mUserAspectRatioSettingsLayout)) {
+ callback.accept(mUserAspectRatioSettingsLayout);
+ }
}
/** An implementation of {@link OnInsetsChangedListener} for a given display id. */
@@ -595,4 +770,14 @@ public class CompatUIController implements OnDisplaysChangedListener,
insetsChanged(insetsState);
}
}
+
+ /**
+ * A class holding the state of the compat UI hints, which is shared between all compat UI
+ * window managers.
+ */
+ static class CompatUIHintsState {
+ boolean mHasShownSizeCompatHint;
+ boolean mHasShownCameraCompatHint;
+ boolean mHasShownUserAspectRatioSettingsButtonHint;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 065806df3dc8..ce3c5093fdd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -38,6 +38,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import java.util.function.Consumer;
@@ -235,15 +236,4 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN
&& mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED;
}
-
- /**
- * A class holding the state of the compat UI hints, which is shared between all compat UI
- * window managers.
- */
- static class CompatUIHintsState {
- @VisibleForTesting
- boolean mHasShownSizeCompatHint;
- @VisibleForTesting
- boolean mHasShownCameraCompatHint;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index 95bb1fe1c986..5612bc8ef226 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -28,6 +28,7 @@ import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
@@ -36,14 +37,14 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
/**
* Window manager for the reachability education
*/
class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
- // The time to wait before hiding the education
- private static final long DISAPPEAR_DELAY_MS = 4000L;
-
private static final int REACHABILITY_LEFT_OR_UP_POSITION = 0;
private static final int REACHABILITY_RIGHT_OR_BOTTOM_POSITION = 2;
@@ -73,6 +74,10 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
// we need to animate them.
private boolean mHasLetterboxSizeChanged;
+ private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback;
+
+ private final Function<Integer, Integer> mDisappearTimeSupplier;
+
@Nullable
@VisibleForTesting
ReachabilityEduLayout mLayout;
@@ -80,7 +85,9 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
ReachabilityEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
- CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor) {
+ CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor,
+ BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback,
+ Function<Integer, Integer> disappearTimeSupplier) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
@@ -89,6 +96,8 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
mCompatUIConfiguration = compatUIConfiguration;
mMainExecutor = mainExecutor;
+ mOnDismissCallback = onDismissCallback;
+ mDisappearTimeSupplier = disappearTimeSupplier;
}
@Override
@@ -209,7 +218,12 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
}
void updateHideTime() {
- mNextHideTime = SystemClock.uptimeMillis() + DISAPPEAR_DELAY_MS;
+ mNextHideTime = SystemClock.uptimeMillis() + getDisappearTimeMs();
+ }
+
+ private long getDisappearTimeMs() {
+ return mDisappearTimeSupplier.apply(
+ AccessibilityManager.FLAG_CONTENT_ICONS | AccessibilityManager.FLAG_CONTENT_TEXT);
}
private void updateVisibilityOfViews() {
@@ -217,13 +231,17 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
return;
}
final TaskInfo lastTaskInfo = getLastTaskInfo();
+ final boolean hasSeenHorizontalReachabilityEdu =
+ mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo);
+ final boolean hasSeenVerticalReachabilityEdu =
+ mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo);
final boolean eligibleForDisplayHorizontalEducation = mForceUpdate
- || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo)
+ || !hasSeenHorizontalReachabilityEdu
|| (mHasUserDoubleTapped
&& (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION
|| mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
final boolean eligibleForDisplayVerticalEducation = mForceUpdate
- || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo)
+ || !hasSeenVerticalReachabilityEdu
|| (mHasUserDoubleTapped
&& (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION
|| mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
@@ -238,7 +256,16 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
availableHeight, mCompatUIConfiguration, lastTaskInfo);
if (!mHasLetterboxSizeChanged) {
updateHideTime();
- mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS);
+ final long disappearTimeMs = getDisappearTimeMs();
+ mMainExecutor.executeDelayed(this::hideReachability, disappearTimeMs);
+ // If reachability education has been seen for the first time, trigger callback to
+ // display aspect ratio settings button once reachability education disappears
+ if (hasShownHorizontalReachabilityEduFirstTime(hasSeenHorizontalReachabilityEdu)
+ || hasShownVerticalReachabilityEduFirstTime(
+ hasSeenVerticalReachabilityEdu)) {
+ mMainExecutor.executeDelayed(this::triggerOnDismissCallback,
+ disappearTimeMs);
+ }
}
mHasUserDoubleTapped = false;
} else {
@@ -246,6 +273,38 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
}
}
+ /**
+ * Compares the value of
+ * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} before and after the
+ * layout is shown. Horizontal reachability education is considered seen for the first time if
+ * prior to viewing the layout,
+ * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} is {@code false}
+ * but becomes {@code true} once the current layout is shown.
+ */
+ private boolean hasShownHorizontalReachabilityEduFirstTime(
+ boolean previouslyShownHorizontalReachabilityEducation) {
+ return !previouslyShownHorizontalReachabilityEducation
+ && mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(getLastTaskInfo());
+ }
+
+ /**
+ * Compares the value of
+ * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} before and after the
+ * layout is shown. Horizontal reachability education is considered seen for the first time if
+ * prior to viewing the layout,
+ * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} is {@code false}
+ * but becomes {@code true} once the current layout is shown.
+ */
+ private boolean hasShownVerticalReachabilityEduFirstTime(
+ boolean previouslyShownVerticalReachabilityEducation) {
+ return !previouslyShownVerticalReachabilityEducation
+ && mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(getLastTaskInfo());
+ }
+
+ private void triggerOnDismissCallback() {
+ mOnDismissCallback.accept(getLastTaskInfo(), getTaskListener());
+ }
+
private void hideReachability() {
if (mLayout == null || !shouldHideEducation()) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
new file mode 100644
index 000000000000..b141bebbe8b1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Layout for the user aspect ratio button which opens the app list page in settings
+ * and allows users to change apps aspect ratio.
+ */
+public class UserAspectRatioSettingsLayout extends LinearLayout {
+
+ private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+
+ private static final Interpolator PATH_INTERPOLATOR =
+ new PathInterpolator(0.2f, 0f, 0f, 1f);
+
+ private static final float ALPHA_FULL_TRANSPARENT = 0f;
+
+ private static final float ALPHA_FULL_OPAQUE = 1f;
+
+ private static final float SCALE_START = 0.8f;
+
+ private static final float SCALE_END = 1f;
+
+ private static final long FADE_ANIMATION_DURATION_MS = 167;
+
+ private static final long SCALE_ANIMATION_DURATION_MS = 300;
+
+ private static final String ALPHA_PROPERTY_NAME = "alpha";
+
+ private static final String SCALE_X_PROPERTY_NAME = "scaleX";
+
+ private static final String SCALE_Y_PROPERTY_NAME = "scaleY";
+
+ private UserAspectRatioSettingsWindowManager mWindowManager;
+
+ public UserAspectRatioSettingsLayout(Context context) {
+ this(context, null);
+ }
+
+ public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ void inject(@NonNull UserAspectRatioSettingsWindowManager windowManager) {
+ mWindowManager = windowManager;
+ }
+
+ void setUserAspectRatioSettingsHintVisibility(boolean show) {
+ setViewVisibility(R.id.user_aspect_ratio_settings_hint, show);
+ }
+
+ void setUserAspectRatioButtonVisibility(boolean show) {
+ setViewVisibility(R.id.user_aspect_ratio_settings_button, show);
+ // Hint should never be visible without button.
+ if (!show) {
+ setUserAspectRatioSettingsHintVisibility(/* show= */ false);
+ }
+ }
+
+ private void setViewVisibility(@IdRes int resId, boolean show) {
+ final View view = findViewById(resId);
+ int visibility = show ? View.VISIBLE : View.GONE;
+ if (view.getVisibility() == visibility) {
+ return;
+ }
+ if (show) {
+ showItem(view);
+ } else {
+ hideItem(view);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ // Need to relayout after changes like hiding / showing a hint since they affect size.
+ // Doing this directly in setUserAspectRatioButtonVisibility can result in flaky animation.
+ mWindowManager.relayout();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ final ImageButton userAspectRatioButton =
+ findViewById(R.id.user_aspect_ratio_settings_button);
+ userAspectRatioButton.setOnClickListener(
+ view -> mWindowManager.onUserAspectRatioSettingsButtonClicked());
+ userAspectRatioButton.setOnLongClickListener(view -> {
+ mWindowManager.onUserAspectRatioSettingsButtonLongClicked();
+ return true;
+ });
+
+ final LinearLayout sizeCompatHint = findViewById(R.id.user_aspect_ratio_settings_hint);
+ ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text))
+ .setText(R.string.user_aspect_ratio_settings_button_hint);
+ sizeCompatHint.setOnClickListener(
+ view -> setUserAspectRatioSettingsHintVisibility(/* show= */ false));
+ }
+
+ private void showItem(@NonNull View view) {
+ final AnimatorSet animatorSet = new AnimatorSet();
+ final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+ ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE);
+ fadeIn.setDuration(FADE_ANIMATION_DURATION_MS);
+ fadeIn.setInterpolator(LINEAR_INTERPOLATOR);
+ final ObjectAnimator scaleY =
+ ObjectAnimator.ofFloat(view, SCALE_Y_PROPERTY_NAME, SCALE_START, SCALE_END);
+ final ObjectAnimator scaleX =
+ ObjectAnimator.ofFloat(view, SCALE_X_PROPERTY_NAME, SCALE_START, SCALE_END);
+ scaleX.setDuration(SCALE_ANIMATION_DURATION_MS);
+ scaleX.setInterpolator(PATH_INTERPOLATOR);
+ scaleY.setDuration(SCALE_ANIMATION_DURATION_MS);
+ scaleY.setInterpolator(PATH_INTERPOLATOR);
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ view.setVisibility(View.VISIBLE);
+ }
+ });
+ animatorSet.playTogether(fadeIn, scaleY, scaleX);
+ animatorSet.start();
+ }
+
+ private void hideItem(@NonNull View view) {
+ final ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+ ALPHA_FULL_OPAQUE, ALPHA_FULL_TRANSPARENT);
+ fadeOut.setDuration(FADE_ANIMATION_DURATION_MS);
+ fadeOut.setInterpolator(LINEAR_INTERPOLATOR);
+ fadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.GONE);
+ }
+ });
+ fadeOut.start();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
new file mode 100644
index 000000000000..c2dec623416b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Window manager for the user aspect ratio settings button which allows users to go to
+ * app settings and change apps aspect ratio.
+ */
+class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract {
+
+ private static final long SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 500L;
+
+ private long mNextButtonHideTimeMs = -1L;
+
+ private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnButtonClicked;
+
+ private final Function<Integer, Integer> mDisappearTimeSupplier;
+
+ private final ShellExecutor mShellExecutor;
+
+ @NonNull
+ private final Supplier<Boolean> mUserAspectRatioButtonShownChecker;
+
+ @NonNull
+ private final Consumer<Boolean> mUserAspectRatioButtonStateConsumer;
+
+ @VisibleForTesting
+ @NonNull
+ final CompatUIHintsState mCompatUIHintsState;
+
+ @Nullable
+ private UserAspectRatioSettingsLayout mLayout;
+
+ // Remember the last reported states in case visibility changes due to keyguard or IME updates.
+ @VisibleForTesting
+ boolean mHasUserAspectRatioSettingsButton;
+
+ UserAspectRatioSettingsWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo,
+ @NonNull SyncTransactionQueue syncQueue,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener,
+ @NonNull DisplayLayout displayLayout, @NonNull CompatUIHintsState compatUIHintsState,
+ @NonNull BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onButtonClicked,
+ @NonNull ShellExecutor shellExecutor,
+ @NonNull Function<Integer, Integer> disappearTimeSupplier,
+ @NonNull Supplier<Boolean> userAspectRatioButtonStateChecker,
+ @NonNull Consumer<Boolean> userAspectRatioButtonShownConsumer) {
+ super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ mShellExecutor = shellExecutor;
+ mUserAspectRatioButtonShownChecker = userAspectRatioButtonStateChecker;
+ mUserAspectRatioButtonStateConsumer = userAspectRatioButtonShownConsumer;
+ mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+ mCompatUIHintsState = compatUIHintsState;
+ mOnButtonClicked = onButtonClicked;
+ mDisappearTimeSupplier = disappearTimeSupplier;
+ }
+
+ @Override
+ protected int getZOrder() {
+ return TASK_CHILD_LAYER_COMPAT_UI + 1;
+ }
+
+ @Override
+ protected @Nullable View getLayout() {
+ return mLayout;
+ }
+
+ @Override
+ protected void removeLayout() {
+ mLayout = null;
+ }
+
+ @Override
+ protected boolean eligibleToShowLayout() {
+ return mHasUserAspectRatioSettingsButton;
+ }
+
+ @Override
+ protected View createLayout() {
+ mLayout = inflateLayout();
+ mLayout.inject(this);
+
+ updateVisibilityOfViews();
+
+ return mLayout;
+ }
+
+ @VisibleForTesting
+ UserAspectRatioSettingsLayout inflateLayout() {
+ return (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.user_aspect_ratio_settings_layout, null);
+ }
+
+ @Override
+ public boolean updateCompatInfo(@NonNull TaskInfo taskInfo,
+ @NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
+ final boolean prevHasUserAspectRatioSettingsButton = mHasUserAspectRatioSettingsButton;
+ mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+
+ if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
+ return false;
+ }
+
+ if (prevHasUserAspectRatioSettingsButton != mHasUserAspectRatioSettingsButton) {
+ updateVisibilityOfViews();
+ }
+ return true;
+ }
+
+ /** Called when the user aspect ratio settings button is clicked. */
+ void onUserAspectRatioSettingsButtonClicked() {
+ mOnButtonClicked.accept(getLastTaskInfo(), getTaskListener());
+ }
+
+ /** Called when the user aspect ratio settings button is long clicked. */
+ void onUserAspectRatioSettingsButtonLongClicked() {
+ if (mLayout == null) {
+ return;
+ }
+ mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ final long disappearTimeMs = getDisappearTimeMs();
+ mNextButtonHideTimeMs = updateHideTime(disappearTimeMs);
+ mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs);
+ }
+
+ @Override
+ @VisibleForTesting
+ public void updateSurfacePosition() {
+ if (mLayout == null) {
+ return;
+ }
+ // Position of the button in the container coordinate.
+ final Rect taskBounds = getTaskBounds();
+ final Rect taskStableBounds = getTaskStableBounds();
+ final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? taskStableBounds.left - taskBounds.left
+ : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
+ final int positionY = taskStableBounds.bottom - taskBounds.top
+ - mLayout.getMeasuredHeight();
+ updateSurfacePosition(positionX, positionY);
+ }
+
+ @VisibleForTesting
+ void updateVisibilityOfViews() {
+ if (mHasUserAspectRatioSettingsButton) {
+ mShellExecutor.executeDelayed(this::showUserAspectRatioButton,
+ SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+ final long disappearTimeMs = getDisappearTimeMs();
+ mNextButtonHideTimeMs = updateHideTime(disappearTimeMs);
+ mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs);
+ } else {
+ mShellExecutor.removeCallbacks(this::showUserAspectRatioButton);
+ mShellExecutor.execute(this::hideUserAspectRatioButton);
+ }
+ }
+
+ @VisibleForTesting
+ boolean isShowingButton() {
+ return (mUserAspectRatioButtonShownChecker.get()
+ && !isHideDelayReached(mNextButtonHideTimeMs));
+ }
+
+ private void showUserAspectRatioButton() {
+ if (mLayout == null) {
+ return;
+ }
+ mLayout.setUserAspectRatioButtonVisibility(true);
+ mUserAspectRatioButtonStateConsumer.accept(true);
+ // Only show by default for the first time.
+ if (!mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint) {
+ mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+ }
+ }
+
+ private void hideUserAspectRatioButton() {
+ if (mLayout == null || !isHideDelayReached(mNextButtonHideTimeMs)) {
+ return;
+ }
+ mLayout.setUserAspectRatioButtonVisibility(false);
+ }
+
+ private boolean isHideDelayReached(long nextHideTime) {
+ return SystemClock.uptimeMillis() >= nextHideTime;
+ }
+
+ private long updateHideTime(long hideDelay) {
+ return SystemClock.uptimeMillis() + hideDelay;
+ }
+
+ private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
+ return taskInfo.topActivityEligibleForUserAspectRatioButton
+ && (taskInfo.topActivityBoundsLetterboxed
+ || taskInfo.isUserFullscreenOverrideEnabled)
+ && (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
+ }
+
+ private long getDisappearTimeMs() {
+ return mDisappearTimeSupplier.apply(AccessibilityManager.FLAG_CONTENT_CONTROLS);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 12d51f54a09c..9facbd542e6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -25,11 +25,13 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.pip.TvPipModule;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -86,13 +88,14 @@ public class TvWMShellModule {
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
@ShellMainThread ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
displayImeController, displayInsetsController, dragAndDropController, transitions,
- transactionPool, iconProvider, recentTasks, mainExecutor, mainHandler,
- systemWindows);
+ transactionPool, iconProvider, recentTasks, launchAdjacentController, mainExecutor,
+ mainHandler, systemWindows);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index c491fed5d896..45869e177c6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -24,6 +24,7 @@ import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.SystemProperties;
import android.view.IWindowManager;
+import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
@@ -46,6 +47,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -56,6 +58,15 @@ import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
@@ -74,11 +85,6 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipMediaController;
-import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -102,13 +108,13 @@ import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
-import java.util.Optional;
-
import dagger.BindsOptionalOf;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
* accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -127,6 +133,12 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static FloatingContentCoordinator provideFloatingContentCoordinator() {
+ return new FloatingContentCoordinator();
+ }
+
+ @WMSingleton
+ @Provides
static DisplayController provideDisplayController(Context context,
IWindowManager wmService,
ShellInit shellInit,
@@ -228,10 +240,12 @@ public abstract class WMShellBaseModule {
DisplayImeController imeController, SyncTransactionQueue syncQueue,
@ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy,
DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
- CompatUIShellCommandHandler compatUIShellCommandHandler) {
+ CompatUIShellCommandHandler compatUIShellCommandHandler,
+ AccessibilityManager accessibilityManager) {
return new CompatUIController(context, shellInit, shellController, displayController,
displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
- dockStateReader, compatUIConfiguration, compatUIShellCommandHandler);
+ dockStateReader, compatUIConfiguration, compatUIShellCommandHandler,
+ accessibilityManager);
}
@WMSingleton
@@ -275,6 +289,13 @@ public abstract class WMShellBaseModule {
return new WindowManagerShellWrapper(mainExecutor);
}
+ @WMSingleton
+ @Provides
+ static LaunchAdjacentController provideLaunchAdjacentController(
+ SyncTransactionQueue syncQueue) {
+ return new LaunchAdjacentController(syncQueue);
+ }
+
//
// Back animation
//
@@ -311,6 +332,60 @@ public abstract class WMShellBaseModule {
return Optional.empty();
}
+ //
+ // PiP (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
+ PackageManager packageManager) {
+ return new PipUiEventLogger(uiEventLogger, packageManager);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipMediaController providePipMediaController(Context context,
+ @ShellMainThread Handler mainHandler) {
+ return new PipMediaController(context, mainHandler);
+ }
+
+ @WMSingleton
+ @Provides
+ static SizeSpecSource provideSizeSpecSource(Context context,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PhoneSizeSpecSource(context, pipDisplayLayoutState);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipBoundsState providePipBoundsState(Context context,
+ SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
+ }
+
+
+ @WMSingleton
+ @Provides
+ static PipSnapAlgorithm providePipSnapAlgorithm() {
+ return new PipSnapAlgorithm();
+ }
+
+ @WMSingleton
+ @Provides
+ static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
+ return new PhonePipKeepClearAlgorithm(context);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
+ PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
+ PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
+ return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
+ pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource);
+ }
//
// Bubbles (optional feature)
@@ -462,40 +537,6 @@ public abstract class WMShellBaseModule {
}
//
- // Pip (optional feature)
- //
-
- @WMSingleton
- @Provides
- static FloatingContentCoordinator provideFloatingContentCoordinator() {
- return new FloatingContentCoordinator();
- }
-
- // Needs handler for registering broadcast receivers
- @WMSingleton
- @Provides
- static PipMediaController providePipMediaController(Context context,
- @ShellMainThread Handler mainHandler) {
- return new PipMediaController(context, mainHandler);
- }
-
- @WMSingleton
- @Provides
- static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
- return new PipSurfaceTransactionHelper(context);
- }
-
- @WMSingleton
- @Provides
- static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
- PackageManager packageManager) {
- return new PipUiEventLogger(uiEventLogger, packageManager);
- }
-
- @BindsOptionalOf
- abstract PipTouchHandler optionalPipTouchHandler();
-
- //
// Recent tasks
//
@@ -829,8 +870,6 @@ public abstract class WMShellBaseModule {
ShellTaskOrganizer shellTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
- Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
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 cff317259f1e..065e7b09a05d 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.dagger;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.os.Handler;
@@ -36,25 +37,29 @@ import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.properties.ProdBubbleProperties;
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
+import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -62,27 +67,7 @@ import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipMediaController;
-import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTransition;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
-import com.android.wm.shell.pip.phone.PhonePipMenuController;
-import com.android.wm.shell.pip.phone.PipController;
-import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -122,7 +107,10 @@ import java.util.Optional;
* This module only defines Shell dependencies for handheld SystemUI implementation. Common
* dependencies should go into {@link WMShellBaseModule}.
*/
-@Module(includes = WMShellBaseModule.class)
+@Module(includes = {
+ WMShellBaseModule.class,
+ PipModule.class
+})
public abstract class WMShellModule {
//
@@ -180,11 +168,12 @@ public abstract class WMShellModule {
IWindowManager wmService) {
return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
null /* synchronizer */, floatingContentCoordinator,
- new BubbleDataRepository(context, launcherApps, mainExecutor),
+ new BubbleDataRepository(launcherApps, mainExecutor,
+ new BubblePersistentRepository(context)),
statusBarService, windowManager, windowManagerShellWrapper, userManager,
launcherApps, logger, taskStackListener, organizer, positioner, displayController,
oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
- taskViewTransitions, syncQueue, wmService);
+ taskViewTransitions, syncQueue, wmService, ProdBubbleProperties.INSTANCE);
}
//
@@ -197,33 +186,35 @@ public abstract class WMShellModule {
Context context,
@ShellMainThread Handler mainHandler,
@ShellMainThread Choreographer mainChoreographer,
+ ShellInit shellInit,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
+ ShellController shellController,
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopModeController> desktopModeController,
- Optional<DesktopTasksController> desktopTasksController,
- Optional<SplitScreenController> splitScreenController) {
+ Optional<DesktopTasksController> desktopTasksController) {
if (DesktopModeStatus.isAnyEnabled()) {
return new DesktopModeWindowDecorViewModel(
context,
mainHandler,
mainChoreographer,
+ shellInit,
taskOrganizer,
displayController,
+ shellController,
syncQueue,
transitions,
desktopModeController,
- desktopTasksController,
- splitScreenController);
+ desktopTasksController);
}
return new CaptionWindowDecorViewModel(
- context,
- mainHandler,
- mainChoreographer,
- taskOrganizer,
- displayController,
- syncQueue);
+ context,
+ mainHandler,
+ mainChoreographer,
+ taskOrganizer,
+ displayController,
+ syncQueue);
}
//
@@ -263,8 +254,13 @@ public abstract class WMShellModule {
static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
ShellInit shellInit,
Transitions transitions,
- WindowDecorViewModel windowDecorViewModel) {
- return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel);
+ Context context,
+ WindowDecorViewModel windowDecorViewModel,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellAnimationThread ShellExecutor animExecutor) {
+ return new FreeformTaskTransitionHandler(shellInit, transitions, context,
+ windowDecorViewModel, displayController, mainExecutor, animExecutor);
}
@WMSingleton
@@ -327,200 +323,15 @@ public abstract class WMShellModule {
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
+ Optional<WindowDecorViewModel> windowDecorViewModel,
+ Optional<DesktopTasksController> desktopTasksController,
@ShellMainThread ShellExecutor mainExecutor) {
return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
displayImeController, displayInsetsController, dragAndDropController, transitions,
- transactionPool, iconProvider, recentTasks, mainExecutor);
- }
-
- //
- // Pip
- //
-
- @WMSingleton
- @Provides
- static Optional<Pip> providePip(Context context,
- ShellInit shellInit,
- ShellCommandHandler shellCommandHandler,
- ShellController shellController,
- DisplayController displayController,
- PipAnimationController pipAnimationController,
- PipAppOpsListener pipAppOpsListener,
- PipBoundsAlgorithm pipBoundsAlgorithm,
- PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
- PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
- PipDisplayLayoutState pipDisplayLayoutState,
- PipMotionHelper pipMotionHelper,
- PipMediaController pipMediaController,
- PhonePipMenuController phonePipMenuController,
- PipTaskOrganizer pipTaskOrganizer,
- PipTransitionState pipTransitionState,
- PipTouchHandler pipTouchHandler,
- PipTransitionController pipTransitionController,
- WindowManagerShellWrapper windowManagerShellWrapper,
- TaskStackListenerImpl taskStackListener,
- PipParamsChangedForwarder pipParamsChangedForwarder,
- DisplayInsetsController displayInsetsController,
- TabletopModeController pipTabletopController,
- Optional<OneHandedController> oneHandedController,
- @ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(PipController.create(
- context, shellInit, shellCommandHandler, shellController,
- displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipDisplayLayoutState,
- pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
- pipTransitionState, pipTouchHandler, pipTransitionController,
- windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, pipTabletopController, oneHandedController, mainExecutor));
- }
-
- @WMSingleton
- @Provides
- static PipBoundsState providePipBoundsState(Context context,
- PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
- return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
- }
-
- @WMSingleton
- @Provides
- static PipSnapAlgorithm providePipSnapAlgorithm() {
- return new PipSnapAlgorithm();
- }
-
- @WMSingleton
- @Provides
- static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
- return new PhonePipKeepClearAlgorithm(context);
- }
-
- @WMSingleton
- @Provides
- static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
- PipDisplayLayoutState pipDisplayLayoutState) {
- return new PipSizeSpecHandler(context, pipDisplayLayoutState);
- }
-
- @WMSingleton
- @Provides
- static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
- PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
- PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
- PipSizeSpecHandler pipSizeSpecHandler) {
- return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
- pipKeepClearAlgorithm, pipSizeSpecHandler);
- }
-
- // Handler is used by Icon.loadDrawableAsync
- @WMSingleton
- @Provides
- static PhonePipMenuController providesPipPhoneMenuController(Context context,
- PipBoundsState pipBoundsState, PipMediaController pipMediaController,
- SystemWindows systemWindows,
- Optional<SplitScreenController> splitScreenOptional,
- PipUiEventLogger pipUiEventLogger,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler) {
- return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
- systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler);
- }
-
- @WMSingleton
- @Provides
- static PipTouchHandler providePipTouchHandler(Context context,
- ShellInit shellInit,
- PhonePipMenuController menuPhoneController,
- PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
- PipTaskOrganizer pipTaskOrganizer,
- PipMotionHelper pipMotionHelper,
- FloatingContentCoordinator floatingContentCoordinator,
- PipUiEventLogger pipUiEventLogger,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
- pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper,
- floatingContentCoordinator, pipUiEventLogger, mainExecutor);
- }
-
- @WMSingleton
- @Provides
- static PipTransitionState providePipTransitionState() {
- return new PipTransitionState();
- }
-
- @WMSingleton
- @Provides
- static PipTaskOrganizer providePipTaskOrganizer(Context context,
- SyncTransactionQueue syncTransactionQueue,
- PipTransitionState pipTransitionState,
- PipBoundsState pipBoundsState,
- PipDisplayLayoutState pipDisplayLayoutState,
- PipBoundsAlgorithm pipBoundsAlgorithm,
- PhonePipMenuController menuPhoneController,
- PipAnimationController pipAnimationController,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- PipTransitionController pipTransitionController,
- PipParamsChangedForwarder pipParamsChangedForwarder,
- Optional<SplitScreenController> splitScreenControllerOptional,
- DisplayController displayController,
- PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new PipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
- pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
- pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
- splitScreenControllerOptional, displayController, pipUiEventLogger,
- shellTaskOrganizer, mainExecutor);
- }
-
- @WMSingleton
- @Provides
- static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
- pipSurfaceTransactionHelper) {
- return new PipAnimationController(pipSurfaceTransactionHelper);
- }
-
- @WMSingleton
- @Provides
- static PipTransitionController providePipTransitionController(Context context,
- ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
- PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState,
- PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- Optional<SplitScreenController> splitScreenOptional) {
- return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
- pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
- pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
- splitScreenOptional);
- }
-
- @WMSingleton
- @Provides
- static PipAppOpsListener providePipAppOpsListener(Context context,
- PipTouchHandler pipTouchHandler,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
- }
-
- @WMSingleton
- @Provides
- static PipMotionHelper providePipMotionHelper(Context context,
- PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
- PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
- PipTransitionController pipTransitionController,
- FloatingContentCoordinator floatingContentCoordinator) {
- return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
- menuController, pipSnapAlgorithm, pipTransitionController,
- floatingContentCoordinator);
- }
-
- @WMSingleton
- @Provides
- static PipParamsChangedForwarder providePipParamsChangedForwarder() {
- return new PipParamsChangedForwarder();
+ transactionPool, iconProvider, recentTasks, launchAdjacentController,
+ windowDecorViewModel, desktopTasksController, mainExecutor);
}
//
@@ -532,13 +343,16 @@ public abstract class WMShellModule {
static DefaultMixedHandler provideDefaultMixedHandler(
ShellInit shellInit,
Optional<SplitScreenController> splitScreenOptional,
- Optional<PipTouchHandler> pipTouchHandlerOptional,
+ @Nullable PipTransitionController pipTransitionController,
Optional<RecentsTransitionHandler> recentsTransitionHandler,
KeyguardTransitionHandler keyguardTransitionHandler,
+ Optional<DesktopModeController> desktopModeController,
+ Optional<DesktopTasksController> desktopTasksController,
Optional<UnfoldTransitionHandler> unfoldHandler,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
- pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler,
+ pipTransitionController, recentsTransitionHandler,
+ keyguardTransitionHandler, desktopModeController, desktopTasksController,
unfoldHandler);
}
@@ -573,13 +387,13 @@ public abstract class WMShellModule {
animators.add(fullscreenAnimator);
return new UnfoldAnimationController(
- shellInit,
- transactionPool,
- progressProvider.get(),
- animators,
- unfoldTransitionHandler,
- mainExecutor
- );
+ shellInit,
+ transactionPool,
+ progressProvider.get(),
+ animators,
+ unfoldTransitionHandler,
+ mainExecutor
+ );
}
@Provides
@@ -671,6 +485,7 @@ public abstract class WMShellModule {
static DesktopTasksController provideDesktopTasksController(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
ShellTaskOrganizer shellTaskOrganizer,
@@ -679,13 +494,16 @@ public abstract class WMShellModule {
Transitions transitions,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
+ ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ LaunchAdjacentController launchAdjacentController,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new DesktopTasksController(context, shellInit, shellController, displayController,
- shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
- enterDesktopTransitionHandler, exitDesktopTransitionHandler,
- desktopModeTaskRepository, mainExecutor);
+ return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
+ displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
+ transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
+ toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository,
+ launchAdjacentController, mainExecutor);
}
@WMSingleton
@@ -697,6 +515,13 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler(
+ Transitions transitions) {
+ return new ToggleResizeDesktopTaskTransitionHandler(transitions);
+ }
+
+ @WMSingleton
+ @Provides
static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
Transitions transitions,
Context context
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
new file mode 100644
index 000000000000..ba882c403aed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger.pip;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
+import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.dagger.WMShellBaseModule;
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransition;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.pip.phone.PhonePipMenuController;
+import com.android.wm.shell.pip.phone.PipController;
+import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import dagger.Module;
+import dagger.Provides;
+
+import java.util.Optional;
+
+/**
+ * Provides dependencies from {@link com.android.wm.shell.pip}, this implementation is meant to be
+ * replaced by the sibling {@link Pip2Module}.
+ */
+@Module(includes = {
+ Pip1SharedModule.class,
+ WMShellBaseModule.class
+})
+public abstract class Pip1Module {
+ @WMSingleton
+ @Provides
+ static Optional<Pip> providePip1(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ DisplayController displayController,
+ PipAnimationController pipAnimationController,
+ PipAppOpsListener pipAppOpsListener,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipBoundsState pipBoundsState,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipMotionHelper pipMotionHelper,
+ PipMediaController pipMediaController,
+ PhonePipMenuController phonePipMenuController,
+ PipTaskOrganizer pipTaskOrganizer,
+ PipTransitionState pipTransitionState,
+ PipTouchHandler pipTouchHandler,
+ PipTransitionController pipTransitionController,
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ TaskStackListenerImpl taskStackListener,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
+ DisplayInsetsController displayInsetsController,
+ TabletopModeController pipTabletopController,
+ Optional<OneHandedController> oneHandedController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ return Optional.empty();
+ } else {
+ return Optional.ofNullable(PipController.create(
+ context, shellInit, shellCommandHandler, shellController,
+ displayController, pipAnimationController, pipAppOpsListener,
+ pipBoundsAlgorithm,
+ pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
+ pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ displayInsetsController, pipTabletopController, oneHandedController,
+ mainExecutor));
+ }
+ }
+
+ // Handler is used by Icon.loadDrawableAsync
+ @WMSingleton
+ @Provides
+ static PhonePipMenuController providesPipPhoneMenuController(Context context,
+ PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+ SystemWindows systemWindows,
+ Optional<SplitScreenController> splitScreenOptional,
+ PipUiEventLogger pipUiEventLogger,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
+ return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
+ systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipTouchHandler providePipTouchHandler(Context context,
+ ShellInit shellInit,
+ PhonePipMenuController menuPhoneController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipBoundsState pipBoundsState,
+ SizeSpecSource sizeSpecSource,
+ PipTaskOrganizer pipTaskOrganizer,
+ PipMotionHelper pipMotionHelper,
+ FloatingContentCoordinator floatingContentCoordinator,
+ PipUiEventLogger pipUiEventLogger,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
+ pipBoundsState, sizeSpecSource, pipTaskOrganizer, pipMotionHelper,
+ floatingContentCoordinator, pipUiEventLogger, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipTransitionState providePipTransitionState() {
+ return new PipTransitionState();
+ }
+
+ @WMSingleton
+ @Provides
+ static PipTaskOrganizer providePipTaskOrganizer(Context context,
+ SyncTransactionQueue syncTransactionQueue,
+ PipTransitionState pipTransitionState,
+ PipBoundsState pipBoundsState,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PhonePipMenuController menuPhoneController,
+ PipAnimationController pipAnimationController,
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ PipTransitionController pipTransitionController,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
+ Optional<SplitScreenController> splitScreenControllerOptional,
+ DisplayController displayController,
+ PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new PipTaskOrganizer(context,
+ syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
+ pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
+ pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+ splitScreenControllerOptional, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipTransition providePipTransition(Context context,
+ ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
+ PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState,
+ PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ Optional<SplitScreenController> splitScreenOptional) {
+ return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
+ pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
+ pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+ splitScreenOptional);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipAppOpsListener providePipAppOpsListener(Context context,
+ PipTouchHandler pipTouchHandler,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipMotionHelper providePipMotionHelper(Context context,
+ PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
+ PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
+ PipTransitionController pipTransitionController,
+ FloatingContentCoordinator floatingContentCoordinator) {
+ return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
+ menuController, pipSnapAlgorithm, pipTransitionController,
+ floatingContentCoordinator);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipParamsChangedForwarder providePipParamsChangedForwarder() {
+ return new PipParamsChangedForwarder();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
new file mode 100644
index 000000000000..b42372b869dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger.pip;
+
+import android.content.Context;
+
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides shared dependencies from {@link com.android.wm.shell.pip}, this implementation is
+ * shared with {@link TvPipModule} and possibly other form factors.
+ */
+@Module
+public abstract class Pip1SharedModule {
+ @WMSingleton
+ @Provides
+ static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
+ return new PipSurfaceTransactionHelper(context);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
+ pipSurfaceTransactionHelper) {
+ return new PipAnimationController(pipSurfaceTransactionHelper);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
new file mode 100644
index 000000000000..af97cf68915f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger.pip;
+
+import android.annotation.NonNull;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.dagger.WMShellBaseModule;
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip2.PipTransition;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be
+ * the successor of its sibling {@link Pip1Module}.
+ */
+@Module(includes = WMShellBaseModule.class)
+public abstract class Pip2Module {
+ @WMSingleton
+ @Provides
+ static PipTransition providePipTransition(@NonNull ShellInit shellInit,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull Transitions transitions,
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm) {
+ return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
+ pipBoundsAlgorithm);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
new file mode 100644
index 000000000000..570f0a3db35a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger.pip;
+
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.PipTransitionController;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides dependencies for external components / modules reference PiP and extracts away the
+ * selection of legacy and new PiP implementation.
+ */
+@Module(includes = {
+ Pip1Module.class,
+ Pip2Module.class
+})
+public abstract class PipModule {
+ @WMSingleton
+ @Provides
+ static PipTransitionController providePipTransitionController(
+ com.android.wm.shell.pip.PipTransition legacyPipTransition,
+ com.android.wm.shell.pip2.PipTransition newPipTransition) {
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ return newPipTransition;
+ } else {
+ return legacyPipTransition;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 14daae03e6b5..a9675f976fa9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.dagger;
+package com.android.wm.shell.dagger.pip;
import android.content.Context;
import android.os.Handler;
@@ -28,19 +28,21 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.dagger.WMShellBaseModule;
+import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
import com.android.wm.shell.pip.tv.TvPipBoundsController;
import com.android.wm.shell.pip.tv.TvPipBoundsState;
@@ -62,7 +64,9 @@ import java.util.Optional;
/**
* Provides TV specific dependencies for Pip.
*/
-@Module(includes = {WMShellBaseModule.class})
+@Module(includes = {
+ WMShellBaseModule.class,
+ Pip1SharedModule.class})
public abstract class TvPipModule {
@WMSingleton
@Provides
@@ -126,31 +130,25 @@ public abstract class TvPipModule {
@WMSingleton
@Provides
- static PipSnapAlgorithm providePipSnapAlgorithm() {
- return new PipSnapAlgorithm();
- }
-
- @WMSingleton
- @Provides
static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
- PipSizeSpecHandler pipSizeSpecHandler) {
+ PipDisplayLayoutState pipDisplayLayoutState, LegacySizeSpecSource sizeSpecSource) {
return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
- pipSizeSpecHandler);
+ pipDisplayLayoutState, sizeSpecSource);
}
@WMSingleton
@Provides
static TvPipBoundsState provideTvPipBoundsState(Context context,
- PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
- return new TvPipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
+ LegacySizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new TvPipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
}
@WMSingleton
@Provides
- static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
+ static LegacySizeSpecSource provideSizeSpecSource(Context context,
PipDisplayLayoutState pipDisplayLayoutState) {
- return new PipSizeSpecHandler(context, pipDisplayLayoutState);
+ return new LegacySizeSpecSource(context, pipDisplayLayoutState);
}
// Handler needed for loadDrawableAsync() in PipControlsViewController
@@ -195,13 +193,6 @@ public abstract class TvPipModule {
@WMSingleton
@Provides
- static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
- pipSurfaceTransactionHelper) {
- return new PipAnimationController(pipSurfaceTransactionHelper);
- }
-
- @WMSingleton
- @Provides
static PipTransitionState providePipTransitionState() {
return new PipTransitionState();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index b9d2be280efb..5b24d7a60c4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -33,6 +33,7 @@ import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DE
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.content.Context;
+import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Region;
import android.net.Uri;
@@ -414,6 +415,25 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
}
/**
+ * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
+ * This is intended to be used when desktop mode is part of another animation but isn't, itself,
+ * animating.
+ */
+ public void syncSurfaceState(@NonNull TransitionInfo info,
+ SurfaceControl.Transaction finishTransaction) {
+ // Add rounded corners to freeform windows
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[]{android.R.attr.dialogCornerRadius});
+ final int cornerRadius = ta.getDimensionPixelSize(0, 0);
+ ta.recycle();
+ for (TransitionInfo.Change change: info.getChanges()) {
+ if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ finishTransaction.setCornerRadius(change.getLeash(), cornerRadius);
+ }
+ }
+ }
+
+ /**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
private final class SettingsObserver extends ContentObserver {
@@ -500,6 +520,11 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
}
@Override
+ public void showDesktopApp(int taskId) throws RemoteException {
+ // TODO
+ }
+
+ @Override
public int getVisibleTaskCount(int displayId) throws RemoteException {
int[] result = new int[1];
executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
@@ -508,5 +533,25 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
);
return result[0];
}
+
+ @Override
+ public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) {
+
+ }
+
+ @Override
+ public void stashDesktopApps(int displayId) throws RemoteException {
+ // Stashing of desktop apps not needed. Apps always launch on desktop
+ }
+
+ @Override
+ public void hideStashedDesktopApps(int displayId) throws RemoteException {
+ // Stashing of desktop apps not needed. Apps always launch on desktop
+ }
+
+ @Override
+ public void setTaskListener(IDesktopTaskListener listener) throws RemoteException {
+ // TODO(b/261234402): move visibility from sysui state to listener
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 76ca68bbfa75..517f9f2aba27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -54,6 +54,15 @@ public class DesktopModeStatus {
public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_change_display", false);
+
+ /**
+ * Flag to indicate that desktop stashing is enabled.
+ * When enabled, swiping home from desktop stashes the open apps. Next app that launches,
+ * will be added to the desktop.
+ */
+ private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_stashing", false);
+
/**
* Return {@code true} if desktop mode support is enabled
*/
@@ -84,6 +93,13 @@ public class DesktopModeStatus {
}
/**
+ * Return {@code true} if desktop task stashing is enabled when going home.
+ * Allows users to use home screen to add tasks to desktop.
+ */
+ public static boolean isStashingEnabled() {
+ return IS_STASHING_ENABLED;
+ }
+ /**
* Check if desktop mode is active
*
* @return {@code true} if active
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index a7a1991c94d6..c05af73e6765 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -25,6 +25,7 @@ import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.util.KtProtoLog
+import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -43,6 +44,7 @@ class DesktopModeTaskRepository {
*/
val activeTasks: ArraySet<Int> = ArraySet(),
val visibleTasks: ArraySet<Int> = ArraySet(),
+ var stashed: Boolean = false
)
// Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
@@ -85,8 +87,10 @@ class DesktopModeTaskRepository {
visibleTasksListeners[visibleTasksListener] = executor
displayData.keyIterator().forEach { displayId ->
val visibleTasks = getVisibleTaskCount(displayId)
+ val stashed = isStashed(displayId)
executor.execute {
visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0)
+ visibleTasksListener.onStashedChanged(displayId, stashed)
}
}
}
@@ -312,6 +316,52 @@ class DesktopModeTaskRepository {
}
/**
+ * Update stashed status on display with id [displayId]
+ */
+ fun setStashed(displayId: Int, stashed: Boolean) {
+ val data = displayData.getOrCreate(displayId)
+ val oldValue = data.stashed
+ data.stashed = stashed
+ if (oldValue != stashed) {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: mark stashed=%b displayId=%d",
+ stashed,
+ displayId
+ )
+ visibleTasksListeners.forEach { (listener, executor) ->
+ executor.execute { listener.onStashedChanged(displayId, stashed) }
+ }
+ }
+ }
+
+ /**
+ * Check if display with id [displayId] has desktop tasks stashed
+ */
+ fun isStashed(displayId: Int): Boolean {
+ return displayData[displayId]?.stashed ?: false
+ }
+
+ internal fun dump(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ pw.println("${prefix}DesktopModeTaskRepository")
+ dumpDisplayData(pw, innerPrefix)
+ pw.println("${innerPrefix}freeformTasksInZOrder=${freeformTasksInZOrder.toDumpString()}")
+ pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
+ pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
+ }
+
+ private fun dumpDisplayData(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ displayData.forEach { displayId, data ->
+ pw.println("${prefix}Display $displayId:")
+ pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
+ pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
+ pw.println("${innerPrefix}stashed=${data.stashed}")
+ }
+ }
+
+ /**
* Defines interface for classes that can listen to changes for active tasks in desktop mode.
*/
interface ActiveTasksListener {
@@ -329,5 +379,14 @@ class DesktopModeTaskRepository {
* Called when the desktop starts or stops showing freeform tasks.
*/
fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
+
+ /**
+ * Called when the desktop stashed status changes.
+ */
+ fun onStashedChanged(displayId: Int, stashed: Boolean) {}
}
}
+
+private fun <T> Iterable<T>.toDumpString(): String {
+ return joinToString(separator = ", ", prefix = "[", postfix = "]")
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 0f0d572f8eae..a587bed3fef0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -27,6 +27,7 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
@@ -47,6 +48,15 @@ import com.android.wm.shell.common.SyncTransactionQueue;
* Animated visual indicator for Desktop Mode windowing transitions.
*/
public class DesktopModeVisualIndicator {
+ public static final int INVALID_INDICATOR = -1;
+ /** Indicates impending transition into desktop mode */
+ public static final int TO_DESKTOP_INDICATOR = 1;
+ /** Indicates impending transition into fullscreen */
+ public static final int TO_FULLSCREEN_INDICATOR = 2;
+ /** Indicates impending transition into split select on the left side */
+ public static final int TO_SPLIT_LEFT_INDICATOR = 3;
+ /** Indicates impending transition into split select on the right side */
+ public static final int TO_SPLIT_RIGHT_INDICATOR = 4;
private final Context mContext;
private final DisplayController mDisplayController;
@@ -54,6 +64,7 @@ public class DesktopModeVisualIndicator {
private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
private final ActivityManager.RunningTaskInfo mTaskInfo;
private final SurfaceControl mTaskSurface;
+ private final Rect mIndicatorRange = new Rect();
private SurfaceControl mLeash;
private final SyncTransactionQueue mSyncQueue;
@@ -61,11 +72,12 @@ public class DesktopModeVisualIndicator {
private View mView;
private boolean mIsFullscreen;
+ private int mType;
public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
- RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer, int type) {
mSyncQueue = syncQueue;
mTaskInfo = taskInfo;
mDisplayController = displayController;
@@ -73,10 +85,64 @@ public class DesktopModeVisualIndicator {
mTaskSurface = taskSurface;
mTaskOrganizer = taskOrganizer;
mRootTdaOrganizer = taskDisplayAreaOrganizer;
+ mType = type;
+ defineIndicatorRange();
createView();
}
/**
+ * If an indicator is warranted based on the input and task bounds, return the type of
+ * indicator that should be created.
+ */
+ public static int determineIndicatorType(PointF inputCoordinates, Rect taskBounds,
+ DisplayLayout layout, Context context) {
+ int transitionAreaHeight = context.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
+ int transitionAreaWidth = context.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
+ if (taskBounds.top <= transitionAreaHeight) return TO_FULLSCREEN_INDICATOR;
+ if (inputCoordinates.x <= transitionAreaWidth) return TO_SPLIT_LEFT_INDICATOR;
+ if (inputCoordinates.x >= layout.width() - transitionAreaWidth) {
+ return TO_SPLIT_RIGHT_INDICATOR;
+ }
+ return INVALID_INDICATOR;
+ }
+
+ /**
+ * Determine range of inputs that will keep this indicator displaying.
+ */
+ private void defineIndicatorRange() {
+ DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ int captionHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.freeform_decor_caption_height);
+ int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
+ int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
+ switch (mType) {
+ case TO_DESKTOP_INDICATOR:
+ // TO_DESKTOP indicator is only dismissed on release; entire display is valid.
+ mIndicatorRange.set(0, 0, layout.width(), layout.height());
+ break;
+ case TO_FULLSCREEN_INDICATOR:
+ // If drag results in caption going above the top edge of the display, we still
+ // want to transition to fullscreen.
+ mIndicatorRange.set(0, -captionHeight, layout.width(), transitionAreaHeight);
+ break;
+ case TO_SPLIT_LEFT_INDICATOR:
+ mIndicatorRange.set(0, transitionAreaHeight, transitionAreaWidth, layout.height());
+ break;
+ case TO_SPLIT_RIGHT_INDICATOR:
+ mIndicatorRange.set(layout.width() - transitionAreaWidth, transitionAreaHeight,
+ layout.width(), layout.height());
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ /**
* Create a fullscreen indicator with no animation
*/
private void createView() {
@@ -85,11 +151,30 @@ public class DesktopModeVisualIndicator {
final DisplayMetrics metrics = resources.getDisplayMetrics();
final int screenWidth = metrics.widthPixels;
final int screenHeight = metrics.heightPixels;
+
mView = new View(mContext);
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
+ String description;
+ switch (mType) {
+ case TO_DESKTOP_INDICATOR:
+ description = "Desktop indicator";
+ break;
+ case TO_FULLSCREEN_INDICATOR:
+ description = "Fullscreen indicator";
+ break;
+ case TO_SPLIT_LEFT_INDICATOR:
+ description = "Split Left indicator";
+ break;
+ case TO_SPLIT_RIGHT_INDICATOR:
+ description = "Split Right indicator";
+ break;
+ default:
+ description = "Invalid indicator";
+ break;
+ }
mLeash = builder
- .setName("Fullscreen Indicator")
+ .setName(description)
.setContainerLayer()
.build();
t.show(mLeash);
@@ -97,14 +182,14 @@ public class DesktopModeVisualIndicator {
new WindowManager.LayoutParams(screenWidth, screenHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
- lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId);
+ lp.setTitle(description + " for Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
final WindowlessWindowManager windowManager = new WindowlessWindowManager(
mTaskInfo.configuration, mLeash,
null /* hostInputToken */);
mViewHost = new SurfaceControlViewHost(mContext,
mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
- "FullscreenVisualIndicator");
+ "DesktopModeVisualIndicator");
mViewHost.setView(mView, lp);
// We want this indicator to be behind the dragged task, but in front of all others.
t.setRelativeLayer(mLeash, mTaskSurface, -1);
@@ -116,24 +201,13 @@ public class DesktopModeVisualIndicator {
}
/**
- * Create fullscreen indicator and fades it in.
- */
- public void createFullscreenIndicator() {
- mIsFullscreen = true;
- mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimator(
- mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
- animator.start();
- }
-
- /**
- * Create a fullscreen indicator. Animator fades it in while expanding the bounds outwards.
+ * Create an indicator. Animator fades it in while expanding the bounds outwards.
*/
- public void createFullscreenIndicatorWithAnimatedBounds() {
- mIsFullscreen = true;
+ public void createIndicatorWithAnimatedBounds() {
+ mIsFullscreen = mType == TO_FULLSCREEN_INDICATOR;
mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
final VisualIndicatorAnimator animator = VisualIndicatorAnimator
- .toFullscreenAnimatorWithAnimatedBounds(mView,
+ .animateBounds(mView, mType,
mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
}
@@ -143,6 +217,7 @@ public class DesktopModeVisualIndicator {
*/
public void transitionFullscreenIndicatorToFreeform() {
mIsFullscreen = false;
+ mType = TO_DESKTOP_INDICATOR;
final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
@@ -153,6 +228,7 @@ public class DesktopModeVisualIndicator {
*/
public void transitionFreeformIndicatorToFullscreen() {
mIsFullscreen = true;
+ mType = TO_FULLSCREEN_INDICATOR;
final VisualIndicatorAnimator animator =
VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
@@ -160,6 +236,14 @@ public class DesktopModeVisualIndicator {
}
/**
+ * Determine if a MotionEvent is in the same range that enabled the indicator.
+ * Used to dismiss the indicator when a transition will no longer result from releasing.
+ */
+ public boolean eventOutsideRange(float x, float y) {
+ return !mIndicatorRange.contains((int) x, (int) y);
+ }
+
+ /**
* Release the indicator and its components when it is no longer needed.
*/
public void releaseVisualIndicator(SurfaceControl.Transaction t) {
@@ -210,32 +294,45 @@ public class DesktopModeVisualIndicator {
* @param view the view for this indicator
* @param displayLayout information about the display the transitioning task is currently on
*/
- public static VisualIndicatorAnimator toFullscreenAnimator(@NonNull View view,
- @NonNull DisplayLayout displayLayout) {
- final Rect bounds = getMaxBounds(displayLayout);
+ public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
+ @NonNull View view, @NonNull DisplayLayout displayLayout) {
+ final int padding = displayLayout.stableInsets().top;
+ Rect startBounds = new Rect(padding, padding,
+ displayLayout.width() - padding, displayLayout.height() - padding);
+ view.getBackground().setBounds(startBounds);
+
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, bounds, bounds);
+ view, startBounds, getMaxBounds(startBounds));
animator.setInterpolator(new DecelerateInterpolator());
setupIndicatorAnimation(animator);
return animator;
}
-
- /**
- * Create animator for visual indicator of fullscreen transition
- *
- * @param view the view for this indicator
- * @param displayLayout information about the display the transitioning task is currently on
- */
- public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
- @NonNull View view, @NonNull DisplayLayout displayLayout) {
+ public static VisualIndicatorAnimator animateBounds(
+ @NonNull View view, int type, @NonNull DisplayLayout displayLayout) {
final int padding = displayLayout.stableInsets().top;
- Rect startBounds = new Rect(padding, padding,
- displayLayout.width() - padding, displayLayout.height() - padding);
+ Rect startBounds = new Rect();
+ switch (type) {
+ case TO_FULLSCREEN_INDICATOR:
+ startBounds.set(padding, padding,
+ displayLayout.width() - padding,
+ displayLayout.height() - padding);
+ break;
+ case TO_SPLIT_LEFT_INDICATOR:
+ startBounds.set(padding, padding,
+ displayLayout.width() / 2 - padding,
+ displayLayout.height() - padding);
+ break;
+ case TO_SPLIT_RIGHT_INDICATOR:
+ startBounds.set(displayLayout.width() / 2 + padding, padding,
+ displayLayout.width() - padding,
+ displayLayout.height() - padding);
+ break;
+ }
view.getBackground().setBounds(startBounds);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, getMaxBounds(displayLayout));
+ view, startBounds, getMaxBounds(startBounds));
animator.setInterpolator(new DecelerateInterpolator());
setupIndicatorAnimation(animator);
return animator;
@@ -252,12 +349,13 @@ public class DesktopModeVisualIndicator {
final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
final int width = displayLayout.width();
final int height = displayLayout.height();
+ Rect startBounds = new Rect(0, 0, width, height);
Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
(int) (adjustmentPercentage * height / 2),
(int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
(int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, getMaxBounds(displayLayout), endBounds);
+ view, startBounds, endBounds);
animator.setInterpolator(new DecelerateInterpolator());
setupIndicatorAnimation(animator);
return animator;
@@ -310,21 +408,17 @@ public class DesktopModeVisualIndicator {
}
/**
- * Return the max bounds of a fullscreen indicator
+ * Return the max bounds of a visual indicator
*/
- private static Rect getMaxBounds(@NonNull DisplayLayout displayLayout) {
- final int padding = displayLayout.stableInsets().top;
- final int width = displayLayout.width() - 2 * padding;
- final int height = displayLayout.height() - 2 * padding;
- Rect endBounds = new Rect((int) (padding
- - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
- (int) (padding
- - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)),
- (int) (displayLayout.width() - padding
- + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
- (int) (displayLayout.height() - padding
- + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)));
- return endBounds;
+ private static Rect getMaxBounds(Rect startBounds) {
+ return new Rect((int) (startBounds.left
+ - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())),
+ (int) (startBounds.top
+ - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height())),
+ (int) (startBounds.right
+ + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())),
+ (int) (startBounds.bottom
+ + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height())));
}
}
}
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 8d1433595223..4e418e11d226 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
@@ -16,19 +16,25 @@
package com.android.wm.shell.desktopmode
+import android.R
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
+import android.content.res.TypedArray
import android.graphics.Point
+import android.graphics.PointF
import android.graphics.Rect
import android.graphics.Region
import android.os.IBinder
import android.os.SystemProperties
+import android.util.DisplayMetrics.DENSITY_DEFAULT
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
@@ -36,7 +42,6 @@ import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
-import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -44,18 +49,28 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ExecutorUtils
import com.android.wm.shell.common.ExternalInterfaceBinder
+import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.RemoteCallable
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.annotations.ExternalThread
import com.android.wm.shell.common.annotations.ShellMainThread
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.ShellSharedConstants
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -63,6 +78,7 @@ import java.util.function.Consumer
class DesktopTasksController(
private val context: Context,
shellInit: ShellInit,
+ private val shellCommandHandler: ShellCommandHandler,
private val shellController: ShellController,
private val displayController: DisplayController,
private val shellTaskOrganizer: ShellTaskOrganizer,
@@ -71,7 +87,10 @@ class DesktopTasksController(
private val transitions: Transitions,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
+ private val toggleResizeDesktopTaskTransitionHandler:
+ ToggleResizeDesktopTaskTransitionHandler,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val launchAdjacentController: LaunchAdjacentController,
@ShellMainThread private val mainExecutor: ShellExecutor
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
@@ -82,6 +101,22 @@ class DesktopTasksController(
visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
}
+ private val taskVisibilityListener = object : VisibleTasksListener {
+ override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+ launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks
+ }
+ }
+
+ private val transitionAreaHeight
+ get() = context.resources.getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)
+
+ private val transitionAreaWidth
+ get() = context.resources.getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_width)
+
+ // This is public to avoid cyclic dependency; it is set by SplitScreenController
+ lateinit var splitScreenController: SplitScreenController
init {
desktopMode = DesktopModeImpl()
@@ -92,12 +127,14 @@ class DesktopTasksController(
private fun onInit() {
KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
+ shellCommandHandler.addDumpCallback(this::dump, this)
shellController.addExternalInterface(
ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
{ createExternalInterface() },
this
)
transitions.addHandler(this)
+ desktopModeTaskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
}
/** Show all tasks, that are part of the desktop, on top of launcher */
@@ -118,78 +155,139 @@ class DesktopTasksController(
}
}
+ /**
+ * Stash desktop tasks on display with id [displayId].
+ *
+ * When desktop tasks are stashed, launcher home screen icons are fully visible. New apps
+ * launched in this state will be added to the desktop. Existing desktop tasks will be brought
+ * back to front during the launch.
+ */
+ fun stashDesktopApps(displayId: Int) {
+ if (DesktopModeStatus.isStashingEnabled()) {
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
+ desktopModeTaskRepository.setStashed(displayId, true)
+ }
+ }
+
+ /**
+ * Clear the stashed state for the given display
+ */
+ fun hideStashedDesktopApps(displayId: Int) {
+ if (DesktopModeStatus.isStashingEnabled()) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: hideStashedApps displayId=%d",
+ displayId
+ )
+ desktopModeTaskRepository.setStashed(displayId, false)
+ }
+ }
+
/** Get number of tasks that are marked as visible */
fun getVisibleTaskCount(displayId: Int): Int {
return desktopModeTaskRepository.getVisibleTaskCount(displayId)
}
/** Move a task with given `taskId` to desktop */
- fun moveToDesktop(taskId: Int) {
- shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task) }
+ fun moveToDesktop(
+ decor: DesktopModeWindowDecoration,
+ taskId: Int,
+ wct: WindowContainerTransaction = WindowContainerTransaction()
+ ) {
+ shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
+ task -> moveToDesktop(decor, task, wct)
+ }
}
- /** Move a task to desktop */
- fun moveToDesktop(task: RunningTaskInfo) {
+ /**
+ * Move a task to desktop
+ */
+ fun moveToDesktop(
+ decor: DesktopModeWindowDecoration,
+ task: RunningTaskInfo,
+ wct: WindowContainerTransaction = WindowContainerTransaction()
+ ) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToDesktop taskId=%d",
task.taskId
)
- val wct = WindowContainerTransaction()
// Bring other apps to front first
bringDesktopAppsToFront(task.displayId, wct)
- addMoveToDesktopChanges(wct, task.token)
+ addMoveToDesktopChanges(wct, task)
+
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
/**
- * Moves a single task to freeform and sets the taskBounds to the passed in bounds,
- * startBounds
+ * The first part of the animated move to desktop transition. Applies the changes to move task
+ * to desktop mode and sets the taskBounds to the passed in bounds, startBounds. This is
+ * followed with a call to {@link finishMoveToDesktop} or {@link cancelMoveToDesktop}.
*/
- fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) {
+ fun startMoveToDesktop(
+ taskInfo: RunningTaskInfo,
+ startBounds: Rect,
+ dragToDesktopValueAnimator: MoveToDesktopAnimator
+ ) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToFreeform with bounds taskId=%d",
+ "DesktopTasksController: startMoveToDesktop taskId=%d",
taskInfo.taskId
)
val wct = WindowContainerTransaction()
moveHomeTaskToFront(wct)
- addMoveToDesktopChanges(wct, taskInfo.getToken())
+ addMoveToDesktopChanges(wct, taskInfo)
wct.setBounds(taskInfo.token, startBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback)
+ enterDesktopTaskTransitionHandler.startMoveToDesktop(wct, dragToDesktopValueAnimator,
+ mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
- /** Brings apps to front and sets freeform task bounds */
- private fun moveToDesktopWithAnimation(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
+ /**
+ * The second part of the animated move to desktop transition, called after
+ * {@link startMoveToDesktop}. Brings apps to front and sets freeform task bounds.
+ */
+ private fun finalizeMoveToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToDesktop with animation taskId=%d",
+ "DesktopTasksController: finalizeMoveToDesktop taskId=%d",
taskInfo.taskId
)
val wct = WindowContainerTransaction()
bringDesktopAppsToFront(taskInfo.displayId, wct)
- addMoveToDesktopChanges(wct, taskInfo.getToken())
+ addMoveToDesktopChanges(wct, taskInfo)
wct.setBounds(taskInfo.token, freeformBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
+ enterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct,
+ mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
releaseVisualIndicator()
}
}
+ /**
+ * Perform needed cleanup transaction once animation is complete. Bounds need to be set
+ * here instead of initial wct to both avoid flicker and to have task bounds to use for
+ * the staging animation.
+ *
+ * @param taskInfo task entering split that requires a bounds update
+ */
+ fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+ val wct = WindowContainerTransaction()
+ wct.setBounds(taskInfo.token, Rect())
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+
/** Move a task with given `taskId` to fullscreen */
fun moveToFullscreen(taskId: Int) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
@@ -204,7 +302,25 @@ class DesktopTasksController(
)
val wct = WindowContainerTransaction()
- addMoveToFullscreenChanges(wct, task.token)
+ addMoveToFullscreenChanges(wct, task)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ /** Move a desktop app to split screen. */
+ fun moveToSplit(task: RunningTaskInfo) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToSplit taskId=%d",
+ task.taskId
+ )
+ val wct = WindowContainerTransaction()
+ wct.setWindowingMode(task.token, WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW)
+ wct.setBounds(task.token, Rect())
+ wct.setDensityDpi(task.token, getDefaultDensityDpi())
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -213,21 +329,30 @@ class DesktopTasksController(
}
/**
- * Move a task to fullscreen after being dragged from fullscreen and released back into
- * status bar area
+ * The second part of the animated move to desktop transition, called after
+ * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
+ * and released back into status bar area.
*/
- fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) {
+ fun cancelMoveToDesktop(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: cancelMoveToFreeform taskId=%d",
- task.taskId
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: cancelMoveToDesktop taskId=%d",
+ task.taskId
)
val wct = WindowContainerTransaction()
- addMoveToFullscreenChanges(wct, task.token)
+ wct.setBounds(task.token, Rect())
+
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, position,
- mOnAnimationFinishedCallback)
+ enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct,
+ moveToDesktopAnimator) { t ->
+ val callbackWCT = WindowContainerTransaction()
+ visualIndicator?.releaseVisualIndicator(t)
+ visualIndicator = null
+ addMoveToFullscreenChanges(callbackWCT, task)
+ transitions.startTransition(TRANSIT_CHANGE, callbackWCT, null /* handler */)
+ }
} else {
+ addMoveToFullscreenChanges(wct, task)
shellTaskOrganizer.applyTransaction(wct)
releaseVisualIndicator()
}
@@ -240,7 +365,7 @@ class DesktopTasksController(
task.taskId
)
val wct = WindowContainerTransaction()
- addMoveToFullscreenChanges(wct, task.token)
+ addMoveToFullscreenChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
exitDesktopTaskTransitionHandler.startTransition(
@@ -252,6 +377,11 @@ class DesktopTasksController(
}
/** Move a task to the front */
+ fun moveTaskToFront(taskId: Int) {
+ shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveTaskToFront(task) }
+ }
+
+ /** Move a task to the front */
fun moveTaskToFront(taskInfo: RunningTaskInfo) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
@@ -331,6 +461,49 @@ class DesktopTasksController(
}
}
+ /** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */
+ fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+
+ val stableBounds = Rect()
+ displayLayout.getStableBounds(stableBounds)
+ val destinationBounds = Rect()
+ if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
+ // The desktop task is currently occupying the whole stable bounds, toggle to the
+ // default bounds.
+ getDefaultDesktopTaskBounds(
+ density = taskInfo.configuration.densityDpi.toFloat() / DENSITY_DEFAULT,
+ stableBounds = stableBounds,
+ outBounds = destinationBounds
+ )
+ } else {
+ // Toggle to the stable bounds.
+ destinationBounds.set(stableBounds)
+ }
+
+ val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ toggleResizeDesktopTaskTransitionHandler.startTransition(
+ wct,
+ taskInfo.taskId,
+ windowDecor
+ )
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) {
+ val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt()
+ val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt()
+ outBounds.set(0, 0, width, height)
+ // Center the task in stable bounds
+ outBounds.offset(
+ stableBounds.centerX() - outBounds.centerX(),
+ stableBounds.centerY() - outBounds.centerY()
+ )
+ }
+
/**
* Get windowing move for a given `taskId`
*
@@ -397,91 +570,205 @@ class DesktopTasksController(
transition: IBinder,
request: TransitionRequestInfo
): WindowContainerTransaction? {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: handleRequest request=%s",
+ request
+ )
// Check if we should skip handling this transition
+ var reason = ""
val triggerTask = request.triggerTask
val shouldHandleRequest =
when {
// Only handle open or to front transitions
- request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false
+ request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> {
+ reason = "transition type not handled (${request.type})"
+ false
+ }
// Only handle when it is a task transition
- triggerTask == null -> false
+ triggerTask == null -> {
+ reason = "triggerTask is null"
+ false
+ }
// Only handle standard type tasks
- triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> false
+ triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> {
+ reason = "activityType not handled (${triggerTask.activityType})"
+ false
+ }
// Only handle fullscreen or freeform tasks
triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
- triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> false
+ triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
+ reason = "windowingMode not handled (${triggerTask.windowingMode})"
+ false
+ }
// Otherwise process it
else -> true
}
if (!shouldHandleRequest) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: skipping handleRequest reason=%s",
+ reason
+ )
return null
}
- if (triggerTask == null) {
- return null
- }
+ val result = triggerTask?.let { task ->
+ when {
+ // If display has tasks stashed, handle as stashed launch
+ desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
+ // Check if fullscreen task should be updated
+ task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
+ // Check if freeform task should be updated
+ task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
+ else -> {
+ null
+ }
+ }
+ }
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: handleRequest result=%s",
+ result ?: "null"
+ )
+ return result
+ }
- val activeTasks = desktopModeTaskRepository.getActiveTasks(triggerTask.displayId)
+ /**
+ * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
+ * This is intended to be used when desktop mode is part of another animation but isn't, itself,
+ * animating.
+ */
+ fun syncSurfaceState(
+ info: TransitionInfo,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ // Add rounded corners to freeform windows
+ val ta: TypedArray = context.obtainStyledAttributes(
+ intArrayOf(R.attr.dialogCornerRadius))
+ val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
+ ta.recycle()
+ info.changes
+ .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
+ .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
+ }
- // Check if we should switch a fullscreen task to freeform
- if (triggerTask.windowingMode == WINDOWING_MODE_FULLSCREEN) {
- // If there are any visible desktop tasks, switch the task to freeform
- if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
- KtProtoLog.d(
+ private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
+ val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
+ if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
+ KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: switch fullscreen task to freeform on transition" +
- " taskId=%d",
- triggerTask.taskId
- )
- return WindowContainerTransaction().also { wct ->
- addMoveToDesktopChanges(wct, triggerTask.token)
- }
+ "DesktopTasksController: switch freeform task to fullscreen oon transition" +
+ " taskId=%d",
+ task.taskId
+ )
+ return WindowContainerTransaction().also { wct ->
+ addMoveToFullscreenChanges(wct, task)
}
}
+ return null
+ }
- // CHeck if we should switch a freeform task to fullscreen
- if (triggerTask.windowingMode == WINDOWING_MODE_FREEFORM) {
- // If no visible desktop tasks, switch this task to freeform as the transition came
- // outside of this controller
- if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
- KtProtoLog.d(
+ private fun handleFullscreenTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
+ val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
+ if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+ KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: switch freeform task to fullscreen oon transition" +
- " taskId=%d",
- triggerTask.taskId
- )
- return WindowContainerTransaction().also { wct ->
- addMoveToFullscreenChanges(wct, triggerTask.token)
- }
+ "DesktopTasksController: switch fullscreen task to freeform on transition" +
+ " taskId=%d",
+ task.taskId
+ )
+ return WindowContainerTransaction().also { wct ->
+ addMoveToDesktopChanges(wct, task)
}
}
return null
}
+ private fun handleStashedTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: launch apps with stashed on transition taskId=%d",
+ task.taskId
+ )
+ val wct = WindowContainerTransaction()
+ bringDesktopAppsToFront(task.displayId, wct)
+ addMoveToDesktopChanges(wct, task)
+ desktopModeTaskRepository.setStashed(task.displayId, false)
+ return wct
+ }
+
private fun addMoveToDesktopChanges(
wct: WindowContainerTransaction,
- token: WindowContainerToken
+ taskInfo: RunningTaskInfo
) {
- wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM)
- wct.reorder(token, true /* onTop */)
+ val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
+ val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+ // Display windowing is freeform, set to undefined and inherit it
+ WINDOWING_MODE_UNDEFINED
+ } else {
+ WINDOWING_MODE_FREEFORM
+ }
+ wct.setWindowingMode(taskInfo.token, targetWindowingMode)
+ wct.reorder(taskInfo.token, true /* onTop */)
if (isDesktopDensityOverrideSet()) {
- wct.setDensityDpi(token, getDesktopDensityDpi())
+ wct.setDensityDpi(taskInfo.token, getDesktopDensityDpi())
}
}
private fun addMoveToFullscreenChanges(
wct: WindowContainerTransaction,
- token: WindowContainerToken
+ taskInfo: RunningTaskInfo
) {
- wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
- wct.setBounds(token, Rect())
+ val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
+ val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // Display windowing is fullscreen, set to undefined and inherit it
+ WINDOWING_MODE_UNDEFINED
+ } else {
+ WINDOWING_MODE_FULLSCREEN
+ }
+ wct.setWindowingMode(taskInfo.token, targetWindowingMode)
+ wct.setBounds(taskInfo.token, Rect())
if (isDesktopDensityOverrideSet()) {
- wct.setDensityDpi(token, getFullscreenDensityDpi())
+ wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
+ }
+ }
+
+ /**
+ * Adds split screen changes to a transaction. Note that bounds are not reset here due to
+ * animation; see {@link onDesktopSplitSelectAnimComplete}
+ */
+ private fun addMoveToSplitChanges(
+ wct: WindowContainerTransaction,
+ taskInfo: RunningTaskInfo
+ ) {
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
+ // The task's density may have been overridden in freeform; revert it here as we don't
+ // want it overridden in multi-window.
+ wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
+ }
+
+ /**
+ * Requests a task be transitioned from desktop to split select. Applies needed windowing
+ * changes if this transition is enabled.
+ */
+ fun requestSplit(
+ taskInfo: RunningTaskInfo
+ ) {
+ val windowingMode = taskInfo.windowingMode
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM
+ ) {
+ val wct = WindowContainerTransaction()
+ addMoveToSplitChanges(wct, taskInfo)
+ splitScreenController.requestEnterSplitSelect(taskInfo, wct,
+ SPLIT_POSITION_BOTTOM_OR_RIGHT, taskInfo.configuration.windowConfiguration.bounds)
}
}
- private fun getFullscreenDensityDpi(): Int {
+ private fun getDefaultDensityDpi(): Int {
return context.resources.displayMetrics.densityDpi
}
@@ -501,26 +788,36 @@ class DesktopTasksController(
/**
* Perform checks required on drag move. Create/release fullscreen indicator as needed.
+ * Different sources for x and y coordinates are used due to different needs for each:
+ * We want split transitions to be based on input coordinates but fullscreen transition
+ * to be based on task edge coordinate.
*
* @param taskInfo the task being dragged.
* @param taskSurface SurfaceControl of dragged task.
- * @param y coordinate of dragged task. Used for checks against status bar height.
+ * @param inputCoordinate coordinates of input. Used for checks against left/right edge of screen.
+ * @param taskBounds bounds of dragged task. Used for checks against status bar height.
*/
fun onDragPositioningMove(
- taskInfo: RunningTaskInfo,
- taskSurface: SurfaceControl,
- y: Float
+ taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ inputCoordinate: PointF,
+ taskBounds: Rect
) {
- if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
- val statusBarHeight = getStatusBarHeight(taskInfo)
- if (y <= statusBarHeight && visualIndicator == null) {
- visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
- displayController, context, taskSurface, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer)
- visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
- } else if (y > statusBarHeight && visualIndicator != null) {
- releaseVisualIndicator()
- }
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+ if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return
+ var type = DesktopModeVisualIndicator.determineIndicatorType(inputCoordinate,
+ taskBounds, displayLayout, context)
+ if (type != DesktopModeVisualIndicator.INVALID_INDICATOR && visualIndicator == null) {
+ visualIndicator = DesktopModeVisualIndicator(
+ syncQueue, taskInfo,
+ displayController, context, taskSurface, shellTaskOrganizer,
+ rootTaskDisplayAreaOrganizer, type)
+ visualIndicator?.createIndicatorWithAnimatedBounds()
+ return
+ }
+ if (visualIndicator?.eventOutsideRange(inputCoordinate.x,
+ taskBounds.top.toFloat()) == true) {
+ releaseVisualIndicator()
}
}
@@ -528,16 +825,40 @@ class DesktopTasksController(
* Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
*
* @param taskInfo the task being dragged.
- * @param position position of surface when drag ends
+ * @param position position of surface when drag ends.
+ * @param inputCoordinate the coordinates of the motion event
+ * @param taskBounds the updated bounds of the task being dragged.
+ * @param windowDecor the window decoration for the task being dragged
*/
fun onDragPositioningEnd(
- taskInfo: RunningTaskInfo,
- position: Point
+ taskInfo: RunningTaskInfo,
+ position: Point,
+ inputCoordinate: PointF,
+ taskBounds: Rect,
+ windowDecor: DesktopModeWindowDecoration
) {
- val statusBarHeight = getStatusBarHeight(taskInfo)
- if (position.y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
+ return
+ }
+ if (taskBounds.top <= transitionAreaHeight) {
+ windowDecor.incrementRelayoutBlock()
moveToFullscreenWithAnimation(taskInfo, position)
}
+ if (inputCoordinate.x <= transitionAreaWidth) {
+ releaseVisualIndicator()
+ var wct = WindowContainerTransaction()
+ addMoveToSplitChanges(wct, taskInfo)
+ splitScreenController.requestEnterSplitSelect(taskInfo, wct,
+ SPLIT_POSITION_TOP_OR_LEFT, taskBounds)
+ }
+ if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width()
+ ?.minus(transitionAreaWidth) ?: return)) {
+ releaseVisualIndicator()
+ var wct = WindowContainerTransaction()
+ addMoveToSplitChanges(wct, taskInfo)
+ splitScreenController.requestEnterSplitSelect(taskInfo, wct,
+ SPLIT_POSITION_BOTTOM_OR_RIGHT, taskBounds)
+ }
}
/**
@@ -553,16 +874,16 @@ class DesktopTasksController(
taskSurface: SurfaceControl,
y: Float
) {
- // If the motion event is above the status bar, return since we do not need to show the
- // visual indicator at this point.
- if (y < getStatusBarHeight(taskInfo)) {
+ // If the motion event is above the status bar and the visual indicator is not yet visible,
+ // return since we do not need to show the visual indicator at this point.
+ if (y < getStatusBarHeight(taskInfo) && visualIndicator == null) {
return
}
if (visualIndicator == null) {
visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
displayController, context, taskSurface, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer)
- visualIndicator?.createFullscreenIndicator()
+ rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR)
+ visualIndicator?.createIndicatorWithAnimatedBounds()
}
val indicator = visualIndicator ?: return
if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
@@ -584,7 +905,7 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
freeformBounds: Rect
) {
- moveToDesktopWithAnimation(taskInfo, freeformBounds)
+ finalizeMoveToDesktop(taskInfo, freeformBounds)
}
private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
@@ -636,6 +957,12 @@ class DesktopTasksController(
desktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor)
}
+ private fun dump(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ pw.println("${prefix}DesktopTasksController")
+ desktopModeTaskRepository.dump(pw, innerPrefix)
+ }
+
/** The interface for calls from outside the shell, within the host process. */
@ExternalThread
private inner class DesktopModeImpl : DesktopMode {
@@ -662,8 +989,51 @@ class DesktopTasksController(
@BinderThread
private class IDesktopModeImpl(private var controller: DesktopTasksController?) :
IDesktopMode.Stub(), ExternalInterfaceBinder {
+
+ private lateinit var remoteListener:
+ SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>
+
+ private val listener: VisibleTasksListener = object : VisibleTasksListener {
+ override fun onVisibilityChanged(displayId: Int, visible: Boolean) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: onVisibilityChanged display=%d visible=%b",
+ displayId,
+ visible
+ )
+ remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) }
+ }
+
+ override fun onStashedChanged(displayId: Int, stashed: Boolean) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: onStashedChanged display=%d stashed=%b",
+ displayId,
+ stashed
+ )
+ remoteListener.call { l -> l.onStashedChanged(displayId, stashed) }
+ }
+ }
+
+ init {
+ remoteListener =
+ SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>(
+ controller,
+ { c ->
+ c.desktopModeTaskRepository.addVisibleTasksListener(
+ listener,
+ c.mainExecutor
+ )
+ },
+ { c ->
+ c.desktopModeTaskRepository.removeVisibleTasksListener(listener)
+ }
+ )
+ }
+
/** Invalidates this instance, preventing future calls from updating the controller. */
override fun invalidate() {
+ remoteListener.unregister()
controller = null
}
@@ -674,6 +1044,27 @@ class DesktopTasksController(
) { c -> c.showDesktopApps(displayId) }
}
+ override fun stashDesktopApps(displayId: Int) {
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "stashDesktopApps"
+ ) { c -> c.stashDesktopApps(displayId) }
+ }
+
+ override fun hideStashedDesktopApps(displayId: Int) {
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "hideStashedDesktopApps"
+ ) { c -> c.hideStashedDesktopApps(displayId) }
+ }
+
+ override fun showDesktopApp(taskId: Int) {
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "showDesktopApp"
+ ) { c -> c.moveTaskToFront(taskId) }
+ }
+
override fun getVisibleTaskCount(displayId: Int): Int {
val result = IntArray(1)
ExecutorUtils.executeRemoteCallWithTaskPermission(
@@ -684,13 +1075,40 @@ class DesktopTasksController(
)
return result[0]
}
+
+ override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "onDesktopSplitSelectAnimComplete"
+ ) { c -> c.onDesktopSplitSelectAnimComplete(taskInfo) }
+ }
+
+ override fun setTaskListener(listener: IDesktopTaskListener?) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: set task listener=%s",
+ listener ?: "null"
+ )
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "setTaskListener"
+ ) { _ -> listener?.let { remoteListener.register(it) } ?: remoteListener.unregister() }
+ }
}
companion object {
private val DESKTOP_DENSITY_OVERRIDE =
- SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
+ SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284)
private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
+ // Override default freeform task width when desktop mode is enabled. In dips.
+ private val DESKTOP_MODE_DEFAULT_WIDTH_DP =
+ SystemProperties.getInt("persist.wm.debug.desktop_mode.default_width", 840)
+
+ // Override default freeform task height when desktop mode is enabled. In dips.
+ private val DESKTOP_MODE_DEFAULT_HEIGHT_DP =
+ SystemProperties.getInt("persist.wm.debug.desktop_mode.default_height", 630)
+
/**
* Check if desktop density override is enabled
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 3733b919e366..1128d91bb5ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -17,15 +17,16 @@
package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
-import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
+import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -36,6 +37,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
import java.util.ArrayList;
import java.util.List;
@@ -48,18 +51,18 @@ import java.util.function.Supplier;
*/
public class EnterDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
+ private static final String TAG = "EnterDesktopTaskTransitionHandler";
private final Transitions mTransitions;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
- // The size of the screen during drag relative to the fullscreen size
- public static final float DRAG_FREEFORM_SCALE = 0.4f;
// The size of the screen after drag relative to the fullscreen size
public static final float FINAL_FREEFORM_SCALE = 0.6f;
public static final int FREEFORM_ANIMATION_DURATION = 336;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
- private Point mPosition;
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
+ private MoveToDesktopAnimator mMoveToDesktopAnimator;
+ private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
public EnterDesktopTaskTransitionHandler(
Transitions transitions) {
@@ -79,7 +82,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
* @param wct WindowContainerTransaction for transition
* @param onAnimationEndCallback to be called after animation
*/
- public void startTransition(@WindowManager.TransitionType int type,
+ private void startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct,
Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
mOnAnimationFinishedCallback = onAnimationEndCallback;
@@ -88,19 +91,58 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
}
/**
+ * Starts Transition of type TRANSIT_START_DRAG_TO_DESKTOP_MODE
+ * @param wct WindowContainerTransaction for transition
+ * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+ * to desktop animation
+ * @param onAnimationEndCallback to be called after animation
+ */
+ public void startMoveToDesktop(@NonNull WindowContainerTransaction wct,
+ @NonNull MoveToDesktopAnimator moveToDesktopAnimator,
+ Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+ mMoveToDesktopAnimator = moveToDesktopAnimator;
+ startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct,
+ onAnimationEndCallback);
+ }
+
+ /**
+ * Starts Transition of type TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
+ * @param wct WindowContainerTransaction for transition
+ * @param onAnimationEndCallback to be called after animation
+ */
+ public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct,
+ Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+ startTransition(Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, wct,
+ onAnimationEndCallback);
+ }
+
+ /**
* Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
* @param wct WindowContainerTransaction for transition
- * @param position Position of task when transition is triggered
+ * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+ * to desktop animation
* @param onAnimationEndCallback to be called after animation
*/
public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
- Point position,
+ MoveToDesktopAnimator moveToDesktopAnimator,
Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
- mPosition = position;
- startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct,
+ mMoveToDesktopAnimator = moveToDesktopAnimator;
+ startTransition(Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE, wct,
onAnimationEndCallback);
}
+ /**
+ * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP
+ * @param wct WindowContainerTransaction for transition
+ * @param decor {@link DesktopModeWindowDecoration} of task being animated
+ */
+ public void moveToDesktop(@NonNull WindowContainerTransaction wct,
+ DesktopModeWindowDecoration decor) {
+ mDesktopModeWindowDecoration = decor;
+ startTransition(Transitions.TRANSIT_MOVE_TO_DESKTOP, wct,
+ null /* onAnimationEndCallback */);
+ }
+
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startT,
@@ -140,103 +182,207 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
}
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (type == Transitions.TRANSIT_ENTER_FREEFORM
+ if (type == Transitions.TRANSIT_MOVE_TO_DESKTOP
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
- // to null and we don't require an animation
- final SurfaceControl sc = change.getLeash();
- startT.setWindowCrop(sc, null);
- startT.apply();
- mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null, null));
- return true;
+ return animateMoveToDesktop(change, startT, finishCallback);
}
- Rect endBounds = change.getEndAbsBounds();
- if (type == Transitions.TRANSIT_ENTER_DESKTOP_MODE
+ if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ return animateStartDragToDesktopMode(change, startT, finishT, finishCallback);
+ }
+
+ final Rect endBounds = change.getEndAbsBounds();
+ if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
&& !endBounds.isEmpty()) {
- // This Transition animates a task to freeform bounds after being dragged into freeform
- // mode and brings the remaining freeform tasks to front
- final SurfaceControl sc = change.getLeash();
- startT.setWindowCrop(sc, endBounds.width(),
- endBounds.height());
- startT.apply();
-
- // We want to find the scale of the current bounds relative to the end bounds. The
- // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
- // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
- // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
- final ValueAnimator animator =
- ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
- animator.setDuration(FREEFORM_ANIMATION_DURATION);
- final SurfaceControl.Transaction t = mTransactionSupplier.get();
- animator.addUpdateListener(animation -> {
- final float animationValue = (float) animation.getAnimatedValue();
- t.setScale(sc, animationValue, animationValue);
-
- final float animationWidth = endBounds.width() * animationValue;
- final float animationHeight = endBounds.height() * animationValue;
- final int animationX = endBounds.centerX() - (int) (animationWidth / 2);
- final int animationY = endBounds.centerY() - (int) (animationHeight / 2);
-
- t.setPosition(sc, animationX, animationY);
- t.apply();
- });
-
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mOnAnimationFinishedCallback != null) {
- mOnAnimationFinishedCallback.accept(finishT);
- }
- mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null, null));
- }
- });
+ return animateFinalizeDragToDesktopMode(change, startT, finishT, finishCallback,
+ endBounds);
+ }
- animator.start();
- return true;
+ if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ return animateCancelDragToDesktopMode(change, startT, finishT, finishCallback,
+ endBounds);
}
- if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && mPosition != null) {
- // This Transition animates a task to fullscreen after being dragged from the status
- // bar and then released back into the status bar area
- final SurfaceControl sc = change.getLeash();
- // Hide the first (fullscreen) frame because the animation will start from the smaller
- // scale size.
- startT.hide(sc)
- .setWindowCrop(sc, endBounds.width(), endBounds.height())
- .apply();
+ return false;
+ }
- final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
- animator.setDuration(FREEFORM_ANIMATION_DURATION);
- final SurfaceControl.Transaction t = mTransactionSupplier.get();
- animator.addUpdateListener(animation -> {
- final float scale = (float) animation.getAnimatedValue();
- t.setPosition(sc, mPosition.x * (1 - scale), mPosition.y * (1 - scale))
- .setScale(sc, scale, scale)
- .show(sc)
- .apply();
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mOnAnimationFinishedCallback != null) {
- mOnAnimationFinishedCallback.accept(finishT);
- }
- mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null, null));
+ private boolean animateMoveToDesktop(
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (mDesktopModeWindowDecoration == null) {
+ Slog.e(TAG, "Window Decoration is not available for this transition");
+ return false;
+ }
+
+ final SurfaceControl leash = change.getLeash();
+ final Rect startBounds = change.getStartAbsBounds();
+ startT.setPosition(leash, startBounds.left, startBounds.right)
+ .setWindowCrop(leash, startBounds.width(), startBounds.height())
+ .show(leash);
+ mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds);
+
+ final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(),
+ change.getStartAbsBounds(), change.getEndAbsBounds());
+ animator.setDuration(FREEFORM_ANIMATION_DURATION);
+ SurfaceControl.Transaction t = mTransactionSupplier.get();
+ animator.addUpdateListener(animation -> {
+ final Rect animationValue = (Rect) animator.getAnimatedValue();
+ t.setPosition(leash, animationValue.left, animationValue.right)
+ .setWindowCrop(leash, animationValue.width(), animationValue.height())
+ .show(leash);
+ mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue);
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mDesktopModeWindowDecoration.hideResizeVeil();
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null));
+ }
+ });
+ animator.start();
+ return true;
+ }
+
+ private boolean animateStartDragToDesktopMode(
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
+ // to null and we don't require an animation
+ final SurfaceControl sc = change.getLeash();
+ startT.setWindowCrop(sc, null);
+
+ if (mMoveToDesktopAnimator == null
+ || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+ Slog.e(TAG, "No animator available for this transition");
+ return false;
+ }
+
+ // Calculate and set position of the task
+ final PointF position = mMoveToDesktopAnimator.getPosition();
+ startT.setPosition(sc, position.x, position.y);
+ finishT.setPosition(sc, position.x, position.y);
+
+ startT.apply();
+
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null));
+
+ return true;
+ }
+
+ private boolean animateFinalizeDragToDesktopMode(
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull Rect endBounds) {
+ // This Transition animates a task to freeform bounds after being dragged into freeform
+ // mode and brings the remaining freeform tasks to front
+ final SurfaceControl sc = change.getLeash();
+ startT.setWindowCrop(sc, endBounds.width(),
+ endBounds.height());
+ startT.apply();
+
+ // End the animation that shrinks the window when task is first dragged from fullscreen
+ if (mMoveToDesktopAnimator != null) {
+ mMoveToDesktopAnimator.endAnimator();
+ }
+
+ // We want to find the scale of the current bounds relative to the end bounds. The
+ // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
+ // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
+ // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
+ final ValueAnimator animator =
+ ValueAnimator.ofFloat(
+ MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
+ animator.setDuration(FREEFORM_ANIMATION_DURATION);
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+ animator.addUpdateListener(animation -> {
+ final float animationValue = (float) animation.getAnimatedValue();
+ t.setScale(sc, animationValue, animationValue);
+
+ final float animationWidth = endBounds.width() * animationValue;
+ final float animationHeight = endBounds.height() * animationValue;
+ final int animationX = endBounds.centerX() - (int) (animationWidth / 2);
+ final int animationY = endBounds.centerY() - (int) (animationHeight / 2);
+
+ t.setPosition(sc, animationX, animationY);
+ t.apply();
+ });
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mOnAnimationFinishedCallback != null) {
+ mOnAnimationFinishedCallback.accept(finishT);
}
- });
- animator.start();
- return true;
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null));
+ }
+ });
+
+ animator.start();
+ return true;
+ }
+ private boolean animateCancelDragToDesktopMode(
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull Rect endBounds) {
+ // This Transition animates a task to fullscreen after being dragged from the status
+ // bar and then released back into the status bar area
+ final SurfaceControl sc = change.getLeash();
+ // Hide the first (fullscreen) frame because the animation will start from the smaller
+ // scale size.
+ startT.hide(sc)
+ .setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .apply();
+
+ if (mMoveToDesktopAnimator == null
+ || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+ Slog.e(TAG, "No animator available for this transition");
+ return false;
}
- return false;
+ // End the animation that shrinks the window when task is first dragged from fullscreen
+ mMoveToDesktopAnimator.endAnimator();
+
+ final ValueAnimator animator = new ValueAnimator();
+ animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f);
+ animator.setDuration(FREEFORM_ANIMATION_DURATION);
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+
+ // Get position of the task
+ final float x = mMoveToDesktopAnimator.getPosition().x;
+ final float y = mMoveToDesktopAnimator.getPosition().y;
+
+ animator.addUpdateListener(animation -> {
+ final float scale = (float) animation.getAnimatedValue();
+ t.setPosition(sc, x * (1 - scale), y * (1 - scale))
+ .setScale(sc, scale, scale)
+ .show(sc)
+ .apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mOnAnimationFinishedCallback != null) {
+ mOnAnimationFinishedCallback.accept(finishT);
+ }
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null));
+ }
+ });
+ animator.start();
+ return true;
}
@Nullable
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 3ad5edf0e604..7342bd1ae5de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -168,7 +168,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
mOnAnimationFinishedCallback.accept(finishT);
}
mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null, null));
+ () -> finishCallback.onTransitionFinished(null));
}
});
animator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 899d67267e69..47edfd455f5a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -16,6 +16,9 @@
package com.android.wm.shell.desktopmode;
+import android.app.ActivityManager.RunningTaskInfo;
+import com.android.wm.shell.desktopmode.IDesktopTaskListener;
+
/**
* Interface that is exposed to remote callers to manipulate desktop mode features.
*/
@@ -24,6 +27,21 @@ interface IDesktopMode {
/** Show apps on the desktop on the given display */
void showDesktopApps(int displayId);
+ /** Stash apps on the desktop to allow launching another app from home screen */
+ void stashDesktopApps(int displayId);
+
+ /** Hide apps that may be stashed */
+ void hideStashedDesktopApps(int displayId);
+
+ /** Bring task with the given id to front */
+ oneway void showDesktopApp(int taskId);
+
/** Get count of visible desktop tasks on the given display */
int getVisibleTaskCount(int displayId);
+
+ /** Perform cleanup transactions after the animation to split select is complete */
+ oneway void onDesktopSplitSelectAnimComplete(in RunningTaskInfo taskInfo);
+
+ /** Set listener that will receive callbacks about updates to desktop tasks */
+ oneway void setTaskListener(IDesktopTaskListener listener);
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
new file mode 100644
index 000000000000..39128a863ec9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+/**
+ * Allows external processes to register a listener in WMShell to get updates about desktop task
+ * state.
+ */
+interface IDesktopTaskListener {
+
+ /** Desktop task visibility has change. Visible if at least 1 task is visible. */
+ oneway void onVisibilityChanged(int displayId, boolean visible);
+
+ /** Desktop task stashed status has changed. */
+ oneway void onStashedChanged(int displayId, boolean stashed);
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
new file mode 100644
index 000000000000..9debb25ff296
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.animation.Animator
+import android.animation.RectEvaluator
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.os.IBinder
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import androidx.core.animation.addListener
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import java.util.function.Supplier
+
+/** Handles the animation of quick resizing of desktop tasks. */
+class ToggleResizeDesktopTaskTransitionHandler(
+ private val transitions: Transitions,
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>
+) : Transitions.TransitionHandler {
+
+ private val rectEvaluator = RectEvaluator(Rect())
+ private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>()
+
+ private var boundsAnimator: Animator? = null
+
+ constructor(
+ transitions: Transitions
+ ) : this(transitions, Supplier { SurfaceControl.Transaction() })
+
+ /** Starts a quick resize transition. */
+ fun startTransition(
+ wct: WindowContainerTransaction,
+ taskId: Int,
+ windowDecoration: DesktopModeWindowDecoration
+ ) {
+ // Pause relayout until the transition animation finishes.
+ windowDecoration.incrementRelayoutBlock()
+ transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
+ taskToDecorationMap.put(taskId, windowDecoration)
+ }
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback
+ ): Boolean {
+ val change = findRelevantChange(info)
+ val leash = change.leash
+ val taskId = checkNotNull(change.taskInfo).taskId
+ val startBounds = change.startAbsBounds
+ val endBounds = change.endAbsBounds
+ val windowDecor =
+ taskToDecorationMap.removeReturnOld(taskId)
+ ?: throw IllegalStateException("Window decoration not found for task $taskId")
+
+ val tx = transactionSupplier.get()
+ boundsAnimator?.cancel()
+ boundsAnimator =
+ ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds)
+ .setDuration(RESIZE_DURATION_MS)
+ .apply {
+ addListener(
+ onStart = {
+ startTransaction
+ .setPosition(
+ leash,
+ startBounds.left.toFloat(),
+ startBounds.top.toFloat()
+ )
+ .setWindowCrop(leash, startBounds.width(), startBounds.height())
+ .show(leash)
+ windowDecor.showResizeVeil(startTransaction, startBounds)
+ },
+ onEnd = {
+ finishTransaction
+ .setPosition(
+ leash,
+ endBounds.left.toFloat(),
+ endBounds.top.toFloat()
+ )
+ .setWindowCrop(leash, endBounds.width(), endBounds.height())
+ .show(leash)
+ windowDecor.hideResizeVeil()
+ finishCallback.onTransitionFinished(null)
+ boundsAnimator = null
+ }
+ )
+ addUpdateListener { anim ->
+ val rect = anim.animatedValue as Rect
+ tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
+ .setWindowCrop(leash, rect.width(), rect.height())
+ .show(leash)
+ windowDecor.updateResizeVeil(tx, rect)
+ }
+ start()
+ }
+ return true
+ }
+
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo
+ ): WindowContainerTransaction? {
+ return null
+ }
+
+ private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change {
+ val matchingChanges =
+ info.changes.filter { c ->
+ !isWallpaper(c) && isValidTaskChange(c) && c.mode == TRANSIT_CHANGE
+ }
+ if (matchingChanges.size != 1) {
+ throw IllegalStateException(
+ "Expected 1 relevant change but found: ${matchingChanges.size}"
+ )
+ }
+ return matchingChanges.first()
+ }
+
+ private fun isWallpaper(change: TransitionInfo.Change): Boolean {
+ return (change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0
+ }
+
+ private fun isValidTaskChange(change: TransitionInfo.Change): Boolean {
+ return change.taskInfo != null && change.taskInfo?.taskId != -1
+ }
+
+ companion object {
+ private const val RESIZE_DURATION_MS = 300L
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 99922fbc2d95..f9ea1d4e2a07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -21,11 +21,17 @@ This code itself will not compile by itself, but the `protologtool` will preproc
building to check the log state (is enabled) before printing the print format style log.
**Notes**
-- ProtoLogs currently only work from soong builds (ie. via make/mp). We need to reimplement the
- tool for use with SysUI-studio
+- ProtoLogs are only fully supported from soong builds (ie. via make/mp). In SysUI-studio it falls
+ back to log via Logcat
- Non-text ProtoLogs are not currently supported with the Shell library (you can't view them with
traces in Winscope)
+### Kotlin
+
+Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
+For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
+class which has a similar API to the Java ProtoLog class.
+
### Enabling ProtoLog command line logging
Run these commands to enable protologs for both WM Core and WM Shell to print to logcat.
```shell
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index be2489da3628..0bf8ec32c6c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -16,9 +16,6 @@
package com.android.wm.shell.draganddrop;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DragEvent.ACTION_DRAG_ENDED;
import static android.view.DragEvent.ACTION_DRAG_ENTERED;
@@ -38,6 +35,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
+import android.app.ActivityTaskManager;
import android.content.ClipDescription;
import android.content.ComponentCallbacks2;
import android.content.Context;
@@ -205,8 +203,6 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
final Context context = mDisplayController.getDisplayContext(displayId)
.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
final WindowManager wm = context.getSystemService(WindowManager.class);
-
- // TODO(b/169894807): Figure out the right layer for this, needs to be below the task bar
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
TYPE_APPLICATION_OVERLAY,
@@ -279,15 +275,11 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
}
if (event.getAction() == ACTION_DRAG_STARTED) {
- final boolean hasValidClipData = event.getClipData().getItemCount() > 0
- && (description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
- || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)
- || description.hasMimeType(MIMETYPE_APPLICATION_TASK));
- pd.isHandlingDrag = hasValidClipData;
+ pd.isHandlingDrag = DragUtils.canHandleDrag(event);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
pd.isHandlingDrag, event.getClipData().getItemCount(),
- getMimeTypes(description));
+ DragUtils.getMimeTypesConcatenated(description));
}
if (!pd.isHandlingDrag) {
@@ -300,10 +292,13 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
Slog.w(TAG, "Unexpected drag start during an active drag");
return false;
}
+ // TODO(b/290391688): Also update the session data with task stack changes
InstanceId loggerSessionId = mLogger.logStart(event);
pd.activeDragCount++;
- pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId),
- event.getClipData(), loggerSessionId);
+ pd.dragSession = new DragSession(mContext, ActivityTaskManager.getInstance(),
+ mDisplayController.getDisplayLayout(displayId), event.getClipData());
+ pd.dragSession.update();
+ pd.dragLayout.prepare(pd.dragSession, loggerSessionId);
setDropTargetWindowVisibility(pd, View.VISIBLE);
notifyDragStarted();
break;
@@ -324,7 +319,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
break;
}
case ACTION_DRAG_ENDED:
- // TODO(b/169894807): Ensure sure it's not possible to get ENDED without DROP
+ // TODO(b/290391688): Ensure sure it's not possible to get ENDED without DROP
// or EXITED
if (pd.dragLayout.hasDropped()) {
mLogger.logDrop();
@@ -362,17 +357,6 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
pd.setWindowVisibility(visibility);
}
- private String getMimeTypes(ClipDescription description) {
- String mimeTypes = "";
- for (int i = 0; i < description.getMimeTypeCount(); i++) {
- if (i > 0) {
- mimeTypes += ", ";
- }
- mimeTypes += description.getMimeType(i);
- }
- return mimeTypes;
- }
-
/**
* Returns if any displays are currently ready to handle a drag/drop.
*/
@@ -462,6 +446,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
// A count of the number of active drags in progress to ensure that we only hide the window
// when all the drag animations have completed
int activeDragCount;
+ // The active drag session
+ DragSession dragSession;
PerDisplay(int dispId, Context c, WindowManager w, FrameLayout rv, DragLayout dl) {
displayId = dispId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index fb08c878837a..e70768b6b752 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -21,7 +21,6 @@ import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVIT
import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
@@ -41,16 +40,13 @@ import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPL
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
-import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
-import android.app.WindowConfiguration;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -67,14 +63,12 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.List;
/**
* The policy for handling drag and drop operations to shell.
@@ -84,7 +78,6 @@ public class DragAndDropPolicy {
private static final String TAG = DragAndDropPolicy.class.getSimpleName();
private final Context mContext;
- private final ActivityTaskManager mActivityTaskManager;
private final Starter mStarter;
private final SplitScreenController mSplitScreen;
private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
@@ -94,14 +87,12 @@ public class DragAndDropPolicy {
private DragSession mSession;
public DragAndDropPolicy(Context context, SplitScreenController splitScreen) {
- this(context, ActivityTaskManager.getInstance(), splitScreen, new DefaultStarter(context));
+ this(context, splitScreen, new DefaultStarter(context));
}
@VisibleForTesting
- DragAndDropPolicy(Context context, ActivityTaskManager activityTaskManager,
- SplitScreenController splitScreen, Starter starter) {
+ DragAndDropPolicy(Context context, SplitScreenController splitScreen, Starter starter) {
mContext = context;
- mActivityTaskManager = activityTaskManager;
mSplitScreen = splitScreen;
mStarter = mSplitScreen != null ? mSplitScreen : starter;
}
@@ -109,11 +100,9 @@ public class DragAndDropPolicy {
/**
* Starts a new drag session with the given initial drag data.
*/
- void start(DisplayLayout displayLayout, ClipData data, InstanceId loggerSessionId) {
+ void start(DragSession session, InstanceId loggerSessionId) {
mLoggerSessionId = loggerSessionId;
- mSession = new DragSession(mActivityTaskManager, displayLayout, data);
- // TODO(b/169894807): Also update the session data with task stack changes
- mSession.update();
+ mSession = session;
RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION);
if (disallowHitRegion == null) {
mDisallowHitRegion.setEmpty();
@@ -123,13 +112,6 @@ public class DragAndDropPolicy {
}
/**
- * Returns the last running task.
- */
- ActivityManager.RunningTaskInfo getLatestRunningTask() {
- return mSession.runningTaskInfo;
- }
-
- /**
* Returns the number of targets.
*/
int getNumTargets() {
@@ -286,49 +268,6 @@ public class DragAndDropPolicy {
}
/**
- * Per-drag session data.
- */
- private static class DragSession {
- private final ActivityTaskManager mActivityTaskManager;
- private final ClipData mInitialDragData;
-
- final DisplayLayout displayLayout;
- Intent dragData;
- ActivityManager.RunningTaskInfo runningTaskInfo;
- @WindowConfiguration.WindowingMode
- int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
- @WindowConfiguration.ActivityType
- int runningTaskActType = ACTIVITY_TYPE_STANDARD;
- boolean dragItemSupportsSplitscreen;
-
- DragSession(ActivityTaskManager activityTaskManager,
- DisplayLayout dispLayout, ClipData data) {
- mActivityTaskManager = activityTaskManager;
- mInitialDragData = data;
- displayLayout = dispLayout;
- }
-
- /**
- * Updates the session data based on the current state of the system.
- */
- void update() {
- List<ActivityManager.RunningTaskInfo> tasks =
- mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
- if (!tasks.isEmpty()) {
- final ActivityManager.RunningTaskInfo task = tasks.get(0);
- runningTaskInfo = task;
- runningTaskWinMode = task.getWindowingMode();
- runningTaskActType = task.getActivityType();
- }
-
- final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
- dragItemSupportsSplitscreen = info == null
- || ActivityInfo.isResizeableMode(info.resizeMode);
- dragData = mInitialDragData.getItemAt(0).getIntent();
- }
- }
-
- /**
* Interface for actually committing the task launches.
*/
public interface Starter {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index fe42822ab6a1..205a455200bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -35,7 +35,6 @@ import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.StatusBarManager;
-import android.content.ClipData;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Color;
@@ -53,14 +52,13 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.ArrayList;
/**
- * Coordinates the visible drop targets for the current drag.
+ * Coordinates the visible drop targets for the current drag within a single display.
*/
public class DragLayout extends LinearLayout {
@@ -86,6 +84,7 @@ public class DragLayout extends LinearLayout {
private boolean mIsShowing;
private boolean mHasDropped;
+ private DragSession mSession;
@SuppressLint("WrongConstant")
public DragLayout(Context context, SplitScreenController splitScreenController,
@@ -182,16 +181,19 @@ public class DragLayout extends LinearLayout {
return mHasDropped;
}
- public void prepare(DisplayLayout displayLayout, ClipData initialData,
- InstanceId loggerSessionId) {
- mPolicy.start(displayLayout, initialData, loggerSessionId);
+ /**
+ * Called when a new drag is started.
+ */
+ public void prepare(DragSession session, InstanceId loggerSessionId) {
+ mPolicy.start(session, loggerSessionId);
+ mSession = session;
mHasDropped = false;
mCurrentTarget = null;
boolean alreadyInSplit = mSplitScreenController != null
&& mSplitScreenController.isSplitScreenVisible();
if (!alreadyInSplit) {
- ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask();
+ ActivityManager.RunningTaskInfo taskInfo1 = mSession.runningTaskInfo;
if (taskInfo1 != null) {
final int activityType = taskInfo1.getActivityType();
if (activityType == ACTIVITY_TYPE_STANDARD) {
@@ -356,7 +358,16 @@ public class DragLayout extends LinearLayout {
*/
public void hide(DragEvent event, Runnable hideCompleteCallback) {
mIsShowing = false;
- animateSplitContainers(false, hideCompleteCallback);
+ animateSplitContainers(false, () -> {
+ if (hideCompleteCallback != null) {
+ hideCompleteCallback.run();
+ }
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DROP:
+ case DragEvent.ACTION_DRAG_ENDED:
+ mSession = null;
+ }
+ });
// Reset the state if we previously force-ignore the bottom margin
mDropZoneView1.setForceIgnoreBottomMargin(false);
mDropZoneView2.setForceIgnoreBottomMargin(false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
new file mode 100644
index 000000000000..478b6a9d95f6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.content.Intent.EXTRA_USER;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.WindowConfiguration;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.common.DisplayLayout;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Per-drag session data.
+ */
+public class DragSession {
+ private final ActivityTaskManager mActivityTaskManager;
+ private final ClipData mInitialDragData;
+
+ final DisplayLayout displayLayout;
+ Intent dragData;
+ ActivityManager.RunningTaskInfo runningTaskInfo;
+ @WindowConfiguration.WindowingMode
+ int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
+ @WindowConfiguration.ActivityType
+ int runningTaskActType = ACTIVITY_TYPE_STANDARD;
+ boolean dragItemSupportsSplitscreen;
+
+ DragSession(Context context, ActivityTaskManager activityTaskManager,
+ DisplayLayout dispLayout, ClipData data) {
+ mActivityTaskManager = activityTaskManager;
+ mInitialDragData = data;
+ displayLayout = dispLayout;
+ }
+
+ /**
+ * Updates the session data based on the current state of the system.
+ */
+ void update() {
+ List<ActivityManager.RunningTaskInfo> tasks =
+ mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
+ if (!tasks.isEmpty()) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(0);
+ runningTaskInfo = task;
+ runningTaskWinMode = task.getWindowingMode();
+ runningTaskActType = task.getActivityType();
+ }
+
+ final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
+ dragItemSupportsSplitscreen = info == null
+ || ActivityInfo.isResizeableMode(info.resizeMode);
+ dragData = mInitialDragData.getItemAt(0).getIntent();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
new file mode 100644
index 000000000000..7c0883d2538f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop;
+
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+
+import android.content.ClipDescription;
+import android.view.DragEvent;
+
+/** Collection of utility classes for handling drag and drop. */
+public class DragUtils {
+ private static final String TAG = "DragUtils";
+
+ /**
+ * Returns whether we can handle this particular drag.
+ */
+ public static boolean canHandleDrag(DragEvent event) {
+ return event.getClipData().getItemCount() > 0
+ && (isAppDrag(event.getClipDescription()));
+ }
+
+ /**
+ * Returns whether this clip data description represents an app drag.
+ */
+ public static boolean isAppDrag(ClipDescription description) {
+ return description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
+ || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)
+ || description.hasMimeType(MIMETYPE_APPLICATION_TASK);
+ }
+
+ /**
+ * Returns a list of the mime types provided in the clip description.
+ */
+ public static String getMimeTypesConcatenated(ClipDescription description) {
+ String mimeTypes = "";
+ for (int i = 0; i < description.getMimeTypeCount(); i++) {
+ if (i > 0) {
+ mimeTypes += ", ";
+ }
+ mimeTypes += description.getMimeType(i);
+ }
+ return mimeTypes;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
deleted file mode 100644
index 73deea54e52f..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.draganddrop;
-
-import android.animation.ObjectAnimator;
-import android.animation.RectEvaluator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.IntProperty;
-import android.util.Property;
-import android.view.animation.Interpolator;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.graphics.ColorUtils;
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.R;
-
-/**
- * Drawable to draw the region that the target will have once it is dropped.
- */
-public class DropOutlineDrawable extends Drawable {
-
- private static final int BOUNDS_DURATION = 200;
- private static final int ALPHA_DURATION = 135;
-
- private final IntProperty<DropOutlineDrawable> ALPHA =
- new IntProperty<DropOutlineDrawable>("alpha") {
- @Override
- public void setValue(DropOutlineDrawable d, int alpha) {
- d.setAlpha(alpha);
- }
-
- @Override
- public Integer get(DropOutlineDrawable d) {
- return d.getAlpha();
- }
- };
-
- private final Property<DropOutlineDrawable, Rect> BOUNDS =
- new Property<DropOutlineDrawable, Rect>(Rect.class, "bounds") {
- @Override
- public void set(DropOutlineDrawable d, Rect bounds) {
- d.setRegionBounds(bounds);
- }
-
- @Override
- public Rect get(DropOutlineDrawable d) {
- return d.getRegionBounds();
- }
- };
-
- private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
- private ObjectAnimator mBoundsAnimator;
- private ObjectAnimator mAlphaAnimator;
-
- private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private final Rect mBounds = new Rect();
- private final float mCornerRadius;
- private final int mMaxAlpha;
- private int mColor;
-
- public DropOutlineDrawable(Context context) {
- super();
- // TODO(b/169894807): Use corner specific radii and maybe lower radius for non-edge corners
- mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mColor = context.getColor(R.color.drop_outline_background);
- mMaxAlpha = Color.alpha(mColor);
- // Initialize as hidden
- ALPHA.set(this, 0);
- }
-
- @Override
- public void setColorFilter(@Nullable ColorFilter colorFilter) {
- // Do nothing
- }
-
- @Override
- public void setAlpha(int alpha) {
- mColor = ColorUtils.setAlphaComponent(mColor, alpha);
- mPaint.setColor(mColor);
- invalidateSelf();
- }
-
- @Override
- public int getAlpha() {
- return Color.alpha(mColor);
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- invalidateSelf();
- }
-
- @Override
- public void draw(@NonNull Canvas canvas) {
- canvas.drawRoundRect(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom,
- mCornerRadius, mCornerRadius, mPaint);
- }
-
- public void setRegionBounds(Rect bounds) {
- mBounds.set(bounds);
- invalidateSelf();
- }
-
- public Rect getRegionBounds() {
- return mBounds;
- }
-
- ObjectAnimator startBoundsAnimation(Rect toBounds, Interpolator interpolator) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Animate bounds: from=%s to=%s",
- mBounds, toBounds);
- if (mBoundsAnimator != null) {
- mBoundsAnimator.cancel();
- }
- mBoundsAnimator = ObjectAnimator.ofObject(this, BOUNDS, mRectEvaluator,
- mBounds, toBounds);
- mBoundsAnimator.setDuration(BOUNDS_DURATION);
- mBoundsAnimator.setInterpolator(interpolator);
- mBoundsAnimator.start();
- return mBoundsAnimator;
- }
-
- ObjectAnimator startVisibilityAnimation(boolean visible, Interpolator interpolator) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Animate alpha: from=%d to=%d",
- Color.alpha(mColor), visible ? mMaxAlpha : 0);
- if (mAlphaAnimator != null) {
- mAlphaAnimator.cancel();
- }
- mAlphaAnimator = ObjectAnimator.ofInt(this, ALPHA, Color.alpha(mColor),
- visible ? mMaxAlpha : 0);
- mAlphaAnimator.setDuration(ALPHA_DURATION);
- mAlphaAnimator.setInterpolator(interpolator);
- mAlphaAnimator.start();
- return mAlphaAnimator;
- }
-}
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 04fc79acadbd..84027753435b 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
@@ -19,9 +19,15 @@ package com.android.wm.shell.freeform;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
+import android.content.Context;
+import android.graphics.Rect;
import android.os.IBinder;
+import android.util.ArrayMap;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -31,6 +37,8 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -39,23 +47,37 @@ import java.util.ArrayList;
import java.util.List;
/**
- * The {@link Transitions.TransitionHandler} that handles freeform task maximizing and restoring
- * transitions.
+ * The {@link Transitions.TransitionHandler} that handles freeform task maximizing, closing, and
+ * restoring transitions.
*/
public class FreeformTaskTransitionHandler
implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
-
+ private static final int CLOSE_ANIM_DURATION = 400;
+ private final Context mContext;
private final Transitions mTransitions;
private final WindowDecorViewModel mWindowDecorViewModel;
+ private final DisplayController mDisplayController;
+ private final ShellExecutor mMainExecutor;
+ private final ShellExecutor mAnimExecutor;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
+ private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
+
public FreeformTaskTransitionHandler(
ShellInit shellInit,
Transitions transitions,
- WindowDecorViewModel windowDecorViewModel) {
+ Context context,
+ WindowDecorViewModel windowDecorViewModel,
+ DisplayController displayController,
+ ShellExecutor mainExecutor,
+ ShellExecutor animExecutor) {
mTransitions = transitions;
+ mContext = context;
mWindowDecorViewModel = windowDecorViewModel;
+ mDisplayController = displayController;
+ mMainExecutor = mainExecutor;
+ mAnimExecutor = animExecutor;
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
shellInit.addInitCallback(this::onInit, this);
}
@@ -103,6 +125,14 @@ public class FreeformTaskTransitionHandler
@NonNull SurfaceControl.Transaction finishT,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
boolean transitionHandled = false;
+ final ArrayList<Animator> animations = new ArrayList<>();
+ final Runnable onAnimFinish = () -> {
+ if (!animations.isEmpty()) return;
+ mMainExecutor.execute(() -> {
+ mAnimations.remove(transition);
+ finishCallback.onTransitionFinished(null /* wct */);
+ });
+ };
for (TransitionInfo.Change change : info.getChanges()) {
if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
continue;
@@ -121,21 +151,45 @@ public class FreeformTaskTransitionHandler
case WindowManager.TRANSIT_TO_BACK:
transitionHandled |= startMinimizeTransition(transition);
break;
+ case WindowManager.TRANSIT_CLOSE:
+ if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ transitionHandled |= startCloseTransition(transition, change,
+ finishT, animations, onAnimFinish);
+ }
+ break;
}
}
-
- mPendingTransitionTokens.remove(transition);
-
if (!transitionHandled) {
return false;
}
-
+ mAnimations.put(transition, animations);
+ // startT must be applied before animations start.
startT.apply();
- mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null, null));
+ mAnimExecutor.execute(() -> {
+ for (Animator anim : animations) {
+ anim.start();
+ }
+ });
+ // Run this here in case no animators are created.
+ onAnimFinish.run();
+ mPendingTransitionTokens.remove(transition);
return true;
}
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ArrayList<Animator> animations = mAnimations.get(mergeTarget);
+ if (animations == null) return;
+ mAnimExecutor.execute(() -> {
+ for (Animator anim : animations) {
+ anim.end();
+ }
+ });
+
+ }
+
private boolean startChangeTransition(
IBinder transition,
int type,
@@ -165,6 +219,36 @@ public class FreeformTaskTransitionHandler
return mPendingTransitionTokens.contains(transition);
}
+ private boolean startCloseTransition(IBinder transition, TransitionInfo.Change change,
+ SurfaceControl.Transaction finishT, ArrayList<Animator> animations,
+ Runnable onAnimFinish) {
+ if (!mPendingTransitionTokens.contains(transition)) return false;
+ int screenHeight = mDisplayController
+ .getDisplayLayout(change.getTaskInfo().displayId).height();
+ ValueAnimator animator = new ValueAnimator();
+ animator.setDuration(CLOSE_ANIM_DURATION)
+ .setFloatValues(0f, 1f);
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ SurfaceControl sc = change.getLeash();
+ finishT.hide(sc);
+ Rect startBounds = new Rect(change.getTaskInfo().configuration.windowConfiguration
+ .getBounds());
+ animator.addUpdateListener(animation -> {
+ t.setPosition(sc, startBounds.left,
+ startBounds.top + (animation.getAnimatedFraction() * screenHeight));
+ t.apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animations.remove(animator);
+ onAnimFinish.run();
+ }
+ });
+ animations.add(animator);
+ return true;
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
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 56bd188a0d7d..53b5bd7ceb94 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
@@ -16,10 +16,12 @@
package com.android.wm.shell.keyguard;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_SLEEP;
@@ -28,6 +30,7 @@ import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -73,6 +76,10 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
private IRemoteTransition mOccludeByDreamTransition = null;
private IRemoteTransition mUnoccludeTransition = null;
+ // While set true, Keyguard has created a remote animation runner to handle the open app
+ // transition.
+ private boolean mIsLaunchingActivityOverLockscreen;
+
private final class StartedTransition {
final TransitionInfo mInfo;
final SurfaceControl.Transaction mFinishT;
@@ -117,7 +124,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
- if (!handles(info)) {
+ if (!handles(info) || mIsLaunchingActivityOverLockscreen) {
return false;
}
@@ -152,9 +159,15 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
+
+ if (remoteHandler == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "missing handler for keyguard %s transition", description);
+ return false;
+ }
+
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"start keyguard %s transition, info = %s", description, info);
-
try {
mStartedTransitions.put(transition,
new StartedTransition(info, finishTransaction, remoteHandler));
@@ -166,10 +179,16 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
if (sct != null) {
finishTransaction.merge(sct);
}
+ final WindowContainerTransaction mergedWct =
+ new WindowContainerTransaction();
+ if (wct != null) {
+ mergedWct.merge(wct, true);
+ }
+ maybeDismissFreeformOccludingKeyguard(mergedWct, info);
// Post our finish callback to let startAnimation finish first.
mMainExecutor.executeDelayed(() -> {
mStartedTransitions.remove(transition);
- finishCallback.onTransitionFinished(wct, null);
+ finishCallback.onTransitionFinished(mergedWct);
}, 0);
}
});
@@ -206,7 +225,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
// implementing an AIDL interface.
Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
}
- nextFinishCallback.onTransitionFinished(null, null);
+ nextFinishCallback.onTransitionFinished(null);
} else if (nextInfo.getType() == TRANSIT_SLEEP) {
// An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep
// token is held. In cases where keyguard is showing, we are running the animation for
@@ -261,6 +280,26 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
}
}
+ private void maybeDismissFreeformOccludingKeyguard(
+ WindowContainerTransaction wct, TransitionInfo info) {
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) == 0) {
+ return;
+ }
+ // There's a window occluding the Keyguard, find it and if it's in freeform mode, change it
+ // to fullscreen.
+ for (int i = 0; i < info.getChanges().size(); i++) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null && taskInfo.taskId != INVALID_TASK_ID
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && taskInfo.isFocused && change.getContainer() != null) {
+ wct.setWindowingMode(change.getContainer(), WINDOWING_MODE_FULLSCREEN);
+ wct.setBounds(change.getContainer(), null);
+ return;
+ }
+ }
+ }
+
private static class FakeFinishCallback extends IRemoteTransitionFinishedCallback.Stub {
@Override
public void onTransitionFinished(
@@ -284,5 +323,11 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
mUnoccludeTransition = unoccludeTransition;
});
}
+
+ @Override
+ public void setLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) {
+ mMainExecutor.execute(() ->
+ mIsLaunchingActivityOverLockscreen = isLaunchingActivityOverLockscreen);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
index b4b327f0eff2..33c299f0b161 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
@@ -38,4 +38,9 @@ public interface KeyguardTransitions {
@NonNull IRemoteTransition occludeTransition,
@NonNull IRemoteTransition occludeByDreamTransition,
@NonNull IRemoteTransition unoccludeTransition) {}
+
+ /**
+ * Notify whether keyguard has created a remote animation runner for next app launch.
+ */
+ default void setLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index f34d2a827e69..a9aa6badcfe2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -67,4 +67,11 @@ public interface Pip {
* view hierarchy or destroyed.
*/
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
+
+ /**
+ * @return {@link PipTransitionController} instance.
+ */
+ default PipTransitionController getPipTransitionController() {
+ return null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
deleted file mode 100644
index 48a3fc2460a2..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.util.Pair;
-
-import com.android.wm.shell.common.ShellExecutor;
-
-public class PipAppOpsListener {
- private static final String TAG = PipAppOpsListener.class.getSimpleName();
-
- private Context mContext;
- private ShellExecutor mMainExecutor;
- private AppOpsManager mAppOpsManager;
- private Callback mCallback;
-
- private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
- @Override
- public void onOpChanged(String op, String packageName) {
- try {
- // Dismiss the PiP once the user disables the app ops setting for that package
- final Pair<ComponentName, Integer> topPipActivityInfo =
- PipUtils.getTopPipActivity(mContext);
- if (topPipActivityInfo.first != null) {
- final ApplicationInfo appInfo = mContext.getPackageManager()
- .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
- if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
- mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
- packageName) != MODE_ALLOWED) {
- mMainExecutor.execute(() -> mCallback.dismissPip());
- }
- }
- } catch (NameNotFoundException e) {
- // Unregister the listener if the package can't be found
- unregisterAppOpsListener();
- }
- }
- };
-
- public PipAppOpsListener(Context context, Callback callback, ShellExecutor mainExecutor) {
- mContext = context;
- mMainExecutor = mainExecutor;
- mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mCallback = callback;
- }
-
- public void onActivityPinned(String packageName) {
- // Register for changes to the app ops setting for this package while it is in PiP
- registerAppOpsListener(packageName);
- }
-
- public void onActivityUnpinned() {
- // Unregister for changes to the previously PiP'ed package
- unregisterAppOpsListener();
- }
-
- private void registerAppOpsListener(String packageName) {
- mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
- mAppOpsChangedListener);
- }
-
- private void unregisterAppOpsListener() {
- mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
- }
-
- /** Callback for PipAppOpsListener to request changes to the PIP window. */
- public interface Callback {
- /** Dismisses the PIP window. */
- void dismissPip();
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
deleted file mode 100644
index 2590cab9ff2e..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ /dev/null
@@ -1,367 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip;
-
-import static android.app.PendingIntent.FLAG_IMMUTABLE;
-import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
-
-import android.annotation.DrawableRes;
-import android.annotation.StringRes;
-import android.annotation.SuppressLint;
-import android.app.PendingIntent;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.drawable.Icon;
-import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
-import com.android.wm.shell.R;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Interfaces with the {@link MediaSessionManager} to compose the right set of actions to show (only
- * if there are no actions from the PiP activity itself). The active media controller is only set
- * when there is a media session from the top PiP activity.
- */
-public class PipMediaController {
- private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
-
- private static final String ACTION_PLAY = "com.android.wm.shell.pip.PLAY";
- private static final String ACTION_PAUSE = "com.android.wm.shell.pip.PAUSE";
- private static final String ACTION_NEXT = "com.android.wm.shell.pip.NEXT";
- private static final String ACTION_PREV = "com.android.wm.shell.pip.PREV";
-
- /**
- * A listener interface to receive notification on changes to the media actions.
- */
- public interface ActionListener {
- /**
- * Called when the media actions changed.
- */
- void onMediaActionsChanged(List<RemoteAction> actions);
- }
-
- /**
- * A listener interface to receive notification on changes to the media metadata.
- */
- public interface MetadataListener {
- /**
- * Called when the media metadata changed.
- */
- void onMediaMetadataChanged(MediaMetadata metadata);
- }
-
- /**
- * A listener interface to receive notification on changes to the media session token.
- */
- public interface TokenListener {
- /**
- * Called when the media session token changed.
- */
- void onMediaSessionTokenChanged(MediaSession.Token token);
- }
-
- private final Context mContext;
- private final Handler mMainHandler;
- private final HandlerExecutor mHandlerExecutor;
-
- private final MediaSessionManager mMediaSessionManager;
- private MediaController mMediaController;
-
- private RemoteAction mPauseAction;
- private RemoteAction mPlayAction;
- private RemoteAction mNextAction;
- private RemoteAction mPrevAction;
-
- private final BroadcastReceiver mMediaActionReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mMediaController == null || mMediaController.getTransportControls() == null) {
- // no active media session, bail early.
- return;
- }
- switch (intent.getAction()) {
- case ACTION_PLAY:
- mMediaController.getTransportControls().play();
- break;
- case ACTION_PAUSE:
- mMediaController.getTransportControls().pause();
- break;
- case ACTION_NEXT:
- mMediaController.getTransportControls().skipToNext();
- break;
- case ACTION_PREV:
- mMediaController.getTransportControls().skipToPrevious();
- break;
- }
- }
- };
-
- private final MediaController.Callback mPlaybackChangedListener =
- new MediaController.Callback() {
- @Override
- public void onPlaybackStateChanged(PlaybackState state) {
- notifyActionsChanged();
- }
-
- @Override
- public void onMetadataChanged(@Nullable MediaMetadata metadata) {
- notifyMetadataChanged(metadata);
- }
- };
-
- private final MediaSessionManager.OnActiveSessionsChangedListener mSessionsChangedListener =
- this::resolveActiveMediaController;
-
- private final ArrayList<ActionListener> mActionListeners = new ArrayList<>();
- private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>();
- private final ArrayList<TokenListener> mTokenListeners = new ArrayList<>();
-
- public PipMediaController(Context context, Handler mainHandler) {
- mContext = context;
- mMainHandler = mainHandler;
- mHandlerExecutor = new HandlerExecutor(mMainHandler);
- IntentFilter mediaControlFilter = new IntentFilter();
- mediaControlFilter.addAction(ACTION_PLAY);
- mediaControlFilter.addAction(ACTION_PAUSE);
- mediaControlFilter.addAction(ACTION_NEXT);
- mediaControlFilter.addAction(ACTION_PREV);
- mContext.registerReceiverForAllUsers(mMediaActionReceiver, mediaControlFilter,
- SYSTEMUI_PERMISSION, mainHandler, Context.RECEIVER_EXPORTED);
-
- // Creates the standard media buttons that we may show.
- mPauseAction = getDefaultRemoteAction(R.string.pip_pause,
- R.drawable.pip_ic_pause_white, ACTION_PAUSE);
- mPlayAction = getDefaultRemoteAction(R.string.pip_play,
- R.drawable.pip_ic_play_arrow_white, ACTION_PLAY);
- mNextAction = getDefaultRemoteAction(R.string.pip_skip_to_next,
- R.drawable.pip_ic_skip_next_white, ACTION_NEXT);
- mPrevAction = getDefaultRemoteAction(R.string.pip_skip_to_prev,
- R.drawable.pip_ic_skip_previous_white, ACTION_PREV);
-
- mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
- }
-
- /**
- * Handles when an activity is pinned.
- */
- public void onActivityPinned() {
- // Once we enter PiP, try to find the active media controller for the top most activity
- resolveActiveMediaController(mMediaSessionManager.getActiveSessionsForUser(null,
- UserHandle.CURRENT));
- }
-
- /**
- * Adds a new media action listener.
- */
- public void addActionListener(ActionListener listener) {
- if (!mActionListeners.contains(listener)) {
- mActionListeners.add(listener);
- listener.onMediaActionsChanged(getMediaActions());
- }
- }
-
- /**
- * Removes a media action listener.
- */
- public void removeActionListener(ActionListener listener) {
- listener.onMediaActionsChanged(Collections.emptyList());
- mActionListeners.remove(listener);
- }
-
- /**
- * Adds a new media metadata listener.
- */
- public void addMetadataListener(MetadataListener listener) {
- if (!mMetadataListeners.contains(listener)) {
- mMetadataListeners.add(listener);
- listener.onMediaMetadataChanged(getMediaMetadata());
- }
- }
-
- /**
- * Removes a media metadata listener.
- */
- public void removeMetadataListener(MetadataListener listener) {
- listener.onMediaMetadataChanged(null);
- mMetadataListeners.remove(listener);
- }
-
- /**
- * Adds a new token listener.
- */
- public void addTokenListener(TokenListener listener) {
- if (!mTokenListeners.contains(listener)) {
- mTokenListeners.add(listener);
- listener.onMediaSessionTokenChanged(getToken());
- }
- }
-
- /**
- * Removes a token listener.
- */
- public void removeTokenListener(TokenListener listener) {
- listener.onMediaSessionTokenChanged(null);
- mTokenListeners.remove(listener);
- }
-
- private MediaSession.Token getToken() {
- if (mMediaController == null) {
- return null;
- }
- return mMediaController.getSessionToken();
- }
-
- private MediaMetadata getMediaMetadata() {
- return mMediaController != null ? mMediaController.getMetadata() : null;
- }
-
- /**
- * Gets the set of media actions currently available.
- */
- // This is due to using PlaybackState#isActive, which is added in API 31.
- // It can be removed when min_sdk of the app is set to 31 or greater.
- @SuppressLint("NewApi")
- private List<RemoteAction> getMediaActions() {
- // Cache the PlaybackState since it's a Binder call.
- final PlaybackState playbackState;
- if (mMediaController == null
- || (playbackState = mMediaController.getPlaybackState()) == null) {
- return Collections.emptyList();
- }
-
- ArrayList<RemoteAction> mediaActions = new ArrayList<>();
- boolean isPlaying = playbackState.isActive();
- long actions = playbackState.getActions();
-
- // Prev action
- mPrevAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0);
- mediaActions.add(mPrevAction);
-
- // Play/pause action
- if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
- mediaActions.add(mPlayAction);
- } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
- mediaActions.add(mPauseAction);
- }
-
- // Next action
- mNextAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0);
- mediaActions.add(mNextAction);
- return mediaActions;
- }
-
- /** @return Default {@link RemoteAction} sends broadcast back to SysUI. */
- private RemoteAction getDefaultRemoteAction(@StringRes int titleAndDescription,
- @DrawableRes int icon, String action) {
- final String titleAndDescriptionStr = mContext.getString(titleAndDescription);
- final Intent intent = new Intent(action);
- intent.setPackage(mContext.getPackageName());
- return new RemoteAction(Icon.createWithResource(mContext, icon),
- titleAndDescriptionStr, titleAndDescriptionStr,
- PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent,
- FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
- }
-
- /**
- * Re-registers the session listener for the current user.
- */
- public void registerSessionListenerForCurrentUser() {
- mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener);
- mMediaSessionManager.addOnActiveSessionsChangedListener(null, UserHandle.CURRENT,
- mHandlerExecutor, mSessionsChangedListener);
- }
-
- /**
- * Tries to find and set the active media controller for the top PiP activity.
- */
- private void resolveActiveMediaController(List<MediaController> controllers) {
- if (controllers != null) {
- final ComponentName topActivity = PipUtils.getTopPipActivity(mContext).first;
- if (topActivity != null) {
- for (int i = 0; i < controllers.size(); i++) {
- final MediaController controller = controllers.get(i);
- if (controller.getPackageName().equals(topActivity.getPackageName())) {
- setActiveMediaController(controller);
- return;
- }
- }
- }
- }
- setActiveMediaController(null);
- }
-
- /**
- * Sets the active media controller for the top PiP activity.
- */
- private void setActiveMediaController(MediaController controller) {
- if (controller != mMediaController) {
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mPlaybackChangedListener);
- }
- mMediaController = controller;
- if (controller != null) {
- controller.registerCallback(mPlaybackChangedListener, mMainHandler);
- }
- notifyActionsChanged();
- notifyMetadataChanged(getMediaMetadata());
- notifyTokenChanged(getToken());
-
- // TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV)
- }
- }
-
- /**
- * Notifies all listeners that the actions have changed.
- */
- private void notifyActionsChanged() {
- if (!mActionListeners.isEmpty()) {
- List<RemoteAction> actions = getMediaActions();
- mActionListeners.forEach(l -> l.onMediaActionsChanged(actions));
- }
- }
-
- /**
- * Notifies all listeners that the metadata have changed.
- */
- private void notifyMetadataChanged(MediaMetadata metadata) {
- if (!mMetadataListeners.isEmpty()) {
- mMetadataListeners.forEach(l -> l.onMediaMetadataChanged(metadata));
- }
- }
-
- private void notifyTokenChanged(MediaSession.Token token) {
- if (!mTokenListeners.isEmpty()) {
- mTokenListeners.forEach(l -> l.onMediaSessionTokenChanged(token));
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 08da4857a0b0..9e8f9c68d43d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,7 +63,6 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
-import android.os.SystemProperties;
import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
@@ -83,6 +82,11 @@ import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -364,13 +368,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mMainExecutor = mainExecutor;
// TODO: Can be removed once wm components are created on the shell-main thread
- mMainExecutor.execute(() -> {
- mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
- });
- mTaskOrganizer.addFocusListener(this);
- mPipTransitionController.setPipOrganizer(this);
- displayController.addDisplayWindowListener(this);
- pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+ if (!PipUtils.isPip2ExperimentEnabled()) {
+ mMainExecutor.execute(() -> {
+ mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
+ });
+ mTaskOrganizer.addFocusListener(this);
+ mPipTransitionController.setPipOrganizer(this);
+ displayController.addDisplayWindowListener(this);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+ }
}
public PipTransitionController getTransitionController() {
@@ -590,7 +596,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
SplitScreenController split = mSplitScreenOptional.get();
if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
split.prepareExitSplitScreen(wct, split.getStageOfTask(
- mTaskInfo.lastParentTaskIdBeforePip));
+ mTaskInfo.lastParentTaskIdBeforePip),
+ SplitScreenController.EXIT_REASON_APP_FINISHED);
}
}
mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
@@ -1732,17 +1739,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// animation.
// TODO(b/272819817): cleanup the null-check and extra logging.
final boolean hasTopActivityInfo = mTaskInfo.topActivityInfo != null;
- if (!hasTopActivityInfo) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "%s: TaskInfo.topActivityInfo is null", TAG);
- }
- if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", true)
- && hasTopActivityInfo) {
+ if (hasTopActivityInfo) {
animator.setAppIconContentOverlay(
mContext, currentBounds, mTaskInfo.topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
} else {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: TaskInfo.topActivityInfo is null", TAG);
animator.setColorContentOverlay(mContext);
}
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e3d53fc415db..018d674e5427 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -49,7 +49,6 @@ import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
-import android.os.SystemProperties;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -65,6 +64,10 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
@@ -89,6 +92,7 @@ public class PipTransition extends PipTransitionController {
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
+ private final PipAnimationController mPipAnimationController;
private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS;
private Transitions.TransitionFinishCallback mFinishCallback;
private SurfaceControl.Transaction mFinishTransaction;
@@ -137,10 +141,11 @@ public class PipTransition extends PipTransitionController {
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenOptional) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
- pipBoundsAlgorithm, pipAnimationController);
+ pipBoundsAlgorithm);
mContext = context;
mPipTransitionState = pipTransitionState;
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipAnimationController = pipAnimationController;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
@@ -148,6 +153,13 @@ public class PipTransition extends PipTransitionController {
}
@Override
+ protected void onInit() {
+ if (!PipUtils.isPip2ExperimentEnabled()) {
+ mTransitions.addHandler(this);
+ }
+ }
+
+ @Override
public void startExitTransition(int type, WindowContainerTransaction out,
@Nullable Rect destinationBounds) {
if (destinationBounds != null) {
@@ -447,7 +459,7 @@ public class PipTransition extends PipTransitionController {
// handler if there is a pending PiP animation.
final Transitions.TransitionFinishCallback finishCallback = mFinishCallback;
mFinishCallback = null;
- finishCallback.onTransitionFinished(wct, null /* callback */);
+ finishCallback.onTransitionFinished(wct);
}
@Override
@@ -456,7 +468,7 @@ public class PipTransition extends PipTransitionController {
// for example, when app crashes while in PiP and exit transition has not started
mCurrentPipTaskToken = null;
if (mFinishCallback == null) return;
- mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
+ mFinishCallback.onTransitionFinished(null /* wct */);
mFinishCallback = null;
mFinishTransaction = null;
}
@@ -586,7 +598,7 @@ public class PipTransition extends PipTransitionController {
final boolean useLocalLeash = activitySc != null;
final boolean toFullscreen = pipChange.getEndAbsBounds().equals(
mPipBoundsState.getDisplayBounds());
- mFinishCallback = (wct, wctCB) -> {
+ mFinishCallback = (wct) -> {
mPipOrganizer.onExitPipFinished(taskInfo);
// TODO(b/286346098): remove the OPEN app flicker completely
@@ -610,7 +622,7 @@ public class PipTransition extends PipTransitionController {
mPipAnimationController.resetAnimatorState();
finishTransaction.remove(pipLeash);
}
- finishCallback.onTransitionFinished(wct, wctCB);
+ finishCallback.onTransitionFinished(wct);
};
mFinishTransaction = finishTransaction;
@@ -750,7 +762,7 @@ public class PipTransition extends PipTransitionController {
finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
mPipDisplayLayoutState.getDisplayBounds());
mPipOrganizer.onExitPipFinished(taskInfo);
- finishCallback.onTransitionFinished(null, null);
+ finishCallback.onTransitionFinished(null);
}
/** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */
@@ -902,17 +914,13 @@ public class PipTransition extends PipTransitionController {
// animation.
// TODO(b/272819817): cleanup the null-check and extra logging.
final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
- if (!hasTopActivityInfo) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "%s: TaskInfo.topActivityInfo is null", TAG);
- }
- if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", true)
- && hasTopActivityInfo) {
+ if (hasTopActivityInfo) {
animator.setAppIconContentOverlay(
mContext, currentBounds, taskInfo.topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
} else {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: TaskInfo.topActivityInfo is null", TAG);
animator.setColorContentOverlay(mContext);
}
} else {
@@ -1045,13 +1053,27 @@ public class PipTransition extends PipTransitionController {
startTransaction.apply();
mPipOrganizer.onExitPipFinished(taskInfo);
- finishCallback.onTransitionFinished(null, null);
+ finishCallback.onTransitionFinished(null);
}
private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange,
@NonNull SurfaceControl.Transaction startTransaction) {
final SurfaceControl leash = prevPipTaskChange.getLeash();
- startTransaction.remove(leash);
+ final Rect bounds = prevPipTaskChange.getEndAbsBounds();
+ final Point offset = prevPipTaskChange.getEndRelOffset();
+ bounds.offset(-offset.x, -offset.y);
+
+ startTransaction.setWindowCrop(leash, null);
+ startTransaction.setMatrix(leash, 1, 0, 0, 1);
+ startTransaction.setCornerRadius(leash, 0);
+ startTransaction.setPosition(leash, bounds.left, bounds.top);
+
+ if (mHasFadeOut && prevPipTaskChange.getTaskInfo().isVisible()) {
+ if (mPipAnimationController.getCurrentAnimator() != null) {
+ mPipAnimationController.getCurrentAnimator().cancel();
+ }
+ startTransaction.setAlpha(leash, 1);
+ }
mHasFadeOut = false;
mCurrentPipTaskToken = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 63627938ec87..20c57fa5e566 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -38,6 +38,8 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -51,7 +53,6 @@ import java.util.List;
*/
public abstract class PipTransitionController implements Transitions.TransitionHandler {
- protected final PipAnimationController mPipAnimationController;
protected final PipBoundsAlgorithm mPipBoundsAlgorithm;
protected final PipBoundsState mPipBoundsState;
protected final ShellTaskOrganizer mShellTaskOrganizer;
@@ -134,20 +135,18 @@ public abstract class PipTransitionController implements Transitions.TransitionH
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
- PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipAnimationController pipAnimationController) {
+ PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm) {
mPipBoundsState = pipBoundsState;
mPipMenuController = pipMenuController;
mShellTaskOrganizer = shellTaskOrganizer;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
- mPipAnimationController = pipAnimationController;
mTransitions = transitions;
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
shellInit.addInitCallback(this::onInit, this);
}
}
- private void onInit() {
+ protected void onInit() {
mTransitions.addHandler(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
deleted file mode 100644
index 8b98790d3499..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
-
-import android.annotation.Nullable;
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.app.RemoteAction;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.RemoteException;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.Pair;
-import android.util.TypedValue;
-import android.window.TaskSnapshot;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
-
-import java.util.List;
-import java.util.Objects;
-
-/** A class that includes convenience methods. */
-public class PipUtils {
- private static final String TAG = "PipUtils";
-
- // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
- private static final double EPSILON = 1e-7;
-
- /**
- * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
- * The component name may be null if no such activity exists.
- */
- public static Pair<ComponentName, Integer> getTopPipActivity(Context context) {
- try {
- final String sysUiPackageName = context.getPackageName();
- final RootTaskInfo pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- if (pinnedTaskInfo != null && pinnedTaskInfo.childTaskIds != null
- && pinnedTaskInfo.childTaskIds.length > 0) {
- for (int i = pinnedTaskInfo.childTaskNames.length - 1; i >= 0; i--) {
- ComponentName cn = ComponentName.unflattenFromString(
- pinnedTaskInfo.childTaskNames[i]);
- if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) {
- return new Pair<>(cn, pinnedTaskInfo.childTaskUserIds[i]);
- }
- }
- }
- } catch (RemoteException e) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Unable to get pinned stack.", TAG);
- }
- return new Pair<>(null, 0);
- }
-
- /**
- * @return the pixels for a given dp value.
- */
- public static int dpToPx(float dpValue, DisplayMetrics dm) {
- return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
- }
-
- /**
- * @return true if the aspect ratios differ
- */
- public static boolean aspectRatioChanged(float aspectRatio1, float aspectRatio2) {
- return Math.abs(aspectRatio1 - aspectRatio2) > EPSILON;
- }
-
- /**
- * Checks whether title, description and intent match.
- * Comparing icons would be good, but using equals causes false negatives
- */
- public static boolean remoteActionsMatch(RemoteAction action1, RemoteAction action2) {
- if (action1 == action2) return true;
- if (action1 == null || action2 == null) return false;
- return action1.isEnabled() == action2.isEnabled()
- && action1.shouldShowIcon() == action2.shouldShowIcon()
- && Objects.equals(action1.getTitle(), action2.getTitle())
- && Objects.equals(action1.getContentDescription(), action2.getContentDescription())
- && Objects.equals(action1.getActionIntent(), action2.getActionIntent());
- }
-
- /**
- * Returns true if the actions in the lists match each other according to {@link
- * PipUtils#remoteActionsMatch(RemoteAction, RemoteAction)}, including their position.
- */
- public static boolean remoteActionsChanged(List<RemoteAction> list1, List<RemoteAction> list2) {
- if (list1 == null && list2 == null) {
- return false;
- }
- if (list1 == null || list2 == null) {
- return true;
- }
- if (list1.size() != list2.size()) {
- return true;
- }
- for (int i = 0; i < list1.size(); i++) {
- if (!remoteActionsMatch(list1.get(i), list2.get(i))) {
- return true;
- }
- }
- return false;
- }
-
- /** @return {@link TaskSnapshot} for a given task id. */
- @Nullable
- public static TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) {
- if (taskId <= 0) return null;
- try {
- return ActivityTaskManager.getService().getTaskSnapshot(
- taskId, isLowResolution, false /* takeSnapshotIfNeeded */);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get task snapshot, taskId=" + taskId, e);
- return null;
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 5e1b6becfa45..760652625f9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -38,12 +38,12 @@ import android.view.WindowManagerGlobal;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipMediaController;
-import com.android.wm.shell.pip.PipMediaController.ActionListener;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipMediaController.ActionListener;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 4a06d84ce90d..bc17ce97dbff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -15,7 +15,7 @@
*/
package com.android.wm.shell.pip.phone;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
import android.annotation.NonNull;
import android.content.Context;
@@ -36,8 +36,8 @@ import androidx.annotation.BinderThread;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index bbd17cd67dc7..106486714a5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -75,6 +75,14 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.IPip;
@@ -82,18 +90,10 @@ import com.android.wm.shell.pip.IPipAnimationListener;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
@@ -123,14 +123,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
- private boolean mEnablePipKeepClearAlgorithm =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
-
- @VisibleForTesting
- void setEnablePipKeepClearAlgorithm(boolean value) {
- mEnablePipKeepClearAlgorithm = value;
- }
-
private Context mContext;
protected ShellExecutor mMainExecutor;
private DisplayController mDisplayController;
@@ -142,7 +134,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
- private PipSizeSpecHandler mPipSizeSpecHandler;
private PipDisplayLayoutState mPipDisplayLayoutState;
private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
@@ -167,10 +158,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// early bail out if the change was caused by keyguard showing up
return;
}
- if (!mEnablePipKeepClearAlgorithm) {
- // early bail out if the keep clear areas feature is disabled
- return;
- }
if (mPipBoundsState.isStashed()) {
// don't move when stashed
return;
@@ -188,10 +175,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
private void updatePipPositionForKeepClearAreas() {
- if (!mEnablePipKeepClearAlgorithm) {
- // early bail out if the keep clear areas feature is disabled
- return;
- }
if (mIsKeyguardShowingOrAnimating) {
// early bail out if the change was caused by keyguard showing up
return;
@@ -344,15 +327,17 @@ public class PipController implements PipTransitionController.PipTransitionCallb
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
if (mPipDisplayLayoutState.getDisplayId() == displayId) {
- if (mEnablePipKeepClearAlgorithm) {
- mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
-
- mMainExecutor.removeCallbacks(
- mMovePipInResponseToKeepClearAreasChangeCallback);
- mMainExecutor.executeDelayed(
- mMovePipInResponseToKeepClearAreasChangeCallback,
- PIP_KEEP_CLEAR_AREAS_DELAY);
- }
+ mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
+
+ mMainExecutor.removeCallbacks(
+ mMovePipInResponseToKeepClearAreasChangeCallback);
+ mMainExecutor.executeDelayed(
+ mMovePipInResponseToKeepClearAreasChangeCallback,
+ PIP_KEEP_CLEAR_AREAS_DELAY);
+
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onKeepClearAreasChanged: restricted=%s, unrestricted=%s",
+ restricted, unrestricted);
}
}
};
@@ -402,7 +387,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
@@ -426,7 +410,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return new PipController(context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener,
- pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
+ pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
@@ -444,7 +428,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
@NonNull PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
@@ -461,8 +444,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor
) {
-
-
mContext = context;
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
@@ -472,7 +453,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
- mPipSizeSpecHandler = pipSizeSpecHandler;
mPipDisplayLayoutState = pipDisplayLayoutState;
mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
@@ -493,7 +473,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mDisplayInsetsController = displayInsetsController;
mTabletopModeController = tabletopModeController;
- shellInit.addInitCallback(this::onInit, this);
+ if (!PipUtils.isPip2ExperimentEnabled()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
private void onInit() {
@@ -660,25 +642,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// there's a keyguard present
return;
}
- int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
onDisplayChangedUncheck(mDisplayController
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId()),
false /* saveRestoreSnapFraction */);
- int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
- if (!mEnablePipKeepClearAlgorithm) {
- // offset PiP to adjust for bottom inset change
- int pipTop = mPipBoundsState.getBounds().top;
- int diff = newMaxMovementBound - oldMaxMovementBound;
- if (diff < 0 && pipTop > newMaxMovementBound) {
- // bottom inset has increased, move PiP up if it is too low
- mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(),
- newMaxMovementBound - pipTop);
- }
- if (diff > 0 && oldMaxMovementBound == pipTop) {
- // bottom inset has decreased, move PiP down if it was by the edge
- mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff);
- }
- }
}
});
@@ -707,7 +673,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Try to move the PiP window if we have entered PiP mode.
if (mPipTransitionState.hasEnteredPip()) {
final Rect pipBounds = mPipBoundsState.getBounds();
- final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
+ final Point edgeInsets = mPipDisplayLayoutState.getScreenEdgeInsets();
if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
// PiP bounds is too big to fit either half, bail early.
return;
@@ -766,7 +732,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTouchHandler.onConfigurationChanged();
mPipBoundsState.onConfigurationChanged();
- mPipSizeSpecHandler.onConfigurationChanged();
+ mPipDisplayLayoutState.onConfigurationChanged();
}
@Override
@@ -947,12 +913,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
* Sets both shelf visibility and its height.
*/
private void setShelfHeight(boolean visible, int height) {
- if (!mIsKeyguardShowingOrAnimating) {
- setShelfHeightLocked(visible, height);
- }
+ // turn this into Launcher keep clear area registration instead
+ setLauncherKeepClearAreaHeight(visible, height);
}
private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height);
if (visible) {
Rect rect = new Rect(
0, mPipBoundsState.getDisplayBounds().bottom - height,
@@ -1008,15 +975,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams,
int launcherRotation, Rect hotseatKeepClearArea) {
-
- if (mEnablePipKeepClearAlgorithm) {
- // pre-emptively add the keep clear area for Hotseat, so that it is taken into account
- // when calculating the entry destination bounds of PiP window
- mPipBoundsState.getRestrictedKeepClearAreas().add(hotseatKeepClearArea);
- } else {
- int shelfHeight = hotseatKeepClearArea.height();
- setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
- }
+ // preemptively add the keep clear area for Hotseat, so that it is taken into account
+ // when calculating the entry destination bounds of PiP window
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
+ hotseatKeepClearArea);
onDisplayRotationChangedNotInPip(mContext, launcherRotation);
final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams);
@@ -1212,7 +1174,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipTaskOrganizer.dump(pw, innerPrefix);
mPipBoundsState.dump(pw, innerPrefix);
mPipInputConsumer.dump(pw, innerPrefix);
- mPipSizeSpecHandler.dump(pw, innerPrefix);
mPipDisplayLayoutState.dump(pw, innerPrefix);
}
@@ -1261,6 +1222,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipController.this.showPictureInPictureMenu();
});
}
+
+ @Override
+ public PipTransitionController getPipTransitionController() {
+ return mPipTransitionController;
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 9729a4007bac..4e75847b6bc0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -33,11 +33,12 @@ import android.view.WindowManager;
import androidx.annotation.NonNull;
import com.android.wm.shell.R;
-import com.android.wm.shell.bubbles.DismissView;
-import com.android.wm.shell.common.DismissCircleView;
+import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
+import com.android.wm.shell.common.bubbles.DismissView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import kotlin.Unit;
@@ -106,6 +107,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
}
mTargetViewContainer = new DismissView(mContext);
+ DismissViewUtils.setup(mTargetViewContainer);
mTargetView = mTargetViewContainer.getCircle();
mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> {
if (!windowInsets.equals(mWindowInsets)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
index d7d335b856be..1b1ebc39b558 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
@@ -20,7 +20,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.graphics.Rect;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipBoundsState;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 779c539a2097..fc34772f2fce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -66,8 +66,8 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 43d3f36f1fe5..c708b86e88c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -20,10 +20,10 @@ import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_B
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_DISMISS;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
@@ -33,7 +33,6 @@ import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Debug;
-import android.os.SystemProperties;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
@@ -41,26 +40,23 @@ import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-import com.android.wm.shell.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.function.Consumer;
-
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
+import java.util.function.Consumer;
+
/**
* A helper to animate and manipulate the PiP.
*/
public class PipMotionHelper implements PipAppOpsListener.Callback,
FloatingContentCoordinator.FloatingContent {
-
- public static final boolean ENABLE_FLING_TO_DISMISS_PIP =
- SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", false);
private static final String TAG = "PipMotionHelper";
private static final boolean DEBUG = false;
@@ -707,7 +703,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
loc[1] = animatedPipBounds.top;
}
};
- mMagnetizedPip.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_PIP);
+ mMagnetizedPip.setFlingToTargetEnabled(false);
}
return mMagnetizedPip;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index abe2db094a5c..e5f9fdc7a740 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -46,11 +46,12 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipUiEventLogger;
import java.io.PrintWriter;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
deleted file mode 100644
index 7971c049ff3b..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip.phone;
-
-import static com.android.wm.shell.pip.PipUtils.dpToPx;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.SystemProperties;
-import android.util.Size;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-
-import java.io.PrintWriter;
-
-/**
- * Acts as a source of truth for appropriate size spec for PIP.
- */
-public class PipSizeSpecHandler {
- private static final String TAG = PipSizeSpecHandler.class.getSimpleName();
-
- @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
-
- private final SizeSpecSource mSizeSpecSourceImpl;
-
- /** The preferred minimum (and default minimum) size specified by apps. */
- @Nullable private Size mOverrideMinSize;
- private int mOverridableMinSize;
-
- /** Used to store values obtained from resource files. */
- private Point mScreenEdgeInsets;
- private float mMinAspectRatioForMinSize;
- private float mMaxAspectRatioForMinSize;
- private int mDefaultMinSize;
-
- @NonNull private final Context mContext;
-
- private interface SizeSpecSource {
- /** Returns max size allowed for the PIP window */
- Size getMaxSize(float aspectRatio);
-
- /** Returns default size for the PIP window */
- Size getDefaultSize(float aspectRatio);
-
- /** Returns min size allowed for the PIP window */
- Size getMinSize(float aspectRatio);
-
- /** Returns the adjusted size based on current size and target aspect ratio */
- Size getSizeForAspectRatio(Size size, float aspectRatio);
-
- /** Updates internal resources on configuration changes */
- default void reloadResources() {}
- }
-
- /**
- * Determines PIP window size optimized for large screens and aspect ratios close to 1:1
- */
- private class SizeSpecLargeScreenOptimizedImpl implements SizeSpecSource {
- private static final float DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16;
-
- /** Default and minimum percentages for the PIP size logic. */
- private final float mDefaultSizePercent;
- private final float mMinimumSizePercent;
-
- /** Aspect ratio that the PIP size spec logic optimizes for. */
- private float mOptimizedAspectRatio;
-
- private SizeSpecLargeScreenOptimizedImpl() {
- mDefaultSizePercent = Float.parseFloat(SystemProperties
- .get("com.android.wm.shell.pip.phone.def_percentage", "0.6"));
- mMinimumSizePercent = Float.parseFloat(SystemProperties
- .get("com.android.wm.shell.pip.phone.min_percentage", "0.5"));
- }
-
- @Override
- public void reloadResources() {
- final Resources res = mContext.getResources();
-
- mOptimizedAspectRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio);
- // make sure the optimized aspect ratio is valid with a default value to fall back to
- if (mOptimizedAspectRatio > 1) {
- mOptimizedAspectRatio = DEFAULT_OPTIMIZED_ASPECT_RATIO;
- }
- }
-
- /**
- * Calculates the max size of PIP.
- *
- * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
- * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
- * whole screen. A linear function is used to calculate these sizes.
- *
- * @param aspectRatio aspect ratio of the PIP window
- * @return dimensions of the max size of the PIP
- */
- @Override
- public Size getMaxSize(float aspectRatio) {
- final int totalHorizontalPadding = getInsetBounds().left
- + (getDisplayBounds().width() - getInsetBounds().right);
- final int totalVerticalPadding = getInsetBounds().top
- + (getDisplayBounds().height() - getInsetBounds().bottom);
-
- final int shorterLength = (int) (1f * Math.min(
- getDisplayBounds().width() - totalHorizontalPadding,
- getDisplayBounds().height() - totalVerticalPadding));
-
- int maxWidth, maxHeight;
-
- // use the optimized max sizing logic only within a certain aspect ratio range
- if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
- // this formula and its derivation is explained in b/198643358#comment16
- maxWidth = (int) (mOptimizedAspectRatio * shorterLength
- + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
- + aspectRatio));
- maxHeight = Math.round(maxWidth / aspectRatio);
- } else {
- if (aspectRatio > 1f) {
- maxWidth = shorterLength;
- maxHeight = Math.round(maxWidth / aspectRatio);
- } else {
- maxHeight = shorterLength;
- maxWidth = Math.round(maxHeight * aspectRatio);
- }
- }
-
- return new Size(maxWidth, maxHeight);
- }
-
- /**
- * Decreases the dimensions by a percentage relative to max size to get default size.
- *
- * @param aspectRatio aspect ratio of the PIP window
- * @return dimensions of the default size of the PIP
- */
- @Override
- public Size getDefaultSize(float aspectRatio) {
- Size minSize = this.getMinSize(aspectRatio);
-
- if (mOverrideMinSize != null) {
- return minSize;
- }
-
- Size maxSize = this.getMaxSize(aspectRatio);
-
- int defaultWidth = Math.max(Math.round(maxSize.getWidth() * mDefaultSizePercent),
- minSize.getWidth());
- int defaultHeight = Math.round(defaultWidth / aspectRatio);
-
- return new Size(defaultWidth, defaultHeight);
- }
-
- /**
- * Decreases the dimensions by a certain percentage relative to max size to get min size.
- *
- * @param aspectRatio aspect ratio of the PIP window
- * @return dimensions of the min size of the PIP
- */
- @Override
- public Size getMinSize(float aspectRatio) {
- // if there is an overridden min size provided, return that
- if (mOverrideMinSize != null) {
- return adjustOverrideMinSizeToAspectRatio(aspectRatio);
- }
-
- Size maxSize = this.getMaxSize(aspectRatio);
-
- int minWidth = Math.round(maxSize.getWidth() * mMinimumSizePercent);
- int minHeight = Math.round(maxSize.getHeight() * mMinimumSizePercent);
-
- // make sure the calculated min size is not smaller than the allowed default min size
- if (aspectRatio > 1f) {
- minHeight = Math.max(minHeight, mDefaultMinSize);
- minWidth = Math.round(minHeight * aspectRatio);
- } else {
- minWidth = Math.max(minWidth, mDefaultMinSize);
- minHeight = Math.round(minWidth / aspectRatio);
- }
- return new Size(minWidth, minHeight);
- }
-
- /**
- * Returns the size for target aspect ratio making sure new size conforms with the rules.
- *
- * <p>Recalculates the dimensions such that the target aspect ratio is achieved, while
- * maintaining the same maximum size to current size ratio.
- *
- * @param size current size
- * @param aspectRatio target aspect ratio
- */
- @Override
- public Size getSizeForAspectRatio(Size size, float aspectRatio) {
- float currAspectRatio = (float) size.getWidth() / size.getHeight();
-
- // getting the percentage of the max size that current size takes
- Size currentMaxSize = getMaxSize(currAspectRatio);
- float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth();
-
- // getting the max size for the target aspect ratio
- Size updatedMaxSize = getMaxSize(aspectRatio);
-
- int width = Math.round(updatedMaxSize.getWidth() * currentPercent);
- int height = Math.round(updatedMaxSize.getHeight() * currentPercent);
-
- // adjust the dimensions if below allowed min edge size
- if (width < getMinEdgeSize() && aspectRatio <= 1) {
- width = getMinEdgeSize();
- height = Math.round(width / aspectRatio);
- } else if (height < getMinEdgeSize() && aspectRatio > 1) {
- height = getMinEdgeSize();
- width = Math.round(height * aspectRatio);
- }
-
- // reduce the dimensions of the updated size to the calculated percentage
- return new Size(width, height);
- }
- }
-
- private class SizeSpecDefaultImpl implements SizeSpecSource {
- private float mDefaultSizePercent;
- private float mMinimumSizePercent;
-
- @Override
- public void reloadResources() {
- final Resources res = mContext.getResources();
-
- mMaxAspectRatioForMinSize = res.getFloat(
- R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
- mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
-
- mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent);
- mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
- }
-
- @Override
- public Size getMaxSize(float aspectRatio) {
- final int shorterLength = Math.min(getDisplayBounds().width(),
- getDisplayBounds().height());
-
- final int totalHorizontalPadding = getInsetBounds().left
- + (getDisplayBounds().width() - getInsetBounds().right);
- final int totalVerticalPadding = getInsetBounds().top
- + (getDisplayBounds().height() - getInsetBounds().bottom);
-
- final int maxWidth, maxHeight;
-
- if (aspectRatio > 1f) {
- maxWidth = (int) Math.max(getDefaultSize(aspectRatio).getWidth(),
- shorterLength - totalHorizontalPadding);
- maxHeight = (int) (maxWidth / aspectRatio);
- } else {
- maxHeight = (int) Math.max(getDefaultSize(aspectRatio).getHeight(),
- shorterLength - totalVerticalPadding);
- maxWidth = (int) (maxHeight * aspectRatio);
- }
-
- return new Size(maxWidth, maxHeight);
- }
-
- @Override
- public Size getDefaultSize(float aspectRatio) {
- if (mOverrideMinSize != null) {
- return this.getMinSize(aspectRatio);
- }
-
- final int smallestDisplaySize = Math.min(getDisplayBounds().width(),
- getDisplayBounds().height());
- final int minSize = (int) Math.max(getMinEdgeSize(),
- smallestDisplaySize * mDefaultSizePercent);
-
- final int width;
- final int height;
-
- if (aspectRatio <= mMinAspectRatioForMinSize
- || aspectRatio > mMaxAspectRatioForMinSize) {
- // Beyond these points, we can just use the min size as the shorter edge
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
- } else {
- // Within these points, ensure that the bounds fit within the radius of the limits
- // at the points
- final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
- final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
- height = (int) Math.round(Math.sqrt((radius * radius)
- / (aspectRatio * aspectRatio + 1)));
- width = Math.round(height * aspectRatio);
- }
-
- return new Size(width, height);
- }
-
- @Override
- public Size getMinSize(float aspectRatio) {
- if (mOverrideMinSize != null) {
- return adjustOverrideMinSizeToAspectRatio(aspectRatio);
- }
-
- final int shorterLength = Math.min(getDisplayBounds().width(),
- getDisplayBounds().height());
- final int minWidth, minHeight;
-
- if (aspectRatio > 1f) {
- minWidth = (int) Math.min(getDefaultSize(aspectRatio).getWidth(),
- shorterLength * mMinimumSizePercent);
- minHeight = (int) (minWidth / aspectRatio);
- } else {
- minHeight = (int) Math.min(getDefaultSize(aspectRatio).getHeight(),
- shorterLength * mMinimumSizePercent);
- minWidth = (int) (minHeight * aspectRatio);
- }
-
- return new Size(minWidth, minHeight);
- }
-
- @Override
- public Size getSizeForAspectRatio(Size size, float aspectRatio) {
- final int smallestSize = Math.min(size.getWidth(), size.getHeight());
- final int minSize = Math.max(getMinEdgeSize(), smallestSize);
-
- final int width;
- final int height;
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size.
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
-
- return new Size(width, height);
- }
- }
-
- public PipSizeSpecHandler(Context context, PipDisplayLayoutState pipDisplayLayoutState) {
- mContext = context;
- mPipDisplayLayoutState = pipDisplayLayoutState;
-
- // choose between two implementations of size spec logic
- if (supportsPipSizeLargeScreen()) {
- mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
- } else {
- mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
- }
-
- reloadResources();
- }
-
- /** Reloads the resources */
- public void onConfigurationChanged() {
- reloadResources();
- }
-
- private void reloadResources() {
- final Resources res = mContext.getResources();
-
- mDefaultMinSize = res.getDimensionPixelSize(
- R.dimen.default_minimal_size_pip_resizable_task);
- mOverridableMinSize = res.getDimensionPixelSize(
- R.dimen.overridable_minimal_size_pip_resizable_task);
-
- final String screenEdgeInsetsDpString = res.getString(
- R.string.config_defaultPictureInPictureScreenEdgeInsets);
- final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
- ? Size.parseSize(screenEdgeInsetsDpString)
- : null;
- mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
- : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
- dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
-
- // update the internal resources of the size spec source's stub
- mSizeSpecSourceImpl.reloadResources();
- }
-
- @NonNull
- private Rect getDisplayBounds() {
- return mPipDisplayLayoutState.getDisplayBounds();
- }
-
- public Point getScreenEdgeInsets() {
- return mScreenEdgeInsets;
- }
-
- /**
- * Returns the inset bounds the PIP window can be visible in.
- */
- public Rect getInsetBounds() {
- Rect insetBounds = new Rect();
- DisplayLayout displayLayout = mPipDisplayLayoutState.getDisplayLayout();
- Rect insets = displayLayout.stableInsets();
- insetBounds.set(insets.left + mScreenEdgeInsets.x,
- insets.top + mScreenEdgeInsets.y,
- displayLayout.width() - insets.right - mScreenEdgeInsets.x,
- displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
- return insetBounds;
- }
-
- /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
- public void setOverrideMinSize(@Nullable Size overrideMinSize) {
- mOverrideMinSize = overrideMinSize;
- }
-
- /** Returns the preferred minimal size specified by the activity in PIP. */
- @Nullable
- public Size getOverrideMinSize() {
- if (mOverrideMinSize != null
- && (mOverrideMinSize.getWidth() < mOverridableMinSize
- || mOverrideMinSize.getHeight() < mOverridableMinSize)) {
- return new Size(mOverridableMinSize, mOverridableMinSize);
- }
-
- return mOverrideMinSize;
- }
-
- /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
- public int getOverrideMinEdgeSize() {
- if (mOverrideMinSize == null) return 0;
- return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight());
- }
-
- public int getMinEdgeSize() {
- return mOverrideMinSize == null ? mDefaultMinSize : getOverrideMinEdgeSize();
- }
-
- /**
- * Returns the size for the max size spec.
- */
- public Size getMaxSize(float aspectRatio) {
- return mSizeSpecSourceImpl.getMaxSize(aspectRatio);
- }
-
- /**
- * Returns the size for the default size spec.
- */
- public Size getDefaultSize(float aspectRatio) {
- return mSizeSpecSourceImpl.getDefaultSize(aspectRatio);
- }
-
- /**
- * Returns the size for the min size spec.
- */
- public Size getMinSize(float aspectRatio) {
- return mSizeSpecSourceImpl.getMinSize(aspectRatio);
- }
-
- /**
- * Returns the adjusted size so that it conforms to the given aspectRatio.
- *
- * @param size current size
- * @param aspectRatio target aspect ratio
- */
- public Size getSizeForAspectRatio(@NonNull Size size, float aspectRatio) {
- if (size.equals(mOverrideMinSize)) {
- return adjustOverrideMinSizeToAspectRatio(aspectRatio);
- }
-
- return mSizeSpecSourceImpl.getSizeForAspectRatio(size, aspectRatio);
- }
-
- /**
- * Returns the adjusted overridden min size if it is set; otherwise, returns null.
- *
- * <p>Overridden min size needs to be adjusted in its own way while making sure that the target
- * aspect ratio is maintained
- *
- * @param aspectRatio target aspect ratio
- */
- @Nullable
- @VisibleForTesting
- Size adjustOverrideMinSizeToAspectRatio(float aspectRatio) {
- if (mOverrideMinSize == null) {
- return null;
- }
- final Size size = getOverrideMinSize();
- final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
- if (sizeAspectRatio > aspectRatio) {
- // Size is wider, fix the width and increase the height
- return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
- } else {
- // Size is taller, fix the height and adjust the width.
- return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
- }
- }
-
- @VisibleForTesting
- boolean supportsPipSizeLargeScreen() {
- // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource
- // can be injected
- return SystemProperties
- .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv();
- }
-
- private boolean isTv() {
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
- }
-
- /** Dumps internal state. */
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
- pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl);
- pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
- pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 500094335258..2ce4fb9e297b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -18,10 +18,10 @@ package com.android.wm.shell.pip.phone;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
@@ -34,7 +34,6 @@ import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.util.Size;
import android.view.DisplayCutout;
@@ -51,12 +50,14 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
@@ -71,20 +72,12 @@ public class PipTouchHandler {
private static final String TAG = "PipTouchHandler";
private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
- private boolean mEnablePipKeepClearAlgorithm =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
-
- @VisibleForTesting
- void setEnablePipKeepClearAlgorithm(boolean value) {
- mEnablePipKeepClearAlgorithm = value;
- }
-
// Allow PIP to resize to a slightly bigger state upon touch
private boolean mEnableResize;
private final Context mContext;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
@NonNull private final PipBoundsState mPipBoundsState;
- @NonNull private final PipSizeSpecHandler mPipSizeSpecHandler;
+ @NonNull private final SizeSpecSource mSizeSpecSource;
private final PipUiEventLogger mPipUiEventLogger;
private final PipDismissTargetHandler mPipDismissTargetHandler;
private final PipTaskOrganizer mPipTaskOrganizer;
@@ -178,7 +171,7 @@ public class PipTouchHandler {
PhonePipMenuController menuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull SizeSpecSource sizeSpecSource,
PipTaskOrganizer pipTaskOrganizer,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
@@ -189,7 +182,7 @@ public class PipTouchHandler {
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mSizeSpecSource = sizeSpecSource;
mPipTaskOrganizer = pipTaskOrganizer;
mMenuController = menuController;
mPipUiEventLogger = pipUiEventLogger;
@@ -228,7 +221,9 @@ public class PipTouchHandler {
// TODO(b/181599115): This should really be initializes as part of the pip controller, but
// until all PIP implementations derive from the controller, just initialize the touch handler
// if it is needed
- shellInit.addInitCallback(this::onInit, this);
+ if (!PipUtils.isPip2ExperimentEnabled()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
/**
@@ -410,7 +405,7 @@ public class PipTouchHandler {
// Calculate the expanded size
float aspectRatio = (float) normalBounds.width() / normalBounds.height();
- Size expandedSize = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
+ Size expandedSize = mSizeSpecSource.getDefaultSize(aspectRatio);
mPipBoundsState.setExpandedBounds(
new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
Rect expandedMovementBounds = new Rect();
@@ -426,48 +421,6 @@ public class PipTouchHandler {
mIsImeShowing ? mImeOffset : 0,
!mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0);
- // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
- // occluded by the IME or shelf.
- if (fromImeAdjustment || fromShelfAdjustment) {
- if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
- // Defer the update of the current movement bounds until after the user finishes
- // touching the screen
- } else if (mEnablePipKeepClearAlgorithm) {
- // Ignore moving PiP if keep clear algorithm is enabled, since IME and shelf height
- // now are accounted for in the keep clear algorithm calculations
- } else {
- final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu();
- final Rect toMovementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
- toMovementBounds, mIsImeShowing ? mImeHeight : 0);
- final int prevBottom = mPipBoundsState.getMovementBounds().bottom
- - mMovementBoundsExtraOffsets;
- // This is to handle landscape fullscreen IMEs, don't apply the extra offset in this
- // case
- final int toBottom = toMovementBounds.bottom < toMovementBounds.top
- ? toMovementBounds.bottom
- : toMovementBounds.bottom - extraOffset;
-
- if (isExpanded) {
- curBounds.set(mPipBoundsState.getExpandedBounds());
- mPipBoundsAlgorithm.getSnapAlgorithm().applySnapFraction(curBounds,
- toMovementBounds, mSavedSnapFraction);
- }
-
- if (prevBottom < toBottom) {
- // The movement bounds are expanding
- if (curBounds.top > prevBottom - mBottomOffsetBufferPx) {
- mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top);
- }
- } else if (prevBottom > toBottom) {
- // The movement bounds are shrinking
- if (curBounds.top > toBottom - mBottomOffsetBufferPx) {
- mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top);
- }
- }
- }
- }
-
// Update the movement bounds after doing the calculations based on the old movement bounds
// above
mPipBoundsState.setNormalMovementBounds(normalMovementBounds);
@@ -514,10 +467,10 @@ public class PipTouchHandler {
private void updatePinchResizeSizeConstraints(float aspectRatio) {
final int minWidth, minHeight, maxWidth, maxHeight;
- minWidth = mPipSizeSpecHandler.getMinSize(aspectRatio).getWidth();
- minHeight = mPipSizeSpecHandler.getMinSize(aspectRatio).getHeight();
- maxWidth = mPipSizeSpecHandler.getMaxSize(aspectRatio).getWidth();
- maxHeight = mPipSizeSpecHandler.getMaxSize(aspectRatio).getHeight();
+ minWidth = mSizeSpecSource.getMinSize(aspectRatio).getWidth();
+ minHeight = mSizeSpecSource.getMinSize(aspectRatio).getHeight();
+ maxWidth = mSizeSpecSource.getMaxSize(aspectRatio).getWidth();
+ maxHeight = mSizeSpecSource.getMaxSize(aspectRatio).getHeight();
mPipResizeGestureHandler.updateMinSize(minWidth, minHeight);
mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
index 3b44f10ebe62..4bba9690707a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
@@ -35,8 +35,8 @@ import android.content.Context;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
-import com.android.wm.shell.pip.PipMediaController;
-import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 825b96921a22..a48e969fde35 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -36,10 +36,11 @@ import androidx.annotation.NonNull;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -62,9 +63,10 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
public TvPipBoundsAlgorithm(Context context,
@NonNull TvPipBoundsState tvPipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
+ @NonNull SizeSpecSource sizeSpecSource) {
super(context, tvPipBoundsState, pipSnapAlgorithm,
- new PipKeepClearAlgorithmInterface() {}, pipSizeSpecHandler);
+ new PipKeepClearAlgorithmInterface() {}, pipDisplayLayoutState, sizeSpecSource);
this.mTvPipBoundsState = tvPipBoundsState;
this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
reloadResources(context);
@@ -291,7 +293,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
int maxHeight = displayLayout.height()
- - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().y)
+ - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().y)
- pipDecorations.top - pipDecorations.bottom;
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
@@ -311,7 +313,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
int maxWidth = displayLayout.width()
- - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().x)
+ - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().x)
- pipDecorations.left - pipDecorations.right;
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
index 8d4a38442ce5..8a215b4b2e25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.pip.tv;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index e1737eccc6e1..47a8cc89559e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -29,10 +29,10 @@ import android.util.Size;
import android.view.Gravity;
import android.view.View;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -76,9 +76,9 @@ public class TvPipBoundsState extends PipBoundsState {
private Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull SizeSpecSource sizeSpecSource,
@NonNull PipDisplayLayoutState pipDisplayLayoutState) {
- super(context, pipSizeSpecHandler, pipDisplayLayoutState);
+ super(context, sizeSpecSource, pipDisplayLayoutState);
mContext = context;
updateDefaultGravity();
mPreviousCollapsedGravity = mDefaultGravity;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 02eeb2ac4fd5..46336ce0655a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -47,12 +47,12 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
index a94bd6ec1040..93f6826ac96b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -21,12 +21,12 @@ import android.graphics.Point
import android.graphics.Rect
import android.util.Size
import android.view.Gravity
-import com.android.wm.shell.pip.PipBoundsState
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP
+import com.android.wm.shell.common.pip.PipBoundsState
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -54,11 +54,11 @@ class TvPipKeepClearAlgorithm() {
* the unstash timeout if already stashed.
*/
data class Placement(
- val bounds: Rect,
- val anchorBounds: Rect,
- @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
- val unstashDestinationBounds: Rect? = null,
- val triggerStash: Boolean = false
+ val bounds: Rect,
+ val anchorBounds: Rect,
+ @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
+ val unstashDestinationBounds: Rect? = null,
+ val triggerStash: Boolean = false
) {
/** Bounds to use if the PiP should not be stashed. */
fun getUnstashedBounds() = unstashDestinationBounds ?: bounds
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 613791ccc062..45e1cde8f9a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -55,7 +55,7 @@ import com.android.internal.widget.LinearLayoutManager;
import com.android.internal.widget.RecyclerView;
import com.android.wm.shell.R;
import com.android.wm.shell.common.TvWindowMenuActionButton;
-import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index f22ee595e6c9..1c94625ddde9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -36,9 +36,9 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ImageUtils;
import com.android.wm.shell.R;
-import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 4819f665d6d3..f315afba9a03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -25,18 +25,18 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.Objects;
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 d3253a5e4d94..f24b2b385cad 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
@@ -21,8 +21,8 @@ import android.content.Context;
import androidx.annotation.NonNull;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTransition;
import com.android.wm.shell.pip.PipTransitionState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
new file mode 100644
index 000000000000..ec09827fa4d1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
@@ -0,0 +1,3 @@
+# WM shell sub-module pip owner
+hwwang@google.com
+mateuszc@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
new file mode 100644
index 000000000000..b8e4c04ac262
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+/** Placeholder, for demonstrate purpose only. */
+public class PipTransition extends PipTransitionController {
+ public PipTransition(
+ @NonNull ShellInit shellInit,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull Transitions transitions,
+ PipBoundsState pipBoundsState,
+ PipMenuController pipMenuController,
+ PipBoundsAlgorithm pipBoundsAlgorithm) {
+ super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
+ pipBoundsAlgorithm);
+ }
+
+ @Override
+ protected void onInit() {
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ mTransitions.addHandler(this);
+ }
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return false;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {}
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md
new file mode 100644
index 000000000000..415ea8f959cc
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md
@@ -0,0 +1,3 @@
+# PLACEHOLDER
+
+This is meant to be the doc for the pip2 package. \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 2a61445b27ba..05e4af3302af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -35,7 +35,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
WM_SHELL_RECENTS_TRANSITION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
"ShellRecents"),
WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
- Consts.TAG_WM_SHELL),
+ "ShellDragAndDrop"),
WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_STARTING_WINDOW),
WM_SHELL_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
@@ -49,12 +49,14 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SPLIT_SCREEN),
WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
- WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_BUBBLES(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ "Bubbles"),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
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 843e5af67af9..50ba8975802b 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
@@ -219,6 +219,13 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
private ArrayList<TaskState> mPausingTasks = null;
/**
+ * List of tasks were pausing but closed in a subsequent merged transition. If a
+ * closing task is reopened, the leash is not initially hidden since it is already
+ * visible.
+ */
+ private ArrayList<TaskState> mClosingTasks = null;
+
+ /**
* List of tasks that we are switching to. Upon finish, these will remain visible and
* on top.
*/
@@ -369,6 +376,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
mFinishTransaction = null;
mPausingTasks = null;
+ mClosingTasks = null;
mOpeningTasks = null;
mInfo = null;
mTransition = null;
@@ -413,6 +421,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mFinishCB = finishCB;
mFinishTransaction = finishT;
mPausingTasks = new ArrayList<>();
+ mClosingTasks = new ArrayList<>();
mOpeningTasks = new ArrayList<>();
mLeashMap = new ArrayMap<>();
mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
@@ -567,15 +576,18 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
&& taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
// Tasks that are always on top (e.g. bubbles), will handle their own transition
// as they are on top of everything else. So cancel the merge here.
- cancel("task #" + taskInfo.taskId + " is always_on_top");
+ cancel(false /* toHome */, false /* withScreenshots */,
+ "task #" + taskInfo.taskId + " is always_on_top");
return;
}
final boolean isRootTask = taskInfo != null
&& TransitionInfo.isIndependent(change, info);
+ final boolean isRecentsTask = mRecentsTask != null
+ && mRecentsTask.equals(change.getContainer());
hasTaskChange = hasTaskChange || isRootTask;
final boolean isLeafTask = leafTaskFilter.test(change);
if (TransitionUtil.isOpeningType(change.getMode())) {
- if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+ if (isRecentsTask) {
recentsOpening = change;
} else if (isRootTask || isLeafTask) {
if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
@@ -590,7 +602,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
openingTaskIsLeafs.add(isLeafTask ? 1 : 0);
}
} else if (TransitionUtil.isClosingType(change.getMode())) {
- if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+ if (isRecentsTask) {
foundRecentsClosing = true;
} else if (isRootTask || isLeafTask) {
if (closingTasks == null) {
@@ -601,7 +613,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
} else if (change.getMode() == TRANSIT_CHANGE) {
// Finish recents animation if the display is changed, so the default
// transition handler can play the animation such as rotation effect.
- if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) {
+ if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)
+ && info.getType() == TRANSIT_CHANGE) {
// This call to cancel will use the screenshots taken preemptively in
// handleMidTransitionRequest() prior to the display changing
cancel(mWillFinishToHome, true /* withScreenshots */, "display change");
@@ -611,7 +624,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
hasChangingApp = true;
} else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
- && !mRecentsTask.equals(change.getContainer())) {
+ && !isRecentsTask ) {
// Unless it is a 3p launcher. This means that the 3p launcher was already
// visible (eg. the "pausing" task is translucent over the 3p launcher).
// Treat it as if we are "re-opening" the 3p launcher.
@@ -655,7 +668,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
final TransitionInfo.Change change = closingTasks.get(i);
final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
if (pausingIdx >= 0) {
- mPausingTasks.remove(pausingIdx);
+ // We are closing the pausing task, but it is still visible and can be
+ // restart by another transition prior to this transition finishing
+ final TaskState closingTask = mPausingTasks.remove(pausingIdx);
+ mClosingTasks.add(closingTask);
didMergeThings = true;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
" closing pausing taskId=%d", change.getTaskInfo().taskId);
@@ -691,7 +707,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
for (int i = 0; i < openingTasks.size(); ++i) {
final TransitionInfo.Change change = openingTasks.get(i);
final boolean isLeaf = openingTaskIsLeafs.get(i) == 1;
- int pausingIdx = TaskState.indexOf(mPausingTasks, change);
+ final int closingIdx = TaskState.indexOf(mClosingTasks, change);
+ if (closingIdx >= 0) {
+ // Remove opening tasks from closing set
+ mClosingTasks.remove(closingIdx);
+ }
+ final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
if (pausingIdx >= 0) {
// Something is showing/opening a previously-pausing app.
if (isLeaf) {
@@ -714,12 +735,23 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
appearedTargets[nextTargetIdx++] = target;
// reparent into the original `mInfo` since that's where we are animating.
final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+ final boolean wasClosing = closingIdx >= 0;
t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
t.setLayer(target.leash, layer);
- // Hide the animation leash, let listener show it.
- t.hide(target.leash);
+ if (wasClosing) {
+ // App was previously visible and is closing
+ t.show(target.leash);
+ t.setAlpha(target.leash, 1f);
+ // Also override the task alpha as it was set earlier when dispatching
+ // the transition and setting up the leash to hide the
+ t.setAlpha(change.getLeash(), 1f);
+ } else {
+ // Hide the animation leash, let the listener show it
+ t.hide(target.leash);
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " opening new leaf taskId=%d", target.taskId);
+ " opening new leaf taskId=%d wasClosing=%b",
+ target.taskId, wasClosing);
mOpeningTasks.add(new TaskState(change, target.leash));
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -767,7 +799,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
}
}
- finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ finishCallback.onTransitionFinished(null /* wct */);
}
/** For now, just set-up a jump-cut to the new activity. */
@@ -933,7 +965,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
cleanUp();
- finishCB.onTransitionFinished(wct.isEmpty() ? null : wct, null /* wctCB */);
+ finishCB.onTransitionFinished(wct.isEmpty() ? null : wct);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index c414e708b28d..14304a3c0aac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -27,6 +27,7 @@ import android.view.RemoteAnimationTarget;
import android.window.RemoteTransition;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import com.android.wm.shell.splitscreen.ISplitSelectListener;
/**
* Interface that is exposed to remote callers to manipulate the splitscreen feature.
@@ -44,6 +45,16 @@ interface ISplitScreen {
oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
/**
+ * Registers a split select listener.
+ */
+ oneway void registerSplitSelectListener(in ISplitSelectListener listener) = 20;
+
+ /**
+ * Unregisters a split select listener.
+ */
+ oneway void unregisterSplitSelectListener(in ISplitSelectListener listener) = 21;
+
+ /**
* Removes a task from the side stage.
*/
oneway void removeFromSideStage(int taskId) = 4;
@@ -148,4 +159,4 @@ interface ISplitScreen {
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
}
-// Last id = 19 \ No newline at end of file
+// Last id = 21 \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
new file mode 100644
index 000000000000..a25f39148b89
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.graphics.Rect;
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-select callbacks.
+ */
+interface ISplitSelectListener {
+ /**
+ * Called when a task requests to enter split select
+ */
+ boolean onRequestSplitSelect(in RunningTaskInfo taskInfo, int splitPosition, in Rect taskBounds);
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index 89538cb394d4..e52235fda80f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -24,6 +24,9 @@ import android.window.WindowContainerTransaction;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+
+import java.util.Optional;
/**
* Main stage for split-screen mode. When split-screen is active all standard activity types launch
@@ -35,9 +38,10 @@ class MainStage extends StageTaskListener {
MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession, IconProvider iconProvider) {
+ SurfaceSession surfaceSession, IconProvider iconProvider,
+ Optional<WindowDecorViewModel> windowDecorViewModel) {
super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
- iconProvider);
+ iconProvider, windowDecorViewModel);
}
boolean isActive() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index 8639b36faf4c..9903113c5453 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -25,6 +25,9 @@ import android.window.WindowContainerTransaction;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+
+import java.util.Optional;
/**
* Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
@@ -37,9 +40,10 @@ class SideStage extends StageTaskListener {
SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession, IconProvider iconProvider) {
+ SurfaceSession surfaceSession, IconProvider iconProvider,
+ Optional<WindowDecorViewModel> windowDecorViewModel) {
super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
- iconProvider);
+ iconProvider, windowDecorViewModel);
}
boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 2f2bc77b804b..ad4049320d93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.graphics.Rect;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -63,6 +64,14 @@ public interface SplitScreen {
default void onSplitVisibilityChanged(boolean visible) {}
}
+ /** Callback interface for listening to requests to enter split select */
+ interface SplitSelectListener {
+ default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+ int splitPosition, Rect taskBounds) {
+ return false;
+ }
+ }
+
/** Registers listener that gets split screen callback. */
void registerSplitScreenListener(@NonNull SplitScreenListener listener,
@NonNull Executor executor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index e7a367f1992d..f90ee586e696 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -31,6 +31,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -50,6 +51,7 @@ import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
@@ -76,6 +78,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -84,6 +87,7 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -94,12 +98,14 @@ import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Class manages split-screen multitasking mode and implements the main interface
@@ -171,6 +177,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final TransactionPool mTransactionPool;
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
+ private final LaunchAdjacentController mLaunchAdjacentController;
+ private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
+ private final Optional<DesktopTasksController> mDesktopTasksController;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
private final String[] mAppsSupportMultiInstances;
@@ -197,6 +206,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
+ Optional<WindowDecorViewModel> windowDecorViewModel,
+ Optional<DesktopTasksController> desktopTasksController,
ShellExecutor mainExecutor) {
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
@@ -213,6 +225,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mTransactionPool = transactionPool;
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
+ mLaunchAdjacentController = launchAdjacentController;
+ mWindowDecorViewModel = windowDecorViewModel;
+ mDesktopTasksController = desktopTasksController;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
@@ -242,6 +257,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
TransactionPool transactionPool,
IconProvider iconProvider,
RecentTasksController recentTasks,
+ LaunchAdjacentController launchAdjacentController,
+ WindowDecorViewModel windowDecorViewModel,
+ DesktopTasksController desktopTasksController,
ShellExecutor mainExecutor,
StageCoordinator stageCoordinator) {
mShellCommandHandler = shellCommandHandler;
@@ -259,6 +277,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mTransactionPool = transactionPool;
mIconProvider = iconProvider;
mRecentTasksOptional = Optional.of(recentTasks);
+ mLaunchAdjacentController = launchAdjacentController;
+ mWindowDecorViewModel = Optional.of(windowDecorViewModel);
+ mDesktopTasksController = Optional.of(desktopTasksController);
mStageCoordinator = stageCoordinator;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
shellInit.addInitCallback(this::onInit, this);
@@ -291,13 +312,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator = createStageCoordinator();
}
mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
+ mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
+ mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
}
protected StageCoordinator createStageCoordinator() {
return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mDisplayController, mDisplayImeController,
- mDisplayInsetsController, mTransitions, mTransactionPool,
- mIconProvider, mMainExecutor, mRecentTasksOptional);
+ mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
+ mMainExecutor, mRecentTasksOptional, mLaunchAdjacentController,
+ mWindowDecorViewModel);
}
@Override
@@ -407,8 +431,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
* transition.
*/
public void prepareExitSplitScreen(WindowContainerTransaction wct,
- @StageType int stageToTop) {
+ @StageType int stageToTop, @ExitReason int reason) {
mStageCoordinator.prepareExitSplitScreen(stageToTop, wct);
+ mStageCoordinator.clearSplitPairedInRecents(reason);
}
public void enterSplitScreen(int taskId, boolean leftOrTop) {
@@ -451,6 +476,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.unregisterSplitScreenListener(listener);
}
+ /** Register a split select listener */
+ public void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mStageCoordinator.registerSplitSelectListener(listener);
+ }
+
+ /** Unregister a split select listener */
+ public void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mStageCoordinator.unregisterSplitSelectListener(listener);
+ }
+
public void goToFullscreenFromSplit() {
mStageCoordinator.goToFullscreenFromSplit();
}
@@ -468,6 +503,18 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator.getActivateSplitPosition(taskInfo);
}
+ /**
+ * Move a task to split select
+ * @param taskInfo the task being moved to split select
+ * @param wct transaction to apply if this is a valid request
+ * @param splitPosition the split position this task should move to
+ * @param taskBounds current freeform bounds of the task entering split
+ */
+ public void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+ WindowContainerTransaction wct, int splitPosition, Rect taskBounds) {
+ mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds);
+ }
+
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -537,6 +584,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startShortcut",
+ "app package " + packageName + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
return;
@@ -566,6 +615,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startShortcutAndTaskWithLegacyTransition",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -598,12 +649,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startShortcutAndTask",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
}
- mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
- splitPosition, splitRatio, remoteTransition, instanceId);
+ mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId);
}
/**
@@ -633,6 +686,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntentAndTaskWithLegacyTransition",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -663,6 +718,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntentAndTask",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -691,6 +748,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
pendingIntent2 = null;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntentsWithLegacyTransition",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -709,24 +768,38 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
Intent fillInIntent2 = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+ final ActivityOptions activityOptions1 = options1 != null
+ ? ActivityOptions.fromBundle(options1) : ActivityOptions.makeBasic();
+ final ActivityOptions activityOptions2 = options2 != null
+ ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
fillInIntent2 = new Intent();
fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+
+ if (shortcutInfo1 != null) {
+ activityOptions1.setApplyMultipleTaskFlagForShortcut(true);
+ }
+ if (shortcutInfo2 != null) {
+ activityOptions2.setApplyMultipleTaskFlagForShortcut(true);
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
pendingIntent2 = null;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntents",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
}
- mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1,
- pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio,
- remoteTransition, instanceId);
+ mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1,
+ activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2,
+ activityOptions2.toBundle(), splitPosition, splitRatio, remoteTransition,
+ instanceId);
}
@Override
@@ -766,6 +839,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntent",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
return;
@@ -1043,6 +1118,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private SplitScreenController mController;
private final SingleInstanceRemoteListener<SplitScreenController,
ISplitScreenListener> mListener;
+ private final SingleInstanceRemoteListener<SplitScreenController,
+ ISplitSelectListener> mSelectListener;
private final SplitScreen.SplitScreenListener mSplitScreenListener =
new SplitScreen.SplitScreenListener() {
@Override
@@ -1056,11 +1133,27 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
};
+ private final SplitScreen.SplitSelectListener mSplitSelectListener =
+ new SplitScreen.SplitSelectListener() {
+ @Override
+ public boolean onRequestEnterSplitSelect(
+ ActivityManager.RunningTaskInfo taskInfo, int splitPosition,
+ Rect taskBounds) {
+ AtomicBoolean result = new AtomicBoolean(false);
+ mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo,
+ splitPosition, taskBounds)));
+ return result.get();
+ }
+ };
+
public ISplitScreenImpl(SplitScreenController controller) {
mController = controller;
mListener = new SingleInstanceRemoteListener<>(controller,
c -> c.registerSplitScreenListener(mSplitScreenListener),
c -> c.unregisterSplitScreenListener(mSplitScreenListener));
+ mSelectListener = new SingleInstanceRemoteListener<>(controller,
+ c -> c.registerSplitSelectListener(mSplitSelectListener),
+ c -> c.unregisterSplitSelectListener(mSplitSelectListener));
}
/**
@@ -1086,6 +1179,18 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
+ public void registerSplitSelectListener(ISplitSelectListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "registerSplitSelectListener",
+ (controller) -> mSelectListener.register(listener));
+ }
+
+ @Override
+ public void unregisterSplitSelectListener(ISplitSelectListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "unregisterSplitSelectListener",
+ (controller) -> mSelectListener.unregister());
+ }
+
+ @Override
public void exitSplitScreen(int toTopTaskId) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
(controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
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 d21f8a48e62a..7dec12aac39b 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
@@ -43,7 +43,6 @@ import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
@@ -109,7 +108,7 @@ class SplitScreenTransitions {
if (pendingTransition.mCanceled) {
// The pending transition was canceled, so skip playing animation.
startTransaction.apply();
- onFinish(null /* wct */, null /* wctCB */);
+ onFinish(null /* wct */);
return;
}
@@ -211,7 +210,7 @@ class SplitScreenTransitions {
}
}
t.apply();
- onFinish(null /* wct */, null /* wctCB */);
+ onFinish(null /* wct */);
}
/** Play animation for drag divider dismiss transition. */
@@ -238,7 +237,7 @@ class SplitScreenTransitions {
mAnimations.remove(va);
if (animated) {
mTransitions.getMainExecutor().execute(() -> {
- onFinish(null /* wct */, null /* wctCB */);
+ onFinish(null /* wct */);
});
}
});
@@ -250,7 +249,7 @@ class SplitScreenTransitions {
}
}
startTransaction.apply();
- onFinish(null /* wct */, null /* wctCB */);
+ onFinish(null /* wct */);
}
/** Play animation for resize transition. */
@@ -283,7 +282,7 @@ class SplitScreenTransitions {
mAnimations.remove(va);
if (animated) {
mTransitions.getMainExecutor().execute(() -> {
- onFinish(null /* wct */, null /* wctCB */);
+ onFinish(null /* wct */);
});
}
});
@@ -291,7 +290,7 @@ class SplitScreenTransitions {
}
startTransaction.apply();
- onFinish(null /* wct */, null /* wctCB */);
+ onFinish(null /* wct */);
}
boolean isPendingTransition(IBinder transition) {
@@ -325,8 +324,10 @@ class SplitScreenTransitions {
void startFullscreenTransition(WindowContainerTransaction wct,
@Nullable RemoteTransition handler) {
- mTransitions.startTransition(TRANSIT_OPEN, wct,
- new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler));
+ OneShotRemoteHandler fullscreenHandler =
+ new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler);
+ fullscreenHandler.setTransition(mTransitions
+ .startTransition(TRANSIT_OPEN, wct, fullscreenHandler));
}
@@ -391,7 +392,7 @@ class SplitScreenTransitions {
if (mPendingResize != null) {
mPendingResize.cancel(null);
mAnimations.clear();
- onFinish(null /* wct */, null /* wctCB */);
+ onFinish(null /* wct */);
}
IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
@@ -450,7 +451,7 @@ class SplitScreenTransitions {
}
}
- void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
+ void onFinish(WindowContainerTransaction wct) {
if (!mAnimations.isEmpty()) return;
if (wct == null) wct = new WindowContainerTransaction();
@@ -470,7 +471,7 @@ class SplitScreenTransitions {
mOnFinish.run();
if (mFinishCallback != null) {
- mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */);
+ mFinishCallback.onTransitionFinished(wct /* wct */);
mFinishCallback = null;
}
}
@@ -495,7 +496,7 @@ class SplitScreenTransitions {
mTransactionPool.release(transaction);
mTransitions.getMainExecutor().execute(() -> {
mAnimations.remove(va);
- onFinish(null /* wct */, null /* wctCB */);
+ onFinish(null /* wct */);
});
}
});
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 7699e2f7c13d..3b1801b8d821 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
@@ -28,6 +28,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
@@ -41,6 +42,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -81,7 +83,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
-import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
@@ -121,6 +122,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -138,11 +140,14 @@ import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.SplitBounds;
import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
/**
* Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -182,6 +187,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+ private final Set<SplitScreen.SplitSelectListener> mSelectListeners = new HashSet<>();
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
@@ -193,6 +199,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// if user is opening another task(s).
private final ArrayList<Integer> mPausingTasks = new ArrayList<>();
private final Optional<RecentTasksController> mRecentTasks;
+ private final LaunchAdjacentController mLaunchAdjacentController;
+ private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
@@ -213,11 +221,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private boolean mIsDropEntering;
private boolean mIsExiting;
private boolean mIsRootTranslucent;
+ @VisibleForTesting
+ int mTopStageAfterFoldDismiss;
private DefaultMixedHandler mMixedHandler;
private final Toast mSplitUnsupportedToast;
private SplitRequest mSplitRequest;
+ /**
+ * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support
+ * CompatUI layouts. CompatUI is handled separately by MainStage and SideStage.
+ */
+ @Override
+ public boolean supportCompatUI() {
+ return false;
+ }
+
class SplitRequest {
@SplitPosition
int mActivatePosition;
@@ -270,7 +289,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider, ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks) {
+ Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
+ Optional<WindowDecorViewModel> windowDecorViewModel) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -278,6 +299,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mLogger = new SplitscreenEventLogger();
mMainExecutor = mainExecutor;
mRecentTasks = recentTasks;
+ mLaunchAdjacentController = launchAdjacentController;
+ mWindowDecorViewModel = windowDecorViewModel;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
@@ -288,7 +311,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStageListener,
mSyncQueue,
mSurfaceSession,
- iconProvider);
+ iconProvider,
+ mWindowDecorViewModel);
mSideStage = new SideStage(
mContext,
mTaskOrganizer,
@@ -296,7 +320,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStageListener,
mSyncQueue,
mSurfaceSession,
- iconProvider);
+ iconProvider,
+ mWindowDecorViewModel);
mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
@@ -323,7 +348,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks) {
+ Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
+ Optional<WindowDecorViewModel> windowDecorViewModel) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -340,6 +367,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mLogger = new SplitscreenEventLogger();
mMainExecutor = mainExecutor;
mRecentTasks = recentTasks;
+ mLaunchAdjacentController = launchAdjacentController;
+ mWindowDecorViewModel = windowDecorViewModel;
mDisplayController.addDisplayWindowListener(this);
transitions.addHandler(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
@@ -445,6 +474,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return mLogger;
}
+ void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+ WindowContainerTransaction wct, int splitPosition, Rect taskBounds) {
+ boolean enteredSplitSelect = false;
+ for (SplitScreen.SplitSelectListener listener : mSelectListeners) {
+ enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo, splitPosition,
+ taskBounds);
+ }
+ if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct);
+ }
+
void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
Bundle options, UserHandle user) {
final boolean isEnteringSplit = !isSplitActive();
@@ -459,6 +498,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (isEnteringSplit && mSideStage.getChildCount() == 0) {
mMainExecutor.execute(() -> exitSplitScreen(
null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("startShortcut",
+ "side stage was not populated"));
mSplitUnsupportedToast.show();
}
@@ -546,6 +587,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (isEnteringSplit && mSideStage.getChildCount() == 0) {
mMainExecutor.execute(() -> exitSplitScreen(
null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("startIntentLegacy",
+ "side stage was not populated"));
mSplitUnsupportedToast.show();
}
@@ -675,6 +718,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.setDivideRatio(splitRatio);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
+ wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ false /* reparentLeafTaskIfRelaunch */);
setRootForceTranslucent(false, wct);
// Make sure the launch options will put tasks in the corresponding split roots
@@ -718,12 +763,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.activate(wct, false /* reparent */);
}
+ setSideStagePosition(splitPosition, wct);
mSplitLayout.setDivideRatio(splitRatio);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
+ wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ false /* reparentLeafTaskIfRelaunch */);
setRootForceTranslucent(false, wct);
- setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
addActivityOptions(options1, mSideStage);
if (shortcutInfo1 != null) {
@@ -1076,6 +1123,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainExecutor.execute(() ->
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
+ "main or side stage was not populated."));
mSplitUnsupportedToast.show();
} else {
mSyncQueue.queue(evictWct);
@@ -1095,6 +1144,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
+ "main or side stage was not populated"));
mSplitUnsupportedToast.show();
return;
}
@@ -1279,20 +1330,30 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
final boolean oneStageVisible =
mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
- if (oneStageVisible) {
+ if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) {
// Dismiss split because there's show-when-locked activity showing on top of keyguard.
// Also make sure the task contains show-when-locked activity remains on top after split
// dismissed.
- if (!ENABLE_SHELL_TRANSITIONS) {
- final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
- exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- } else {
- final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
+ exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ }
+
+ // Dismiss split if the flag record any side of stages.
+ if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Need manually clear here due to this transition might be aborted due to keyguard
+ // on top and lead to no visible change.
+ clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED);
final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(dismissTop, wct);
- mSplitTransitions.startDismissTransition(wct, this, dismissTop,
- EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
+ mSplitTransitions.startDismissTransition(wct, this,
+ mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
+ } else {
+ exitSplitScreen(
+ mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ EXIT_REASON_DEVICE_FOLDED);
}
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
}
}
@@ -1330,15 +1391,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!mMainStage.isActive() || mIsExiting) return;
onSplitScreenExit();
+ clearSplitPairedInRecents(exitReason);
- mRecentTasks.ifPresent(recentTasks -> {
- // Notify recents if we are exiting in a way that breaks the pair, and disable further
- // updates to splits in the recents until we enter split again
- if (shouldBreakPairedTaskInRecents(exitReason) && mShouldUpdateRecents) {
- recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId());
- recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId());
- }
- });
mShouldUpdateRecents = false;
mIsDividerRemoteAnimating = false;
mSplitRequest = null;
@@ -1465,6 +1519,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ void clearSplitPairedInRecents(@ExitReason int exitReason) {
+ if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return;
+
+ mRecentTasks.ifPresent(recentTasks -> {
+ // Notify recents if we are exiting in a way that breaks the pair, and disable further
+ // updates to splits in the recents until we enter split again
+ mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+ mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+ });
+ }
+
/**
* Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
* an existing WindowContainerTransaction (rather than applying immediately). This is intended
@@ -1490,6 +1555,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
boolean resizeAnim) {
onSplitScreenEnter();
+ // Preemptively reset the reparenting behavior if we know that we are entering, as starting
+ // split tasks with activity trampolines can inadvertently trigger the task to be
+ // reparented out of the split root mid-launch
+ wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ false /* setReparentLeafTaskIfRelaunch */);
if (isSplitActive()) {
prepareBringSplit(wct, taskInfo, startPosition, resizeAnim);
} else {
@@ -1547,6 +1617,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// split bounds.
wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token,
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
+ mSplitLayout.getInvisibleBounds(mTempRect1);
+ mSplitLayout.setTaskBounds(wct, mSideStage.mRootTaskInfo, mTempRect1);
}
wct.reorder(mRootTaskInfo.token, true);
setRootForceTranslucent(false, wct);
@@ -1616,6 +1688,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mListeners.remove(listener);
}
+ void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mSelectListeners.add(listener);
+ }
+
+ void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mSelectListeners.remove(listener);
+ }
+
void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
@@ -1733,12 +1813,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mRootTaskInfo == null || mRootTaskInfo.taskId != taskInfo.taskId) {
throw new IllegalArgumentException(this + "\n Unknown task info changed: " + taskInfo);
}
-
+ mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
mRootTaskInfo = taskInfo;
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
- && mMainStage.isActive()
- && !ENABLE_SHELL_TRANSITIONS) {
+ && mMainStage.isActive()) {
// Clear the divider remote animating flag as the divider will be re-rendered to apply
// the new rotation config.
mIsDividerRemoteAnimating = false;
@@ -1781,7 +1860,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
- wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
setRootForceTranslucent(true, wct);
mSplitLayout.getInvisibleBounds(mTempRect1);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
@@ -1789,6 +1867,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSyncQueue.runInSync(t -> {
t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top);
});
+ mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token);
}
/** Callback when split roots have child task appeared under it, this is a little different from
@@ -1818,9 +1897,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void onRootTaskVanished() {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (mRootTaskInfo != null) {
- wct.clearLaunchAdjacentFlagRoot(mRootTaskInfo.token);
- }
+ mLaunchAdjacentController.clearLaunchAdjacentRoot();
applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED);
mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout);
}
@@ -2178,21 +2255,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayController.addDisplayChangingController(this::onDisplayChange);
}
- @Override
- public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- if (displayId != DEFAULT_DISPLAY) {
- return;
- }
- if (mSplitLayout != null && mSplitLayout.isDensityChanged(newConfig.densityDpi)
- && mMainStage.isActive()
- && mSplitLayout.updateConfiguration(newConfig)
- && ENABLE_SHELL_TRANSITIONS) {
- mSplitLayout.update(null /* t */);
- onLayoutSizeChanged(mSplitLayout);
- }
- }
-
- void updateSurfaces(SurfaceControl.Transaction transaction) {
+ /**
+ * Update surfaces of the split screen layout based on the current state
+ * @param transaction to write the updates to
+ */
+ public void updateSurfaces(SurfaceControl.Transaction transaction) {
updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false);
mSplitLayout.update(transaction);
}
@@ -2211,26 +2278,18 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@VisibleForTesting
void onFoldedStateChanged(boolean folded) {
- int topStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
if (!folded) return;
- if (!mMainStage.isActive()) return;
+ if (!isSplitActive() || !isSplitScreenVisible()) return;
+ // To avoid split dismiss when user fold the device and unfold to use later, we only
+ // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss
+ // when user interact on phone folded.
if (mMainStage.isFocused()) {
- topStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+ mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
} else if (mSideStage.isFocused()) {
- topStageAfterFoldDismiss = STAGE_TYPE_SIDE;
- }
-
- if (ENABLE_SHELL_TRANSITIONS) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(topStageAfterFoldDismiss, wct);
- mSplitTransitions.startDismissTransition(wct, this,
- topStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
- } else {
- exitSplitScreen(
- topStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
- EXIT_REASON_DEVICE_FOLDED);
+ mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
}
}
@@ -2364,6 +2423,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// so appends operations to exit split.
prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
}
+ } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null
+ && isSplitScreenVisible()) {
+ // Split include show when lock activity case, check the top activity under which
+ // stage and move it to the top.
+ int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity)
+ ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ prepareExitSplitScreen(top, out);
+ mSplitTransitions.setDismissTransition(transition, top,
+ EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
// When split in the background, it should be only opening/dismissing transition and
@@ -2519,6 +2587,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// so don't handle it.
Log.e(TAG, "Somehow removed the last task in a stage outside of a proper "
+ "transition.");
+ // This new transition would be merged to current one so we need to clear
+ // tile manually here.
+ clearSplitPairedInRecents(EXIT_REASON_APP_FINISHED);
final WindowContainerTransaction wct = new WindowContainerTransaction();
final int dismissTop = (dismissStages.size() == 1
&& getStageType(dismissStages.valueAt(0)) == STAGE_TYPE_MAIN)
@@ -2688,19 +2759,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
== TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
// Open to side should only be used when split already active and foregorund.
if (mainChild == null && sideChild == null) {
- Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
+ Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
+ "Launched a task in split, but didn't receive any task in transition."));
// This should happen when the target app is already on front, so just cancel.
mSplitTransitions.mPendingEnter.cancel(null);
return true;
}
} else {
if (mainChild == null || sideChild == null) {
- Log.w(TAG, "Launched 2 tasks in split, but didn't receive"
- + " 2 tasks in transition. Possibly one of them failed to launch");
final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
(sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
mSplitTransitions.mPendingEnter.cancel(
(cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+ Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
+ "launched 2 tasks in split, but didn't receive "
+ + "2 tasks in transition. Possibly one of them failed to launch"));
+ if (mRecentTasks.isPresent() && mainChild != null) {
+ mRecentTasks.get().removeSplitPair(mainChild.getTaskInfo().taskId);
+ }
+ if (mRecentTasks.isPresent() && sideChild != null) {
+ mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId);
+ }
mSplitUnsupportedToast.show();
return true;
}
@@ -3158,7 +3237,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
- public void onNoLongerSupportMultiWindow() {
+ public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
if (mMainStage.isActive()) {
final boolean isMainStage = mMainStageListener == this;
if (!ENABLE_SHELL_TRANSITIONS) {
@@ -3173,6 +3252,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
prepareExitSplitScreen(stageType, wct);
mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+ Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
+ "app package " + taskInfo.baseActivity.getPackageName()
+ + " does not support splitscreen, or is a controlled activity type"));
mSplitUnsupportedToast.show();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index a01eddbc9b9f..af7bf360f036 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -51,8 +51,11 @@ import com.android.wm.shell.common.SurfaceUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.io.PrintWriter;
+import java.util.Optional;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -79,7 +82,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
void onRootTaskVanished();
- void onNoLongerSupportMultiWindow();
+ void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo);
}
private final Context mContext;
@@ -87,6 +90,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
private final SurfaceSession mSurfaceSession;
private final SyncTransactionQueue mSyncQueue;
private final IconProvider mIconProvider;
+ private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
protected ActivityManager.RunningTaskInfo mRootTaskInfo;
protected SurfaceControl mRootLeash;
@@ -98,12 +102,14 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession, IconProvider iconProvider) {
+ SurfaceSession surfaceSession, IconProvider iconProvider,
+ Optional<WindowDecorViewModel> windowDecorViewModel) {
mContext = context;
mCallbacks = callbacks;
mSyncQueue = syncQueue;
mSurfaceSession = surfaceSession;
mIconProvider = iconProvider;
+ mWindowDecorViewModel = windowDecorViewModel;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
}
@@ -202,6 +208,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
@CallSuper
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
if (mRootTaskInfo.taskId == taskInfo.taskId) {
// Inflates split decor view only when the root task is visible.
if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) {
@@ -220,7 +227,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
taskInfo.getWindowingMode())) {
// Leave split screen if the task no longer supports multi window or have
// uncontrolled task.
- mCallbacks.onNoLongerSupportMultiWindow();
+ mCallbacks.onNoLongerSupportMultiWindow(taskInfo);
return;
}
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
@@ -341,6 +348,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
}
+ void doForAllChildTasks(Consumer<Integer> consumer) {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+ consumer.accept(taskInfo.taskId);
+ }
+ }
+
/** Collects all the current child tasks and prepares transaction to evict them to display. */
void evictAllChildren(WindowContainerTransaction wct) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index 27d520d81c41..c10142588bde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -27,6 +27,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -58,6 +59,7 @@ public class TvSplitScreenController extends SplitScreenController {
private final TransactionPool mTransactionPool;
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
+ private final LaunchAdjacentController mLaunchAdjacentController;
private final Handler mMainHandler;
private final SystemWindows mSystemWindows;
@@ -77,13 +79,15 @@ public class TvSplitScreenController extends SplitScreenController {
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer,
syncQueue, rootTDAOrganizer, displayController, displayImeController,
displayInsetsController, dragAndDropController, transitions, transactionPool,
- iconProvider, recentTasks, mainExecutor);
+ iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
+ Optional.empty(), mainExecutor);
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
@@ -96,6 +100,7 @@ public class TvSplitScreenController extends SplitScreenController {
mTransactionPool = transactionPool;
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
+ mLaunchAdjacentController = launchAdjacentController;
mMainHandler = mainHandler;
mSystemWindows = systemWindows;
@@ -111,7 +116,7 @@ public class TvSplitScreenController extends SplitScreenController {
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool,
mIconProvider, mMainExecutor, mMainHandler,
- mRecentTasksOptional, mSystemWindows);
+ mRecentTasksOptional, mLaunchAdjacentController, mSystemWindows);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index 4d563fbb7f04..79476919221e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -24,6 +24,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -51,10 +52,11 @@ public class TvStageCoordinator extends StageCoordinator
IconProvider iconProvider, ShellExecutor mainExecutor,
Handler mainHandler,
Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
SystemWindows systemWindows) {
super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
- mainExecutor, recentTasks);
+ mainExecutor, recentTasks, launchAdjacentController, Optional.empty());
mTvSplitMenuController = new TvSplitMenuController(context, this,
systemWindows, mainHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
index 20c4d5ae5f58..e7e1e0a98550 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
@@ -35,13 +35,14 @@ class SnapshotWindowCreator {
void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
final int taskId = startingWindowInfo.taskInfo.taskId;
// Remove any existing starting window for this task before adding.
- mStartingWindowRecordManager.removeWindow(taskId, true);
+ mStartingWindowRecordManager.removeWindow(taskId);
final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo,
startingWindowInfo.appToken, snapshot, mMainExecutor,
- () -> mStartingWindowRecordManager.removeWindow(taskId, true));
+ () -> mStartingWindowRecordManager.removeWindow(taskId));
if (surface != null) {
final SnapshotWindowRecord tView = new SnapshotWindowRecord(surface,
- startingWindowInfo.taskInfo.topActivityType, mMainExecutor);
+ startingWindowInfo.taskInfo.topActivityType, mMainExecutor,
+ taskId, mStartingWindowRecordManager);
mStartingWindowRecordManager.addRecord(taskId, tView);
}
}
@@ -50,8 +51,9 @@ class SnapshotWindowCreator {
private final TaskSnapshotWindow mTaskSnapshotWindow;
SnapshotWindowRecord(TaskSnapshotWindow taskSnapshotWindow,
- int activityType, ShellExecutor removeExecutor) {
- super(activityType, removeExecutor);
+ int activityType, ShellExecutor removeExecutor, int id,
+ StartingSurfaceDrawer.StartingWindowRecordManager recordManager) {
+ super(activityType, removeExecutor, id, recordManager);
mTaskSnapshotWindow = taskSnapshotWindow;
mBGColor = mTaskSnapshotWindow.getBackgroundColor();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index dc91a11dc64f..29be34347b22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -385,8 +385,8 @@ public class SplashscreenContentDrawer {
private static int estimateWindowBGColor(Drawable themeBGDrawable) {
final DrawableColorTester themeBGTester = new DrawableColorTester(
themeBGDrawable, DrawableColorTester.TRANSLUCENT_FILTER /* filterType */);
- if (themeBGTester.passFilterRatio() != 1) {
- // the window background is translucent, unable to draw
+ if (themeBGTester.passFilterRatio() < 0.5f) {
+ // more than half pixels of the window background is translucent, unable to draw
Slog.w(TAG, "Window background is translucent, fill background with black color");
return getSystemBGColor();
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
index 4cfbbd971fe3..31fc98b713ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -358,7 +358,7 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator {
}
}
if (shouldSaveView) {
- mStartingWindowRecordManager.removeWindow(taskId, true);
+ mStartingWindowRecordManager.removeWindow(taskId);
saveSplashScreenRecord(appToken, taskId, view, suggestType);
}
return shouldSaveView;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 7cbf263f7cb1..e2be1533118a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -188,7 +188,7 @@ public class StartingSurfaceDrawer {
final SnapshotRecord record = sRecord instanceof SnapshotRecord
? (SnapshotRecord) sRecord : null;
if (record != null && record.hasImeSurface()) {
- records.removeWindow(taskId, true);
+ records.removeWindow(taskId);
}
}
@@ -256,10 +256,15 @@ public class StartingSurfaceDrawer {
@WindowConfiguration.ActivityType protected final int mActivityType;
protected final ShellExecutor mRemoveExecutor;
+ private final int mTaskId;
+ private final StartingWindowRecordManager mRecordManager;
- SnapshotRecord(int activityType, ShellExecutor removeExecutor) {
+ SnapshotRecord(int activityType, ShellExecutor removeExecutor, int taskId,
+ StartingWindowRecordManager recordManager) {
mActivityType = activityType;
mRemoveExecutor = removeExecutor;
+ mTaskId = taskId;
+ mRecordManager = recordManager;
}
@Override
@@ -301,6 +306,7 @@ public class StartingSurfaceDrawer {
@CallSuper
protected void removeImmediately() {
mRemoveExecutor.removeCallbacks(mScheduledRunnable);
+ mRecordManager.onRecordRemoved(mTaskId);
}
}
@@ -316,7 +322,7 @@ public class StartingSurfaceDrawer {
taskIds[i] = mStartingWindowRecords.keyAt(i);
}
for (int i = taskSize - 1; i >= 0; --i) {
- removeWindow(taskIds[i], true);
+ removeWindow(taskIds[i]);
}
}
@@ -335,9 +341,13 @@ public class StartingSurfaceDrawer {
}
}
- void removeWindow(int taskId, boolean immediately) {
+ void removeWindow(int taskId) {
mTmpRemovalInfo.taskId = taskId;
- removeWindow(mTmpRemovalInfo, immediately);
+ removeWindow(mTmpRemovalInfo, true/* immediately */);
+ }
+
+ void onRecordRemoved(int taskId) {
+ mStartingWindowRecords.remove(taskId);
}
StartingWindowRecord getRecord(int taskId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
index 144547885501..fed2f34b5e0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
@@ -92,7 +92,8 @@ class WindowlessSnapshotWindowCreator {
final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, wlw.mChildSurface,
taskDescription.getBackgroundColor(), snapshot.hasImeSurface(),
- runningTaskInfo.topActivityType, removeExecutor);
+ runningTaskInfo.topActivityType, removeExecutor,
+ taskId, mStartingWindowRecordManager);
mStartingWindowRecordManager.addRecord(taskId, record);
info.notifyAddComplete(wlw.mChildSurface);
}
@@ -104,8 +105,9 @@ class WindowlessSnapshotWindowCreator {
SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl childSurface,
int bgColor, boolean hasImeSurface, int activityType,
- ShellExecutor removeExecutor) {
- super(activityType, removeExecutor);
+ ShellExecutor removeExecutor, int id,
+ StartingSurfaceDrawer.StartingWindowRecordManager recordManager) {
+ super(activityType, removeExecutor, id, recordManager);
mViewHost = viewHost;
mChildSurface = childSurface;
mBGColor = bgColor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
index 5f54f58557d1..56c0d0e67cab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -38,8 +38,6 @@ public class ShellSharedConstants {
public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
// See IBackAnimation.aidl
public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
- // See IFloatingTasks.aidl
- public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
// See IDesktopMode.aidl
public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
// See IDragAndDrop.aidl
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 4faa92979733..0d77a2e4610c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -26,6 +26,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.view.SurfaceControl;
@@ -69,8 +70,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private final Rect mTmpRect = new Rect();
private final Rect mTmpRootRect = new Rect();
private final int[] mTmpLocation = new int[2];
+ private final Rect mBoundsOnScreen = new Rect();
private final TaskViewTaskController mTaskViewTaskController;
private Region mObscuredTouchRegion;
+ private Insets mCaptionInsets;
public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
super(context, null, 0, 0, true /* disableBackgroundLayer */);
@@ -169,6 +172,25 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
}
/**
+ * Sets a region of the task to inset to allow for a caption bar. Currently only top insets
+ * are supported.
+ * <p>
+ * This region will be factored in as an area of taskview that is not touchable activity
+ * content (i.e. you don't need to additionally set {@link #setObscuredTouchRect(Rect)} for
+ * the caption area).
+ *
+ * @param captionInsets the insets to apply to task view.
+ */
+ public void setCaptionInsets(Insets captionInsets) {
+ mCaptionInsets = captionInsets;
+ if (captionInsets == null) {
+ // If captions are null we can set them now; otherwise they'll get set in
+ // onComputeInternalInsets.
+ mTaskViewTaskController.setCaptionInsets(null);
+ }
+ }
+
+ /**
* Call when view position or size has changed. Do not call when animating.
*/
public void onLocationChanged() {
@@ -230,6 +252,15 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
getLocationInWindow(mTmpLocation);
mTmpRect.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight());
+ if (mCaptionInsets != null) {
+ mTmpRect.inset(mCaptionInsets);
+ getBoundsOnScreen(mBoundsOnScreen);
+ mTaskViewTaskController.setCaptionInsets(new Rect(
+ mBoundsOnScreen.left,
+ mBoundsOnScreen.top,
+ mBoundsOnScreen.right + getWidth(),
+ mBoundsOnScreen.top + mCaptionInsets.top));
+ }
inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE);
if (mObscuredTouchRegion != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 163cf501734c..93d763608b5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -33,6 +33,7 @@ import android.os.Binder;
import android.util.CloseGuard;
import android.util.Slog;
import android.view.SurfaceControl;
+import android.view.WindowInsets;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -53,12 +54,13 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
private static final String TAG = TaskViewTaskController.class.getSimpleName();
private final CloseGuard mGuard = new CloseGuard();
-
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ /** Used to inset the activity content to allow space for a caption bar. */
+ private final Binder mCaptionInsetsOwner = new Binder();
private final ShellTaskOrganizer mTaskOrganizer;
private final Executor mShellExecutor;
private final SyncTransactionQueue mSyncQueue;
private final TaskViewTransitions mTaskViewTransitions;
- private TaskViewBase mTaskViewBase;
private final Context mContext;
/**
@@ -69,18 +71,20 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
* in this situation to allow us to notify listeners correctly if the task failed to open.
*/
private ActivityManager.RunningTaskInfo mPendingInfo;
- /* Indicates that the task we attempted to launch in the task view failed to launch. */
- private boolean mTaskNotFound;
+ private TaskViewBase mTaskViewBase;
protected ActivityManager.RunningTaskInfo mTaskInfo;
private WindowContainerToken mTaskToken;
private SurfaceControl mTaskLeash;
- private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ /* Indicates that the task we attempted to launch in the task view failed to launch. */
+ private boolean mTaskNotFound;
private boolean mSurfaceCreated;
private SurfaceControl mSurfaceControl;
private boolean mIsInitialized;
private boolean mNotifiedForInitialized;
+ private boolean mHideTaskWithSurface = true;
private TaskView.Listener mListener;
private Executor mListenerExecutor;
+ private Rect mCaptionInsets;
public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
@@ -89,12 +93,27 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
mShellExecutor = organizer.getExecutor();
mSyncQueue = syncQueue;
mTaskViewTransitions = taskViewTransitions;
- if (mTaskViewTransitions != null) {
- mTaskViewTransitions.addTaskView(this);
- }
+ mShellExecutor.execute(() -> {
+ if (mTaskViewTransitions != null) {
+ mTaskViewTransitions.addTaskView(this);
+ }
+ });
mGuard.open("release");
}
+ /**
+ * Specifies if the task should be hidden when the surface is destroyed.
+ * <p>This is {@code true} by default.
+ *
+ * @param hideTaskWithSurface {@code false} if task needs to remain visible even when the
+ * surface is destroyed, {@code true} otherwise.
+ */
+ public void setHideTaskWithSurface(boolean hideTaskWithSurface) {
+ // TODO(b/299535374): Remove mHideTaskWithSurface once the taskviews with launch root tasks
+ // are moved to a window in SystemUI in auto.
+ mHideTaskWithSurface = hideTaskWithSurface;
+ }
+
SurfaceControl getSurfaceControl() {
return mSurfaceControl;
}
@@ -220,10 +239,10 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
}
private void performRelease() {
- if (mTaskViewTransitions != null) {
- mTaskViewTransitions.removeTaskView(this);
- }
mShellExecutor.execute(() -> {
+ if (mTaskViewTransitions != null) {
+ mTaskViewTransitions.removeTaskView(this);
+ }
mTaskOrganizer.removeListener(this);
resetTaskInfo();
});
@@ -250,9 +269,17 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
mTaskNotFound = false;
}
+ /** This method shouldn't be called when shell transitions are enabled. */
private void updateTaskVisibility() {
+ boolean visible = mSurfaceCreated;
+ if (!visible && !mHideTaskWithSurface) {
+ return;
+ }
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
+ wct.setHidden(mTaskToken, !visible /* hidden */);
+ if (!visible) {
+ wct.reorder(mTaskToken, false /* onTop */);
+ }
mSyncQueue.queue(wct);
if (mListener == null) {
return;
@@ -312,18 +339,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
// we know about -- so leave clean-up here even if shell transitions are enabled.
if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
- if (mListener != null) {
- final int taskId = taskInfo.taskId;
- mListenerExecutor.execute(() -> {
- mListener.onTaskRemovalStarted(taskId);
- });
- }
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
+ final SurfaceControl taskLeash = mTaskLeash;
+ handleAndNotifyTaskRemoval(mTaskInfo);
// Unparent the task when this surface is destroyed
- mTransaction.reparent(mTaskLeash, null).apply();
+ mTransaction.reparent(taskLeash, null).apply();
resetTaskInfo();
- mTaskViewBase.onTaskVanished(taskInfo);
}
@Override
@@ -411,9 +432,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
if (mTaskToken == null) {
return;
}
- // Sync Transactions can't operate simultaneously with shell transition collection.
+
if (isUsingShellTransitions()) {
- mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
+ mShellExecutor.execute(() -> {
+ // Sync Transactions can't operate simultaneously with shell transition collection.
+ mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
+ });
return;
}
@@ -431,9 +455,37 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
return;
}
+ mShellExecutor.execute(() -> {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.removeTask(mTaskToken);
+ mTaskViewTransitions.closeTaskView(wct, this);
+ });
+ }
+
+ /**
+ * Sets a region of the task to inset to allow for a caption bar.
+ *
+ * @param captionInsets the rect for the insets in screen coordinates.
+ */
+ void setCaptionInsets(Rect captionInsets) {
+ if (mCaptionInsets != null && mCaptionInsets.equals(captionInsets)) {
+ return;
+ }
+ mCaptionInsets = captionInsets;
+ applyCaptionInsetsIfNeeded();
+ }
+
+ void applyCaptionInsetsIfNeeded() {
+ if (mTaskToken == null) return;
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.removeTask(mTaskToken);
- mTaskViewTransitions.closeTaskView(wct, this);
+ if (mCaptionInsets != null) {
+ wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+ WindowInsets.Type.captionBar(), mCaptionInsets);
+ } else {
+ wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+ WindowInsets.Type.captionBar());
+ }
+ mTaskOrganizer.applyTransaction(wct);
}
/** Should be called when the client surface is destroyed. */
@@ -467,6 +519,20 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
}
}
+ /** Notifies listeners of a task being removed and stops intercepting back presses on it. */
+ private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) {
+ if (taskInfo != null) {
+ if (mListener != null) {
+ final int taskId = taskInfo.taskId;
+ mListenerExecutor.execute(() -> {
+ mListener.onTaskRemovalStarted(taskId);
+ });
+ }
+ mTaskViewBase.onTaskVanished(taskInfo);
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, false);
+ }
+ }
+
/** Returns the task info for the task in the TaskView. */
@Nullable
public ActivityManager.RunningTaskInfo getTaskInfo() {
@@ -492,18 +558,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
*/
void cleanUpPendingTask() {
if (mPendingInfo != null) {
- if (mListener != null) {
- final int taskId = mPendingInfo.taskId;
- mListenerExecutor.execute(() -> {
- mListener.onTaskRemovalStarted(taskId);
- });
- }
- mTaskViewBase.onTaskVanished(mPendingInfo);
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mPendingInfo.token, false);
+ final ActivityManager.RunningTaskInfo pendingInfo = mPendingInfo;
+ handleAndNotifyTaskRemoval(pendingInfo);
// Make sure the task is removed
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.removeTask(mPendingInfo.token);
+ wct.removeTask(pendingInfo.token);
mTaskViewTransitions.closeTaskView(wct, this);
}
resetTaskInfo();
@@ -528,16 +588,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
* is used instead.
*/
void prepareCloseAnimation() {
- if (mTaskToken != null) {
- if (mListener != null) {
- final int taskId = mTaskInfo.taskId;
- mListenerExecutor.execute(() -> {
- mListener.onTaskRemovalStarted(taskId);
- });
- }
- mTaskViewBase.onTaskVanished(mTaskInfo);
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
- }
+ handleAndNotifyTaskRemoval(mTaskInfo);
resetTaskInfo();
}
@@ -564,6 +615,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
mTaskViewTransitions.updateBoundsState(this, boundsOnScreen);
mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
wct.setBounds(mTaskToken, boundsOnScreen);
+ applyCaptionInsetsIfNeeded();
} else {
// The surface has already been destroyed before the task has appeared,
// so go ahead and hide the task entirely
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 5baf2e320227..cefbb17d783d 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
@@ -202,15 +202,10 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
if (taskView == null) return null;
// Opening types should all be initiated by shell
if (!TransitionUtil.isClosingType(request.getType())) return null;
- PendingTransition pending = findPendingCloseTransition(taskView);
- if (pending == null) {
- pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */);
- }
- if (pending.mClaimed != null) {
- throw new IllegalStateException("Task is closing in 2 collecting transitions?"
- + " This state doesn't make sense");
- }
+ PendingTransition pending = new PendingTransition(request.getType(), null,
+ taskView, null /* cookie */);
pending.mClaimed = transition;
+ mPending.add(pending);
return new WindowContainerTransaction();
}
@@ -409,7 +404,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
}
// No animation, just show it immediately.
startTransaction.apply();
- finishCallback.onTransitionFinished(wct, null /* wctCB */);
+ finishCallback.onTransitionFinished(wct);
startNextTransition();
return true;
}
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 d0a361a8ecd2..32cc6f8cebc0 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
@@ -40,13 +40,14 @@ import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -70,6 +71,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
private RecentsTransitionHandler mRecentsHandler;
private StageCoordinator mSplitHandler;
private final KeyguardTransitionHandler mKeyguardHandler;
+ private DesktopModeController mDesktopModeController;
+ private DesktopTasksController mDesktopTasksController;
private UnfoldTransitionHandler mUnfoldHandler;
private static class MixedTransition {
@@ -87,8 +90,11 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
/** Keyguard exit/occlude/unocclude transition. */
static final int TYPE_KEYGUARD = 5;
+ /** Recents Transition while in desktop mode. */
+ static final int TYPE_RECENTS_DURING_DESKTOP = 6;
+
/** Fuld/Unfold transition. */
- static final int TYPE_UNFOLD = 6;
+ static final int TYPE_UNFOLD = 7;
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -107,6 +113,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
WindowContainerTransaction mFinishWCT = null;
/**
+ * Whether the transition has request for remote transition while mLeftoversHandler
+ * isn't remote transition handler.
+ * If true and the mLeftoversHandler can handle the transition, need to notify remote
+ * transition handler to consume the transition.
+ */
+ boolean mHasRequestToRemote;
+
+ /**
* Mixed transitions are made up of multiple "parts". This keeps track of how many
* parts are currently animating.
*/
@@ -117,14 +131,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
mTransition = transition;
}
- void joinFinishArgs(WindowContainerTransaction wct,
- WindowContainerTransactionCallback wctCB) {
- if (wctCB != null) {
- // Technically can probably support 1, but don't want to encourage CB usage since
- // it creates instabliity, so just throw.
- throw new IllegalArgumentException("Can't mix transitions that require finish"
- + " sync callback");
- }
+ void joinFinishArgs(WindowContainerTransaction wct) {
if (wct != null) {
if (mFinishWCT == null) {
mFinishWCT = wct;
@@ -139,17 +146,20 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player,
Optional<SplitScreenController> splitScreenControllerOptional,
- Optional<PipTouchHandler> pipTouchHandlerOptional,
+ @Nullable PipTransitionController pipTransitionController,
Optional<RecentsTransitionHandler> recentsHandlerOptional,
KeyguardTransitionHandler keyguardHandler,
+ Optional<DesktopModeController> desktopModeControllerOptional,
+ Optional<DesktopTasksController> desktopTasksControllerOptional,
Optional<UnfoldTransitionHandler> unfoldHandler) {
mPlayer = player;
mKeyguardHandler = keyguardHandler;
- if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent()
+ if (Transitions.ENABLE_SHELL_TRANSITIONS
+ && pipTransitionController != null
&& splitScreenControllerOptional.isPresent()) {
// Add after dependencies because it is higher priority
shellInit.addInitCallback(() -> {
- mPipHandler = pipTouchHandlerOptional.get().getTransitionHandler();
+ mPipHandler = pipTransitionController;
mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler();
mPlayer.addHandler(this);
if (mSplitHandler != null) {
@@ -159,6 +169,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (mRecentsHandler != null) {
mRecentsHandler.addMixer(this);
}
+ mDesktopModeController = desktopModeControllerOptional.orElse(null);
+ mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
mUnfoldHandler = unfoldHandler.orElse(null);
}, this);
}
@@ -200,6 +212,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition);
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
+ if (mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+ mixed.mHasRequestToRemote = true;
+ mPlayer.getRemoteTransitionHandler().handleRequest(transition, request);
+ }
return handler.second;
} else if (mSplitHandler.isSplitScreenVisible()
&& isOpeningType(request.getType())
@@ -239,7 +255,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
@Override
public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
- if (mRecentsHandler != null && mSplitHandler.isSplitScreenVisible()) {
+ if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
+ || DesktopModeStatus.isActive(mPlayer.getContext()))) {
return this;
}
return null;
@@ -254,6 +271,13 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
mixed.mLeftoversHandler = mRecentsHandler;
mActiveTransitions.add(mixed);
+ } else if (DesktopModeStatus.isActive(mPlayer.getContext())) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ + "desktop mode is active, so treat it as Mixed.");
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition);
+ mixed.mLeftoversHandler = mRecentsHandler;
+ mActiveTransitions.add(mixed);
} else {
throw new IllegalStateException("Accepted a recents transition but don't know how to"
+ " handle it");
@@ -303,12 +327,22 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
// the time of handleRequest, but we need more information than is available at that time.
if (KeyguardTransitionHandler.handles(info)) {
if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Converting mixed transition into a keyguard transition");
- onTransitionConsumed(transition, false, null);
+ final MixedTransition keyguardMixed =
+ new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
+ mActiveTransitions.add(keyguardMixed);
+ final boolean hasAnimateKeyguard = animateKeyguard(keyguardMixed, info,
+ startTransaction, finishTransaction, finishCallback);
+ if (hasAnimateKeyguard) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Converting mixed transition into a keyguard transition");
+ // Consume the original mixed transition
+ onTransitionConsumed(transition, false, null);
+ return true;
+ } else {
+ // Keyguard handler cannot handle it, process through original mixed
+ mActiveTransitions.remove(keyguardMixed);
+ }
}
- mixed = new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
- mActiveTransitions.add(mixed);
}
if (mixed == null) return false;
@@ -319,8 +353,17 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
} else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
return false;
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction,
- finishTransaction, finishCallback);
+ final boolean handledToPip = animateOpenIntentWithRemoteAndPip(mixed, info,
+ startTransaction, finishTransaction, finishCallback);
+ // Consume the transition on remote handler if the leftover handler already handle this
+ // transition. And if it cannot, the transition will be handled by remote handler, so
+ // don't consume here.
+ // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
+ if (handledToPip && mixed.mHasRequestToRemote
+ && mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
+ }
+ return handledToPip;
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -340,6 +383,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
} else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
return animateKeyguard(mixed, info, startTransaction, finishTransaction,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+ return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
+ finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback);
} else {
@@ -366,12 +412,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
info.getChanges().remove(i);
}
}
- Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
--mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct, wctCB);
+ mixed.joinFinishArgs(wct);
if (mixed.mInFlightSubAnimations > 0) return;
mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT);
};
if (pipChange == null) {
if (mixed.mLeftoversHandler != null) {
@@ -438,15 +484,15 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
return false;
}
final boolean isGoingHome = homeIsOpening;
- Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
--mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct, wctCB);
+ mixed.joinFinishArgs(wct);
if (mixed.mInFlightSubAnimations > 0) return;
mActiveTransitions.remove(mixed);
if (isGoingHome) {
mSplitHandler.onTransitionAnimationComplete();
}
- finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT);
};
if (isGoingHome || mSplitHandler.getSplitItemPosition(pipChange.getLastParent())
!= SPLIT_POSITION_UNDEFINED) {
@@ -563,12 +609,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
// We need to split the transition into 2 parts: the split part and the display part.
mixed.mInFlightSubAnimations = 2;
- Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
--mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct, wctCB);
+ mixed.joinFinishArgs(wct);
if (mixed.mInFlightSubAnimations > 0) return;
mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT);
};
// Dispatch the display change. This will most-likely be taken by the default handler.
@@ -591,7 +637,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
// Split-screen is only interested in the recents transition finishing (and merging), so
// just wrap finish and start recents animation directly.
- Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
mixed.mInFlightSubAnimations = 0;
mActiveTransitions.remove(mixed);
// If pair-to-pair switching, the post-recents clean-up isn't needed.
@@ -603,7 +649,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
}
mSplitHandler.onTransitionAnimationComplete();
- finishCallback.onTransitionFinished(wct, wctCB);
+ finishCallback.onTransitionFinished(wct);
};
mixed.mInFlightSubAnimations = 1;
mSplitHandler.onRecentsInSplitAnimationStart(info);
@@ -621,11 +667,11 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
mixed.mInFlightSubAnimations--;
if (mixed.mInFlightSubAnimations == 0) {
mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(wct, wctCB);
+ finishCallback.onTransitionFinished(wct);
}
};
mixed.mInFlightSubAnimations++;
@@ -641,22 +687,49 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
return true;
}
+ private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ boolean consumed = mRecentsHandler.startAnimation(
+ mixed.mTransition, info, startTransaction, finishTransaction, finishCallback);
+ if (!consumed) {
+ return false;
+ }
+ //Sync desktop mode state (proto 1)
+ if (mDesktopModeController != null) {
+ mDesktopModeController.syncSurfaceState(info, finishTransaction);
+ return true;
+ }
+ //Sync desktop mode state (proto 2)
+ if (mDesktopTasksController != null) {
+ mDesktopTasksController.syncSurfaceState(info, finishTransaction);
+ return true;
+ }
+
+ return false;
+ }
+
private boolean animateUnfold(@NonNull final MixedTransition mixed,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
mixed.mInFlightSubAnimations--;
if (mixed.mInFlightSubAnimations > 0) return;
mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(wct, wctCB);
+ finishCallback.onTransitionFinished(wct);
};
mixed.mInFlightSubAnimations = 1;
// Sync pip state.
if (mPipHandler != null) {
mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
}
+ if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
+ mSplitHandler.updateSurfaces(startTransaction);
+ }
return mUnfoldHandler.startAnimation(
mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
}
@@ -727,6 +800,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
} else {
@@ -754,8 +830,13 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+ mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
}
+ if (mixed.mHasRequestToRemote) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+ }
}
}
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 e52fd00e7df7..de03f5826925 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
@@ -99,7 +99,6 @@ import android.window.WindowContainerTransaction;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
@@ -182,7 +181,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
/* broadcastPermission = */ null,
mMainHandler);
- AttributeCache.init(mContext);
+ TransitionAnimation.initAttributeCache(mContext, mMainHandler);
}
private void updateEnterpriseThumbnailDrawable() {
@@ -300,7 +299,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// immediately finishes since there is no animation for screen-wake.
if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) {
startTransaction.apply();
- finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ finishCallback.onTransitionFinished(null /* wct */);
return true;
}
@@ -309,7 +308,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|| (info.getFlags() & WindowManager.TRANSIT_FLAG_INVISIBLE) != 0) {
startTransaction.apply();
finishTransaction.apply();
- finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ finishCallback.onTransitionFinished(null /* wct */);
return true;
}
@@ -323,7 +322,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final Runnable onAnimFinish = () -> {
if (!animations.isEmpty()) return;
mAnimations.remove(transition);
- finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ finishCallback.onTransitionFinished(null /* wct */);
};
final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
@@ -407,7 +406,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
}
// Rotation change of independent non display window container.
- if (change.getParent() == null
+ if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY)
&& change.getStartRotation() != change.getEndRotation()) {
startRotationAnimation(startTransaction, change, info,
ROTATION_ANIMATION_ROTATE, animations, onAnimFinish);
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 4e3d220f1ea2..fab2dd2bf3e1 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
@@ -68,7 +68,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
final IBinder.DeathRecipient remoteDied = () -> {
Log.e(Transitions.TAG, "Remote transition died, finishing");
mMainExecutor.execute(
- () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+ () -> finishCallback.onTransitionFinished(null /* wct */));
};
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@Override
@@ -81,7 +81,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
finishTransaction.merge(sct);
}
mMainExecutor.execute(() -> {
- finishCallback.onTransitionFinished(wct, null /* wctCB */);
+ finishCallback.onTransitionFinished(wct);
});
}
};
@@ -104,7 +104,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
if (mRemote.asBinder() != null) {
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
- finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ finishCallback.onTransitionFinished(null /* wct */);
}
return true;
}
@@ -122,8 +122,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
// remote applied the transaction, but applying twice will break surfaceflinger
// so just assume the worst-case and clear the local transaction.
t.clear();
- mMainExecutor.execute(
- () -> finishCallback.onTransitionFinished(wct, null /* wctCB */));
+ mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct));
}
};
try {
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 a242c72db8b3..bbf67a6155d7 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
@@ -133,7 +133,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
}
mMainExecutor.execute(() -> {
mRequestedRemotes.remove(transition);
- finishCallback.onTransitionFinished(wct, null /* wctCB */);
+ finishCallback.onTransitionFinished(wct);
});
}
};
@@ -153,8 +153,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
Log.e(Transitions.TAG, "Error running remote transition.", e);
unhandleDeath(remote.asBinder(), finishCallback);
mRequestedRemotes.remove(transition);
- mMainExecutor.execute(
- () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+ mMainExecutor.execute(() -> finishCallback.onTransitionFinished(null /* wct */));
}
return true;
}
@@ -186,9 +185,12 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget);
- final IRemoteTransition remote = remoteTransition.getRemoteTransition();
+ if (remoteTransition == null) return;
+
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Merge into remote: %s",
remoteTransition);
+
+ final IRemoteTransition remote = remoteTransition.getRemoteTransition();
if (remote == null) return;
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@@ -207,7 +209,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
+ "that the mergeTarget's RemoteTransition impl erroneously "
+ "accepted/ran the merge request after finishing the mergeTarget");
}
- finishCallback.onTransitionFinished(wct, null /* wctCB */);
+ finishCallback.onTransitionFinished(wct);
});
}
};
@@ -313,8 +315,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
}
}
for (int i = mPendingFinishCallbacks.size() - 1; i >= 0; --i) {
- mPendingFinishCallbacks.get(i).onTransitionFinished(
- null /* wct */, null /* wctCB */);
+ mPendingFinishCallbacks.get(i).onTransitionFinished(null /* wct */);
}
mPendingFinishCallbacks.clear();
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
index d2795959494a..87c438a5b37d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -43,7 +43,7 @@ class SleepHandler implements Transitions.TransitionHandler {
@NonNull Transitions.TransitionFinishCallback finishCallback) {
mSleepTransitions.remove(transition);
startTransaction.apply();
- finishCallback.onTransitionFinished(null, null);
+ finishCallback.onTransitionFinished(null);
return true;
}
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 2327d86ab618..c74b3f30e52d 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
@@ -21,12 +21,14 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
@@ -61,7 +63,6 @@ import android.window.TransitionInfo;
import android.window.TransitionMetrics;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
import android.window.WindowOrganizer;
import androidx.annotation.BinderThread;
@@ -148,19 +149,28 @@ public class Transitions implements RemoteCallable<Transitions>,
/** Transition type for maximize to freeform transition. */
public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
- /** Transition type to freeform in desktop mode. */
- public static final int TRANSIT_ENTER_FREEFORM = WindowManager.TRANSIT_FIRST_CUSTOM + 10;
+ /** Transition type for starting the move to desktop mode. */
+ public static final int TRANSIT_START_DRAG_TO_DESKTOP_MODE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 10;
- /** Transition type to freeform in desktop mode. */
- public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11;
+ /** Transition type for finalizing the move to desktop mode. */
+ public static final int TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 11;
/** Transition type to fullscreen from desktop mode. */
public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
/** Transition type to animate back to fullscreen when drag to freeform is cancelled. */
- public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
+ public static final int TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE =
WindowManager.TRANSIT_FIRST_CUSTOM + 13;
+ /** Transition type to animate the toggle resize between the max and default desktop sizes. */
+ public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 14;
+
+ /** Transition to animate task to desktop. */
+ public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
+
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
@@ -724,11 +734,15 @@ public class Transitions implements RemoteCallable<Transitions>,
final int changeSize = info.getChanges().size();
boolean taskChange = false;
boolean transferStartingWindow = false;
+ int noAnimationBehindStartingWindow = 0;
boolean allOccluded = changeSize > 0;
for (int i = changeSize - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
taskChange |= change.getTaskInfo() != null;
transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
+ if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) {
+ noAnimationBehindStartingWindow++;
+ }
if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
allOccluded = false;
}
@@ -736,9 +750,11 @@ public class Transitions implements RemoteCallable<Transitions>,
// There does not need animation when:
// A. Transfer starting window. Apply transfer starting window directly if there is no other
// task change. Since this is an activity->activity situation, we can detect it by selecting
- // transitions with only 2 changes where neither are tasks and one is a starting-window
- // recipient.
- if (!taskChange && transferStartingWindow && changeSize == 2
+ // transitions with only 2 changes where
+ // 1. neither are tasks, and
+ // 2. one is a starting-window recipient, or all change is behind starting window.
+ if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize)
+ && changeSize == 2
// B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
// changes are underneath another change.
|| ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
@@ -813,7 +829,7 @@ public class Transitions implements RemoteCallable<Transitions>,
ready.mStartT.apply();
}
// finish now since there's nothing to animate. Calls back into processReadyQueue
- onFinish(ready, null, null);
+ onFinish(ready, null);
return;
}
playTransition(ready);
@@ -833,7 +849,7 @@ public class Transitions implements RemoteCallable<Transitions>,
+ " in case they can be merged", ready, playing);
mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
- playing.mToken, (wct, cb) -> onMerged(playing, ready));
+ playing.mToken, (wct) -> onMerged(playing, ready));
}
private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
@@ -883,7 +899,7 @@ public class Transitions implements RemoteCallable<Transitions>,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
active.mHandler);
boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo,
- active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb));
+ active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct));
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
@@ -892,7 +908,7 @@ public class Transitions implements RemoteCallable<Transitions>,
}
// Otherwise give every other handler a chance
active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
- active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
+ active.mFinishT, (wct) -> onFinish(active, wct), active.mHandler);
}
/**
@@ -969,8 +985,7 @@ public class Transitions implements RemoteCallable<Transitions>,
}
private void onFinish(ActiveTransition active,
- @Nullable WindowContainerTransaction wct,
- @Nullable WindowContainerTransactionCallback wctCB) {
+ @Nullable WindowContainerTransaction wct) {
final Track track = mTracks.get(active.getTrack());
if (track.mActiveTransition != active) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
@@ -1019,11 +1034,11 @@ public class Transitions implements RemoteCallable<Transitions>,
// Now perform all the finish callbacks (starting with the playing one and then all the
// transitions merged into it).
releaseSurfaces(active.mInfo);
- mOrganizer.finishTransition(active.mToken, wct, wctCB);
+ mOrganizer.finishTransition(active.mToken, wct);
if (active.mMerged != null) {
for (int iM = 0; iM < active.mMerged.size(); ++iM) {
ActiveTransition merged = active.mMerged.get(iM);
- mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+ mOrganizer.finishTransition(merged.mToken, null /* wct */);
releaseSurfaces(merged.mInfo);
}
active.mMerged.clear();
@@ -1090,7 +1105,9 @@ public class Transitions implements RemoteCallable<Transitions>,
}
}
}
- if (request.getType() == TRANSIT_KEYGUARD_OCCLUDE && request.getTriggerTask() != null
+ final boolean isOccludingKeyguard = request.getType() == TRANSIT_KEYGUARD_OCCLUDE
+ || ((request.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0);
+ if (isOccludingKeyguard && request.getTriggerTask() != null
&& request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
// This freeform task is on top of keyguard, so its windowing mode should be changed to
// fullscreen.
@@ -1162,7 +1179,7 @@ public class Transitions implements RemoteCallable<Transitions>,
forceFinish.mHandler.onTransitionConsumed(
forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
}
- onFinish(forceFinish, null, null);
+ onFinish(forceFinish, null);
}
}
if (track.isIdle() || mReadyDuringSync.isEmpty()) {
@@ -1182,7 +1199,7 @@ public class Transitions implements RemoteCallable<Transitions>,
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.mToken, (wct, cb) -> {});
+ 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
// finishing immediately.
@@ -1210,11 +1227,8 @@ public class Transitions implements RemoteCallable<Transitions>,
* The transition must not touch the surfaces after this has been called.
*
* @param wct A WindowContainerTransaction to run along with the transition clean-up.
- * @param wctCB A sync callback that will be run when the transition clean-up is done and
- * wct has been applied.
*/
- void onTransitionFinished(@Nullable WindowContainerTransaction wct,
- @Nullable WindowContainerTransactionCallback wctCB);
+ void onTransitionFinished(@Nullable WindowContainerTransaction wct);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
index 367676f54aba..f7a060f0638b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
@@ -46,5 +46,7 @@ public interface ShellUnfoldProgressProvider {
default void onStateChangeProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onStateChangeFinished() {}
+
+ default void onFoldStateChanged(boolean isFolded) {}
}
}
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 f148412205bf..68b5a81f8d7b 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
@@ -63,6 +63,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@Nullable
private IBinder mTransition;
+ private boolean mAnimationFinished = false;
private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>();
public UnfoldTransitionHandler(ShellInit shellInit,
@@ -132,6 +133,13 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
startTransaction.apply();
mFinishCallback = finishCallback;
+
+ // Shell transition started when unfold animation has already finished,
+ // finish shell transition immediately
+ if (mAnimationFinished) {
+ finishTransitionIfNeeded();
+ }
+
return true;
}
@@ -161,17 +169,8 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@Override
public void onStateChangeFinished() {
- if (mFinishCallback == null) return;
-
- for (int i = 0; i < mAnimators.size(); i++) {
- final UnfoldTaskAnimator animator = mAnimators.get(i);
- animator.clearTasks();
- animator.stop();
- }
-
- mFinishCallback.onTransitionFinished(null, null);
- mFinishCallback = null;
- mTransition = null;
+ mAnimationFinished = true;
+ finishTransitionIfNeeded();
}
@Override
@@ -193,7 +192,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
}
// Apply changes happening during the unfold animation immediately
t.apply();
- finishCallback.onTransitionFinished(null, null);
+ finishCallback.onTransitionFinished(null);
}
}
@@ -218,4 +217,25 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
public boolean willHandleTransition() {
return mTransition != null;
}
+
+ @Override
+ public void onFoldStateChanged(boolean isFolded) {
+ if (isFolded) {
+ mAnimationFinished = false;
+ }
+ }
+
+ private void finishTransitionIfNeeded() {
+ if (mFinishCallback == null) return;
+
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ animator.clearTasks();
+ animator.stop();
+ }
+
+ mFinishCallback.onTransitionFinished(null);
+ mFinishCallback = null;
+ mTransition = null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index f81fc6fbea49..6bba0d1386fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -110,7 +110,7 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
for (int i = state.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = state.sourceAt(i);
if (source.getType() == WindowInsets.Type.navigationBars()
- && source.insetsRoundedCornerFrame()) {
+ && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
return source;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index a4cf149cc3b5..bb5d54652460 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -50,11 +50,11 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
+import dagger.Lazy;
+
import java.util.Optional;
import java.util.concurrent.Executor;
-import dagger.Lazy;
-
/**
* This helper class contains logic that calculates scaling and cropping parameters
* for the folding/unfolding animation. As an input it receives TaskInfo objects and
@@ -149,7 +149,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
for (int i = state.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = state.sourceAt(i);
if (source.getType() == WindowInsets.Type.navigationBars()
- && source.insetsRoundedCornerFrame()) {
+ && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
return source;
}
}
@@ -270,7 +270,6 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
@Override
public void prepareStartTransaction(Transaction transaction) {
mUnfoldBackgroundController.ensureBackground(transaction);
- mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 39fb7936747e..cf1692018518 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -33,11 +33,14 @@ import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
/**
@@ -89,6 +92,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
@Override
+ public void setSplitScreenController(SplitScreenController splitScreenController) {}
+
+ @Override
public boolean onTaskOpening(
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -187,7 +193,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
final DragPositioningCallback dragPositioningCallback =
new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
- null /* disallowedAreaForEndBounds */);
+ 0 /* disallowedAreaForEndBoundsHeight */);
final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, dragPositioningCallback);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -254,7 +260,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
* @return {@code true} if a drag is happening; or {@code false} if it is not
*/
@Override
- public boolean handleMotionEvent(MotionEvent e) {
+ public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return false;
@@ -268,7 +274,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
return false;
}
case MotionEvent.ACTION_MOVE: {
- int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ if (e.findPointerIndex(mDragPointerId) == -1) {
+ mDragPointerId = e.getPointerId(0);
+ }
+ final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mIsDragging = true;
@@ -276,7 +285,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ if (e.findPointerIndex(mDragPointerId) == -1) {
+ mDragPointerId = e.getPointerId(0);
+ }
+ final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
final boolean wasDragging = mIsDragging;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 116af7094e13..ce8191067ae9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -113,7 +113,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
- mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
+ mRelayoutParams.mCaptionHeightId = getCaptionHeightId();
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
@@ -143,8 +143,11 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mHandler,
mChoreographer,
mDisplay.getDisplayId(),
+ 0 /* taskCornerRadius */,
mDecorationContainerSurface,
- mDragPositioningCallback);
+ mDragPositioningCallback,
+ mSurfaceControlBuilderSupplier,
+ mSurfaceControlTransactionSupplier);
}
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
@@ -221,4 +224,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
closeDragResizeListener();
super.close();
}
+
+ @Override
+ int getCaptionHeightId() {
+ return R.dimen.freeform_decor_caption_height;
+ }
}
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 9fd57d7e1201..3a0bdd668fbc 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
@@ -19,12 +19,14 @@ package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
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_PINNED;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.DRAG_FREEFORM_SCALE;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
+import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -34,6 +36,7 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
@@ -42,6 +45,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.util.SparseArray;
import android.view.Choreographer;
+import android.view.GestureDetector;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
@@ -53,6 +57,7 @@ import android.view.View;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -67,7 +72,11 @@ import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.TaskCornersListener;
@@ -85,6 +94,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer;
+ private final ShellController mShellController;
private final Context mContext;
private final Handler mMainHandler;
private final Choreographer mMainChoreographer;
@@ -106,37 +116,41 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
private final Transitions mTransitions;
- private Optional<SplitScreenController> mSplitScreenController;
+ private SplitScreenController mSplitScreenController;
- private ValueAnimator mDragToDesktopValueAnimator;
+ private MoveToDesktopAnimator mMoveToDesktopAnimator;
private final Rect mDragToDesktopAnimationStartBounds = new Rect();
- private boolean mDragToDesktopAnimationStarted;
+ private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener;
public DesktopModeWindowDecorViewModel(
Context context,
Handler mainHandler,
Choreographer mainChoreographer,
+ ShellInit shellInit,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
+ ShellController shellController,
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopModeController> desktopModeController,
- Optional<DesktopTasksController> desktopTasksController,
- Optional<SplitScreenController> splitScreenController) {
+ Optional<DesktopTasksController> desktopTasksController
+ ) {
this(
context,
mainHandler,
mainChoreographer,
+ shellInit,
taskOrganizer,
displayController,
+ shellController,
syncQueue,
transitions,
desktopModeController,
desktopTasksController,
- splitScreenController,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
- SurfaceControl.Transaction::new);
+ SurfaceControl.Transaction::new,
+ new DesktopModeKeyguardChangeListener());
}
@VisibleForTesting
@@ -144,23 +158,25 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
Context context,
Handler mainHandler,
Choreographer mainChoreographer,
+ ShellInit shellInit,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
+ ShellController shellController,
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
- Optional<SplitScreenController> splitScreenController,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
- Supplier<SurfaceControl.Transaction> transactionFactory) {
+ Supplier<SurfaceControl.Transaction> transactionFactory,
+ DesktopModeKeyguardChangeListener desktopModeKeyguardChangeListener) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
mTaskOrganizer = taskOrganizer;
+ mShellController = shellController;
mDisplayController = displayController;
- mSplitScreenController = splitScreenController;
mSyncQueue = syncQueue;
mTransitions = transitions;
mDesktopModeController = desktopModeController;
@@ -169,6 +185,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
+ mDesktopModeKeyguardChangeListener = desktopModeKeyguardChangeListener;
+
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
}
@Override
@@ -177,6 +200,24 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
@Override
+ public void setSplitScreenController(SplitScreenController splitScreenController) {
+ mSplitScreenController = splitScreenController;
+ mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ if (visible) {
+ DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
+ if (decor != null && DesktopModeStatus.isActive(mContext)
+ && decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
+ mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
+ }
+ }
+ }
+ });
+ }
+
+ @Override
public boolean onTaskOpening(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -193,7 +234,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
if (change.getMode() == WindowManager.TRANSIT_CHANGE
- && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE)) {
+ && (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
+ || info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
+ || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
+ || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
+ || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
.addTransitionPausingRelayout(transition);
}
@@ -225,7 +270,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
removeTaskFromEventReceiver(oldTaskInfo.displayId);
incrementEventReceiverTasks(taskInfo.displayId);
}
-
decoration.relayout(taskInfo);
}
@@ -236,7 +280,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
-
if (!shouldShowWindowDecor(taskInfo)) {
if (decoration != null) {
destroyWindowDecoration(taskInfo);
@@ -275,15 +318,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
- private class DesktopModeTouchEventListener implements
- View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
+ private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
+ implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
private final int mTaskId;
private final WindowContainerToken mTaskToken;
private final DragPositioningCallback mDragPositioningCallback;
private final DragDetector mDragDetector;
+ private final GestureDetector mGestureDetector;
private boolean mIsDragging;
+ private boolean mShouldClick;
private int mDragPointerId = -1;
private DesktopModeTouchEventListener(
@@ -293,6 +338,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mTaskToken = taskInfo.token;
mDragPositioningCallback = dragPositioningCallback;
mDragDetector = new DragDetector(this);
+ mGestureDetector = new GestureDetector(mContext, this);
}
@Override
@@ -301,14 +347,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final int id = v.getId();
if (id == R.id.close_window || id == R.id.close_button) {
mTaskOperations.closeTask(mTaskToken);
- if (mSplitScreenController.isPresent()
- && mSplitScreenController.get().isSplitScreenVisible()) {
- int remainingTaskPosition = mTaskId == mSplitScreenController.get()
+ if (mSplitScreenController != null
+ && mSplitScreenController.isSplitScreenVisible()) {
+ int remainingTaskPosition = mTaskId == mSplitScreenController
.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
- ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController.get()
+ ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController
.getTaskInfo(remainingTaskPosition);
- mSplitScreenController.get().moveTaskToFullscreen(remainingTask.taskId);
+ mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId);
}
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey();
@@ -321,12 +367,24 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
} else if (id == R.id.desktop_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
- mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId));
+ if (mDesktopTasksController.isPresent()) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // App sometimes draws before the insets from WindowDecoration#relayout have
+ // been added, so they must be added here
+ mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
+ decoration.incrementRelayoutBlock();
+ mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
+ }
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
+ } else if (id == R.id.split_screen_button) {
+ decoration.closeHandleMenu();
+ mDesktopTasksController.ifPresent(c -> {
+ c.requestSplit(decoration.mTaskInfo);
+ });
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.select_button) {
@@ -336,6 +394,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
decoration.closeHandleMenu();
}
+ } else if (id == R.id.maximize_window) {
+ final RunningTaskInfo taskInfo = decoration.mTaskInfo;
+ mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
+ taskInfo, decoration));
+ decoration.closeHandleMenu();
}
}
@@ -347,7 +410,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return false;
}
moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
- return mDragDetector.onMotionEvent(e);
+ return mDragDetector.onMotionEvent(v, e);
}
private void moveTaskToFront(RunningTaskInfo taskInfo) {
@@ -362,7 +425,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
* @return {@code true} if the motion event is handled.
*/
@Override
- public boolean handleMotionEvent(MotionEvent e) {
+ public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (DesktopModeStatus.isProto2Enabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
@@ -373,6 +436,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
== WINDOWING_MODE_FULLSCREEN) {
return false;
}
+ if (mGestureDetector.onTouchEvent(e)) {
+ return true;
+ }
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
@@ -380,21 +446,40 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
0 /* ctrlType */, e.getRawX(0),
e.getRawY(0));
mIsDragging = false;
- return false;
+ mShouldClick = true;
+ return true;
}
case MotionEvent.ACTION_MOVE: {
final DesktopModeWindowDecoration decoration =
mWindowDecorByTaskId.get(mTaskId);
+ if (e.findPointerIndex(mDragPointerId) == -1) {
+ mDragPointerId = e.getPointerId(0);
+ }
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
- decoration.mTaskSurface, e.getRawY(dragPointerIdx)));
- mDragPositioningCallback.onDragPositioningMove(
+ final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+ decoration.mTaskSurface,
+ new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
+ newTaskBounds));
mIsDragging = true;
+ mShouldClick = false;
return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
+ final boolean wasDragging = mIsDragging;
+ if (!wasDragging) {
+ if (mShouldClick && v != null) {
+ v.performClick();
+ mShouldClick = false;
+ return true;
+ }
+ return false;
+ }
+ if (e.findPointerIndex(mDragPointerId) == -1) {
+ mDragPointerId = e.getPointerId(0);
+ }
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
// Position of the task is calculated by subtracting the raw location of the
// motion event (the location of the motion relative to the display) by the
@@ -402,17 +487,27 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final Point position = new Point(
(int) (e.getRawX(dragPointerIdx) - e.getX(dragPointerIdx)),
(int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx)));
- mDragPositioningCallback.onDragPositioningEnd(
+ final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
- position));
- final boolean wasDragging = mIsDragging;
+ position,
+ new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
+ newTaskBounds, mWindowDecorByTaskId.get(mTaskId)));
mIsDragging = false;
- return wasDragging;
+ return true;
}
}
return true;
}
+
+ @Override
+ public boolean onDoubleTap(@NonNull MotionEvent e) {
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ mDesktopTasksController.ifPresent(c -> {
+ c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId));
+ });
+ return true;
+ }
}
// InputEventReceiver to listen for touch input outside of caption bounds
@@ -540,9 +635,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
boolean dragFromStatusBarAllowed = false;
if (DesktopModeStatus.isProto2Enabled()) {
- // In proto2 any full screen task can be dragged to freeform
- dragFromStatusBarAllowed = relevantDecor.mTaskInfo.getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN;
+ // In proto2 any full screen or multi-window task can be dragged to
+ // freeform.
+ final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
+ dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_MULTI_WINDOW;
}
if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) {
@@ -553,7 +650,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
case MotionEvent.ACTION_UP: {
if (relevantDecor == null) {
- mDragToDesktopAnimationStarted = false;
+ mMoveToDesktopAnimator = null;
mTransitionDragActive = false;
return;
}
@@ -567,14 +664,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
} else if (DesktopModeStatus.isProto1Enabled()) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
- mDragToDesktopAnimationStarted = false;
+ mMoveToDesktopAnimator = null;
return;
- } else if (mDragToDesktopAnimationStarted) {
- Point position = new Point((int) ev.getX(), (int) ev.getY());
+ } else if (mMoveToDesktopAnimator != null) {
+ relevantDecor.incrementRelayoutBlock();
mDesktopTasksController.ifPresent(
- c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
- position));
- mDragToDesktopAnimationStarted = false;
+ c -> c.cancelMoveToDesktop(relevantDecor.mTaskInfo,
+ mMoveToDesktopAnimator));
+ mMoveToDesktopAnimator = null;
return;
}
}
@@ -594,21 +691,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > statusBarHeight) {
- if (!mDragToDesktopAnimationStarted) {
- mDragToDesktopAnimationStarted = true;
+ if (mMoveToDesktopAnimator == null) {
+ mMoveToDesktopAnimator = new MoveToDesktopAnimator(
+ mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface);
mDesktopTasksController.ifPresent(
- c -> c.moveToFreeform(relevantDecor.mTaskInfo,
- mDragToDesktopAnimationStartBounds));
- startAnimation(relevantDecor);
+ c -> c.startMoveToDesktop(relevantDecor.mTaskInfo,
+ mDragToDesktopAnimationStartBounds,
+ mMoveToDesktopAnimator));
+ mMoveToDesktopAnimator.startAnimation();
}
}
- if (mDragToDesktopAnimationStarted) {
- Transaction t = mTransactionFactory.get();
- float width = (float) mDragToDesktopValueAnimator.getAnimatedValue()
- * mDragToDesktopAnimationStartBounds.width();
- float x = ev.getX() - (width / 2);
- t.setPosition(relevantDecor.mTaskSurface, x, ev.getY());
- t.apply();
+ if (mMoveToDesktopAnimator != null) {
+ mMoveToDesktopAnimator.updatePosition(ev);
}
}
break;
@@ -616,7 +711,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
case MotionEvent.ACTION_CANCEL: {
mTransitionDragActive = false;
- mDragToDesktopAnimationStarted = false;
+ mMoveToDesktopAnimator = null;
}
}
}
@@ -683,24 +778,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
animator.start();
}
- private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) {
- mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE);
- mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION);
- final Transaction t = mTransactionFactory.get();
- mDragToDesktopValueAnimator.addUpdateListener(animation -> {
- final float animatorValue = (float) animation.getAnimatedValue();
- SurfaceControl sc = focusedDecor.mTaskSurface;
- t.setScale(sc, animatorValue, animatorValue);
- t.apply();
- });
-
- mDragToDesktopValueAnimator.start();
- }
-
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
- if (mSplitScreenController.isPresent()
- && mSplitScreenController.get().isSplitScreenVisible()) {
+ if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
// We can't look at focused task here as only one task will have focus.
return getSplitScreenDecor(ev);
} else {
@@ -711,9 +791,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Nullable
private DesktopModeWindowDecoration getSplitScreenDecor(MotionEvent ev) {
ActivityManager.RunningTaskInfo topOrLeftTask =
- mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+ mSplitScreenController.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
ActivityManager.RunningTaskInfo bottomOrRightTask =
- mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
if (topOrLeftTask != null && topOrLeftTask.getConfiguration()
.windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
return mWindowDecorByTaskId.get(topOrLeftTask.taskId);
@@ -765,12 +845,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
- if (mSplitScreenController.isPresent()
- && mSplitScreenController.get().isTaskRootOrStageRoot(taskInfo.taskId)) {
+ if (mSplitScreenController != null
+ && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
+ return false;
+ }
+ if (mDesktopModeKeyguardChangeListener.isKeyguardVisibleAndOccluded()
+ && taskInfo.isFocused) {
return false;
}
return DesktopModeStatus.isProto2Enabled()
+ && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+ && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
@@ -796,9 +882,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mMainChoreographer,
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+ windowDecoration.createResizeVeil();
final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
- windowDecoration, taskInfo);
+ windowDecoration);
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
@@ -811,20 +898,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
incrementEventReceiverTasks(taskInfo.displayId);
}
private DragPositioningCallback createDragPositioningCallback(
- @NonNull DesktopModeWindowDecoration windowDecoration,
- @NonNull RunningTaskInfo taskInfo) {
- final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width();
- final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth,
- getStatusBarHeight(taskInfo.displayId));
+ @NonNull DesktopModeWindowDecoration windowDecoration) {
+ final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.desktop_mode_transition_area_height);
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
- mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
- mTransactionFactory);
+ mDisplayController, mDragStartListener, mTransactionFactory,
+ transitionAreaHeight);
} else {
- windowDecoration.createResizeVeil();
return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
- mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
- mTransitions);
+ mDisplayController, mDragStartListener, mTransitions,
+ transitionAreaHeight);
}
}
@@ -857,6 +941,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(taskId));
}
}
+
+ static class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
+ private boolean mIsKeyguardVisible;
+ private boolean mIsKeyguardOccluded;
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
+ mIsKeyguardVisible = visible;
+ mIsKeyguardOccluded = occluded;
+ }
+
+ public boolean isKeyguardVisibleAndOccluded() {
+ return mIsKeyguardVisible && mIsKeyguardOccluded;
+ }
+ }
}
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 ce11b2604559..a359395711e3 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
@@ -23,7 +23,6 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.content.res.TypedArray;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -39,6 +38,7 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.window.WindowContainerTransaction;
+import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -178,15 +178,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = windowDecorLayoutId;
- mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
+ mRelayoutParams.mCaptionHeightId = getCaptionHeightId();
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
- final TypedArray ta = mContext.obtainStyledAttributes(
- new int[]{android.R.attr.dialogCornerRadius});
- mRelayoutParams.mCornerRadius = ta.getDimensionPixelSize(0, 0);
- ta.recycle();
-
+ mRelayoutParams.mCornerRadius =
+ (int) ScreenDecorationsUtils.getWindowCornerRadius(mContext);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -235,8 +232,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mHandler,
mChoreographer,
mDisplay.getDisplayId(),
+ mRelayoutParams.mCornerRadius,
mDecorationContainerSurface,
- mDragPositioningCallback);
+ mDragPositioningCallback,
+ mSurfaceControlBuilderSupplier,
+ mSurfaceControlTransactionSupplier);
}
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
@@ -294,23 +294,37 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
- * Fade in the resize veil
+ * Show the resize veil.
*/
- void showResizeVeil(Rect taskBounds) {
+ public void showResizeVeil(Rect taskBounds) {
mResizeVeil.showVeil(mTaskSurface, taskBounds);
}
/**
+ * Show the resize veil.
+ */
+ public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) {
+ mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, false /* fadeIn */);
+ }
+
+ /**
* Set new bounds for the resize veil
*/
- void updateResizeVeil(Rect newBounds) {
+ public void updateResizeVeil(Rect newBounds) {
mResizeVeil.updateResizeVeil(newBounds);
}
/**
+ * Set new bounds for the resize veil
+ */
+ public void updateResizeVeil(SurfaceControl.Transaction tx, Rect newBounds) {
+ mResizeVeil.updateResizeVeil(tx, newBounds);
+ }
+
+ /**
* Fade the resize veil out.
*/
- void hideResizeVeil() {
+ public void hideResizeVeil() {
mResizeVeil.hideVeil();
}
@@ -479,6 +493,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
}
+ @Override
+ int getCaptionHeightId() {
+ return R.dimen.freeform_decor_caption_height;
+ }
+
/**
* Add transition to mTransitionsPausingRelayout
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index 65b5a7a17afe..da268988bac7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -24,6 +24,9 @@ import static android.view.MotionEvent.ACTION_UP;
import android.graphics.PointF;
import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
/**
* A detector for touch inputs that differentiates between drag and click inputs. It receives a flow
@@ -54,14 +57,24 @@ class DragDetector {
*
* @return the result returned by {@link #mEventHandler}, or the result when
* {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
- */
+ */
boolean onMotionEvent(MotionEvent ev) {
+ return onMotionEvent(null /* view */, ev);
+ }
+
+ /**
+ * The receiver of the {@link MotionEvent} flow.
+ *
+ * @return the result returned by {@link #mEventHandler}, or the result when
+ * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
+ */
+ boolean onMotionEvent(View v, MotionEvent ev) {
final boolean isTouchScreen =
(ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
if (!isTouchScreen) {
// Only touches generate noisy moves, so mouse/trackpad events don't need to filtered
// to take the slop threshold into consideration.
- return mEventHandler.handleMotionEvent(ev);
+ return mEventHandler.handleMotionEvent(v, ev);
}
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
@@ -69,12 +82,15 @@ class DragDetector {
float rawX = ev.getRawX(0);
float rawY = ev.getRawY(0);
mInputDownPoint.set(rawX, rawY);
- mResultOfDownAction = mEventHandler.handleMotionEvent(ev);
+ mResultOfDownAction = mEventHandler.handleMotionEvent(v, ev);
return mResultOfDownAction;
}
case ACTION_MOVE: {
+ if (ev.findPointerIndex(mDragPointerId) == -1) {
+ mDragPointerId = ev.getPointerId(0);
+ }
+ final int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
if (!mIsDragEvent) {
- int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
// Touches generate noisy moves, so only once the move is past the touch
@@ -84,7 +100,7 @@ class DragDetector {
// The event handler should only be notified about 'move' events if a drag has been
// detected.
if (mIsDragEvent) {
- return mEventHandler.handleMotionEvent(ev);
+ return mEventHandler.handleMotionEvent(v, ev);
} else {
return mResultOfDownAction;
}
@@ -92,10 +108,10 @@ class DragDetector {
case ACTION_UP:
case ACTION_CANCEL: {
resetState();
- return mEventHandler.handleMotionEvent(ev);
+ return mEventHandler.handleMotionEvent(v, ev);
}
default:
- return mEventHandler.handleMotionEvent(ev);
+ return mEventHandler.handleMotionEvent(v, ev);
}
}
@@ -111,6 +127,6 @@ class DragDetector {
}
interface MotionEventHandler {
- boolean handleMotionEvent(MotionEvent ev);
+ boolean handleMotionEvent(@Nullable View v, MotionEvent ev);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 4e98f0c3a48a..1669cf4a222c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -17,12 +17,15 @@
package com.android.wm.shell.windowdecor;
import android.annotation.IntDef;
+import android.graphics.Rect;
/**
* Callback called when receiving drag-resize or drag-move related input events.
*/
public interface DragPositioningCallback {
- @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
+ @IntDef(flag = true, value = {
+ CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM
+ })
@interface CtrlType {}
int CTRL_TYPE_UNDEFINED = 0;
@@ -44,13 +47,15 @@ public interface DragPositioningCallback {
* Called when the pointer moves during a drag-resize or drag-move.
* @param x x coordinate in window decoration coordinate system of the new pointer location
* @param y y coordinate in window decoration coordinate system of the new pointer location
+ * @return the updated task bounds
*/
- void onDragPositioningMove(float x, float y);
+ Rect onDragPositioningMove(float x, float y);
/**
* Called when a drag-resize or drag-move stops.
* @param x x coordinate in window decoration coordinate system where the drag resize stops
* @param y y coordinate in window decoration coordinate system where the drag resize stops
+ * @return the final bounds for the dragged task
*/
- void onDragPositioningEnd(float x, float y);
+ Rect onDragPositioningEnd(float x, float y);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 09e29bcbcf9f..e32bd42acf74 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -83,8 +83,6 @@ public class DragPositioningCallbackUtility {
// Make sure the new resizing destination in any direction falls within the stable bounds.
// If not, set the bounds back to the old location that was valid to avoid conflicts with
// some regions such as the gesture area.
- displayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
- .getStableBounds(stableBounds);
if ((ctrlType & CTRL_TYPE_LEFT) != 0) {
final int candidateLeft = repositionTaskBounds.left + (int) delta.x;
repositionTaskBounds.left = (candidateLeft > stableBounds.left)
@@ -136,7 +134,7 @@ public class DragPositioningCallbackUtility {
repositionTaskBounds.top);
}
- static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
PointF repositionStartPoint, float x, float y) {
final float deltaX = x - repositionStartPoint.x;
final float deltaY = y - repositionStartPoint.y;
@@ -145,6 +143,23 @@ public class DragPositioningCallbackUtility {
}
/**
+ * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
+ * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
+ * stable bounds.
+ */
+ static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
+ PointF repositionStartPoint, float x, float y) {
+ updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
+ x, y);
+
+ // If task is outside of stable bounds (in the status bar area), shift the task down.
+ if (stableBounds.top > repositionTaskBounds.top) {
+ final int yShift = stableBounds.top - repositionTaskBounds.top;
+ repositionTaskBounds.offset(0, yShift);
+ }
+ }
+
+ /**
* Apply a bounds change to a task.
* @param windowDecoration decor of task we are changing bounds for
* @param taskBounds new bounds of this task
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 287d86187288..7c6fb99e9c8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -18,8 +18,11 @@ package com.android.wm.shell.windowdecor;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
@@ -42,11 +45,14 @@ import android.view.InputEventReceiver;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.SurfaceControl;
+import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal;
import com.android.internal.view.BaseIWindow;
+import java.util.function.Supplier;
+
/**
* An input event listener registered to InputDispatcher to receive input events on task edges and
* and corners. Converts them to drag resize requests.
@@ -55,11 +61,11 @@ import com.android.internal.view.BaseIWindow;
*/
class DragResizeInputListener implements AutoCloseable {
private static final String TAG = "DragResizeInputListener";
-
private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
private final Handler mHandler;
private final Choreographer mChoreographer;
private final InputManager mInputManager;
+ private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
private final int mDisplayId;
private final BaseIWindow mFakeWindow;
@@ -69,10 +75,15 @@ class DragResizeInputListener implements AutoCloseable {
private final TaskResizeInputEventReceiver mInputEventReceiver;
private final DragPositioningCallback mCallback;
+ private final SurfaceControl mInputSinkSurface;
+ private final BaseIWindow mFakeSinkWindow;
+ private final InputChannel mSinkInputChannel;
+
private int mTaskWidth;
private int mTaskHeight;
private int mResizeHandleThickness;
private int mCornerSize;
+ private int mTaskCornerRadius;
private Rect mLeftTopCornerBounds;
private Rect mRightTopCornerBounds;
@@ -87,15 +98,20 @@ class DragResizeInputListener implements AutoCloseable {
Handler handler,
Choreographer choreographer,
int displayId,
+ int taskCornerRadius,
SurfaceControl decorationSurface,
- DragPositioningCallback callback) {
+ DragPositioningCallback callback,
+ Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
mInputManager = context.getSystemService(InputManager.class);
mHandler = handler;
mChoreographer = choreographer;
+ mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mDisplayId = displayId;
+ mTaskCornerRadius = taskCornerRadius;
mDecorationSurface = decorationSurface;
- // Use a fake window as the backing surface is a container layer and we don't want to create
- // a buffer layer for it so we can't use ViewRootImpl.
+ // Use a fake window as the backing surface is a container layer, and we don't want to
+ // create a buffer layer for it, so we can't use ViewRootImpl.
mFakeWindow = new BaseIWindow();
mFakeWindow.setSession(mWindowSession);
mFocusGrantToken = new Binder();
@@ -108,7 +124,7 @@ class DragResizeInputListener implements AutoCloseable {
null /* hostInputToken */,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
- 0 /* inputFeatures */,
+ INPUT_FEATURE_SPY,
TYPE_APPLICATION,
null /* windowToken */,
mFocusGrantToken,
@@ -123,15 +139,39 @@ class DragResizeInputListener implements AutoCloseable {
mCallback = callback;
mDragDetector = new DragDetector(mInputEventReceiver);
mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
+
+ mInputSinkSurface = surfaceControlBuilderSupplier.get()
+ .setName("TaskInputSink of " + decorationSurface)
+ .setContainerLayer()
+ .setParent(mDecorationSurface)
+ .build();
+ mSurfaceControlTransactionSupplier.get()
+ .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
+ .show(mInputSinkSurface)
+ .apply();
+ mFakeSinkWindow = new BaseIWindow();
+ mSinkInputChannel = new InputChannel();
+ try {
+ mWindowSession.grantInputChannel(
+ mDisplayId,
+ mInputSinkSurface,
+ mFakeSinkWindow,
+ null /* hostInputToken */,
+ FLAG_NOT_FOCUSABLE,
+ 0 /* privateFlags */,
+ INPUT_FEATURE_NO_INPUT_CHANNEL,
+ TYPE_INPUT_CONSUMER,
+ null /* windowToken */,
+ mFocusGrantToken,
+ "TaskInputSink of " + decorationSurface,
+ mSinkInputChannel);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
/**
- * Updates geometry of this drag resize handler. Needs to be called every time there is a size
- * change to notify the input event receiver it's ready to take the next input event. Otherwise
- * it'll keep batching move events and the drag resize process is stalled.
- *
- * This is also used to update the touch regions of this handler every event dispatched here is
- * a potential resize request.
+ * Updates the geometry (the touch region) of this drag resize handler.
*
* @param taskWidth The width of the task.
* @param taskHeight The height of the task.
@@ -221,7 +261,35 @@ class DragResizeInputListener implements AutoCloseable {
mDecorationSurface,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
- 0 /* inputFeatures */,
+ INPUT_FEATURE_SPY,
+ touchRegion);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ mSurfaceControlTransactionSupplier.get()
+ .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight)
+ .apply();
+ // The touch region of the TaskInputSink should be the touch region of this
+ // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent
+ // input windows from handling down events, which will bring tasks in the back to front.
+ //
+ // Note not the entire touch region responds to both mouse and touchscreen events.
+ // Therefore, in the region that only responds to one of them, it would be a no-op to
+ // perform a gesture in the other type of events. We currently only have a mouse-only region
+ // out of the task bounds, and due to the roughness of touchscreen events, it's not a severe
+ // issue. However, were there touchscreen-only a region out of the task bounds, mouse
+ // gestures will become no-op in that region, even though the mouse gestures may appear to
+ // be performed on the input window behind the resize handle.
+ touchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
+ try {
+ mWindowSession.updateInputChannel(
+ mSinkInputChannel.getToken(),
+ mDisplayId,
+ mInputSinkSurface,
+ FLAG_NOT_FOCUSABLE,
+ 0 /* privateFlags */,
+ INPUT_FEATURE_NO_INPUT_CHANNEL,
touchRegion);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -250,6 +318,16 @@ class DragResizeInputListener implements AutoCloseable {
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
+
+ mSinkInputChannel.dispose();
+ try {
+ mWindowSession.remove(mFakeSinkWindow);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ mSurfaceControlTransactionSupplier.get()
+ .remove(mInputSinkSurface)
+ .apply();
}
private class TaskResizeInputEventReceiver extends InputEventReceiver
@@ -258,6 +336,7 @@ class DragResizeInputListener implements AutoCloseable {
private final Runnable mConsumeBatchEventRunnable;
private boolean mConsumeBatchEventScheduled;
private boolean mShouldHandleEvents;
+ private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -303,7 +382,7 @@ class DragResizeInputListener implements AutoCloseable {
}
@Override
- public boolean handleMotionEvent(MotionEvent e) {
+ public boolean handleMotionEvent(View v, MotionEvent e) {
boolean result = false;
// Check if this is a touch event vs mouse event.
// Touch events are tracked in four corners. Other events are tracked in resize edges.
@@ -318,6 +397,8 @@ class DragResizeInputListener implements AutoCloseable {
mShouldHandleEvents = isInResizeHandleBounds(x, y);
}
if (mShouldHandleEvents) {
+ mInputManager.pilferPointers(mInputChannel.getToken());
+
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
@@ -357,7 +438,6 @@ class DragResizeInputListener implements AutoCloseable {
break;
}
case MotionEvent.ACTION_HOVER_EXIT:
- mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
result = true;
break;
}
@@ -383,19 +463,71 @@ class DragResizeInputListener implements AutoCloseable {
@DragPositioningCallback.CtrlType
private int calculateResizeHandlesCtrlType(float x, float y) {
int ctrlType = 0;
- if (x < 0) {
+ // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
+ // sides will use the bounds specified in setGeometry and not go into task bounds.
+ if (x < mTaskCornerRadius) {
ctrlType |= CTRL_TYPE_LEFT;
}
- if (x > mTaskWidth) {
+ if (x > mTaskWidth - mTaskCornerRadius) {
ctrlType |= CTRL_TYPE_RIGHT;
}
- if (y < 0) {
+ if (y < mTaskCornerRadius) {
ctrlType |= CTRL_TYPE_TOP;
}
- if (y > mTaskHeight) {
+ if (y > mTaskHeight - mTaskCornerRadius) {
ctrlType |= CTRL_TYPE_BOTTOM;
}
- return ctrlType;
+ // Check distances from the center if it's in one of four corners.
+ if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
+ && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
+ return checkDistanceFromCenter(ctrlType, x, y);
+ }
+ // Otherwise, we should make sure we don't resize tasks inside task bounds.
+ return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0;
+ }
+
+ // If corner input is not within appropriate distance of corner radius, do not use it.
+ // If input is not on a corner or is within valid distance, return ctrlType.
+ @DragPositioningCallback.CtrlType
+ private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType,
+ float x, float y) {
+ int centerX;
+ int centerY;
+
+ // Determine center of rounded corner circle; this is simply the corner if radius is 0.
+ switch (ctrlType) {
+ case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
+ centerX = mTaskCornerRadius;
+ centerY = mTaskCornerRadius;
+ break;
+ }
+ case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
+ centerX = mTaskCornerRadius;
+ centerY = mTaskHeight - mTaskCornerRadius;
+ break;
+ }
+ case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
+ centerX = mTaskWidth - mTaskCornerRadius;
+ centerY = mTaskCornerRadius;
+ break;
+ }
+ case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
+ centerX = mTaskWidth - mTaskCornerRadius;
+ centerY = mTaskHeight - mTaskCornerRadius;
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("ctrlType should be complex, but it's 0x"
+ + Integer.toHexString(ctrlType));
+ }
+ }
+ double distanceFromCenter = Math.hypot(x - centerX, y - centerY);
+
+ if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
+ && distanceFromCenter >= mTaskCornerRadius) {
+ return ctrlType;
+ }
+ return 0;
}
@DragPositioningCallback.CtrlType
@@ -439,7 +571,19 @@ class DragResizeInputListener implements AutoCloseable {
cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
break;
}
- mInputManager.setPointerIconType(cursorType);
+ // Only update the cursor type to default once so that views behind the decor container
+ // layer that aren't in the active resizing regions have chances to update the cursor
+ // type. We would like to enforce the cursor type by setting the cursor type multilple
+ // times in active regions because we shouldn't allow the views behind to change it, as
+ // we'll pilfer the gesture initiated in this area. This is necessary because 1) we
+ // should allow the views behind regions only for touches to set the cursor type; and 2)
+ // there is a small region out of each rounded corner that's inside the task bounds,
+ // where views in the task can receive input events because we can't set touch regions
+ // of input sinks to have rounded corners.
+ if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
+ mInputManager.setPointerIconType(cursorType);
+ mLastCursorType = cursorType;
+ }
}
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 9082323452c9..e0ee25242550 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -21,8 +21,6 @@ import android.graphics.Rect;
import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
-import androidx.annotation.Nullable;
-
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -42,28 +40,29 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes in this region, the positioner will not attempt to
+ // If a task move (not resize) finishes with the positions y less than this value, do not
// finalize the bounds there using WCT#setBounds
- private final Rect mDisallowedAreaForEndBounds;
+ private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
private int mCtrlType;
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds) {
- this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
- dragStartListener -> {}, SurfaceControl.Transaction::new);
+ DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
+ this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {},
+ SurfaceControl.Transaction::new, disallowedAreaForEndBoundsHeight);
}
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds,
+ DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier) {
+ Supplier<SurfaceControl.Transaction> supplier,
+ int disallowedAreaForEndBoundsHeight) {
mTaskOrganizer = taskOrganizer;
mWindowDecoration = windowDecoration;
mDisplayController = displayController;
- mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds);
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
+ mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -79,10 +78,14 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
mTaskOrganizer.applyTransaction(wct);
}
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+ if (mStableBounds.isEmpty()) {
+ mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(mStableBounds);
+ }
}
@Override
- public void onDragPositioningMove(float x, float y) {
+ public Rect onDragPositioningMove(float x, float y) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint);
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
@@ -103,10 +106,11 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
t.apply();
}
+ return new Rect(mRepositionTaskBounds);
}
@Override
- public void onDragPositioningEnd(float x, float y) {
+ public Rect onDragPositioningEnd(float x, float y) {
// If task has been resized or task was dragged into area outside of
// mDisallowedAreaForEndBounds, apply WCT to finish it.
if (isResizing() && mHasDragResized) {
@@ -121,10 +125,10 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
}
mTaskOrganizer.applyTransaction(wct);
} else if (mCtrlType == CTRL_TYPE_UNDEFINED
- && !mDisallowedAreaForEndBounds.contains((int) x, (int) y)) {
+ && y > mDisallowedAreaForEndBoundsHeight) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
+ DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTaskOrganizer.applyTransaction(wct);
}
@@ -133,6 +137,7 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
mRepositionStartPoint.set(0, 0);
mCtrlType = CTRL_TYPE_UNDEFINED;
mHasDragResized = false;
+ return new Rect(mRepositionTaskBounds);
}
private boolean isResizing() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
new file mode 100644
index 000000000000..b2267ddb6ba7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -0,0 +1,72 @@
+package com.android.wm.shell.windowdecor
+
+import android.animation.ValueAnimator
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.PointF
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.SurfaceControl
+
+/**
+ * Creates an animator to shrink and position task after a user drags a fullscreen task from
+ * the top of the screen to transition it into freeform and before the user releases the task. The
+ * MoveToDesktopAnimator object also holds information about the state of the task that are
+ * accessed by the EnterDesktopTaskTransitionHandler.
+ */
+class MoveToDesktopAnimator @JvmOverloads constructor(
+ private val startBounds: Rect,
+ private val taskInfo: RunningTaskInfo,
+ private val taskSurface: SurfaceControl,
+ private val transactionFactory: () -> SurfaceControl.Transaction =
+ SurfaceControl::Transaction
+) {
+ companion object {
+ // The size of the screen during drag relative to the fullscreen size
+ const val DRAG_FREEFORM_SCALE: Float = 0.4f
+ const val ANIMATION_DURATION = 336
+ }
+
+ private val animatedTaskWidth
+ get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width()
+ private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
+ DRAG_FREEFORM_SCALE)
+ .setDuration(ANIMATION_DURATION.toLong())
+ .apply {
+ val t = SurfaceControl.Transaction()
+ addUpdateListener { animation ->
+ val animatorValue = animation.animatedValue as Float
+ t.setScale(taskSurface, animatorValue, animatorValue)
+ .apply()
+ }
+ }
+
+ val taskId get() = taskInfo.taskId
+ val position: PointF = PointF(0.0f, 0.0f)
+
+ /**
+ * Starts the animation that scales the task down.
+ */
+ fun startAnimation() {
+ dragToDesktopAnimator.start()
+ }
+
+ /**
+ * Uses the position of the motion event and the current scale of the task as defined by the
+ * ValueAnimator to update the local position variable and set the task surface's position
+ */
+ fun updatePosition(ev: MotionEvent) {
+ position.x = ev.x - animatedTaskWidth / 2
+ position.y = ev.y
+
+ val t = transactionFactory()
+ t.setPosition(taskSurface, position.x, position.y)
+ t.apply()
+ }
+
+ /**
+ * Ends the animation, setting the scale and position to the final animation value
+ */
+ fun endAnimator() {
+ dragToDesktopAnimator.end()
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index 82771095cd82..bfce72bcadf0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -102,11 +102,17 @@ public class ResizeVeil {
}
/**
- * Animate veil's alpha to 1, fading it in.
+ * Shows the veil surface/view.
+ *
+ * @param t the transaction to apply in sync with the veil draw
+ * @param parentSurface the surface that the veil should be a child of
+ * @param taskBounds the bounds of the task that owns the veil
+ * @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown
+ * immediately
*/
- public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+ public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface,
+ Rect taskBounds, boolean fadeIn) {
// Parent surface can change, ensure it is up to date.
- SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
if (!parentSurface.equals(mParentSurface)) {
t.reparent(mVeilSurface, parentSurface);
mParentSurface = parentSurface;
@@ -115,22 +121,36 @@ public class ResizeVeil {
int backgroundColorId = getBackgroundColorId();
mViewHost.getView().setBackgroundColor(mContext.getColor(backgroundColorId));
- final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(0f, 1f);
- animator.setDuration(RESIZE_ALPHA_DURATION);
- animator.addUpdateListener(animation -> {
- t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
- t.apply();
- });
-
relayout(taskBounds, t);
- t.show(mVeilSurface)
- .addTransactionCommittedListener(mContext.getMainExecutor(), () -> animator.start())
- .setAlpha(mVeilSurface, 0);
+ if (fadeIn) {
+ final ValueAnimator animator = new ValueAnimator();
+ animator.setFloatValues(0f, 1f);
+ animator.setDuration(RESIZE_ALPHA_DURATION);
+ animator.addUpdateListener(animation -> {
+ t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
+ t.apply();
+ });
+
+ t.show(mVeilSurface)
+ .addTransactionCommittedListener(
+ mContext.getMainExecutor(), () -> animator.start())
+ .setAlpha(mVeilSurface, 0);
+ } else {
+ // Show the veil immediately at full opacity.
+ t.show(mVeilSurface).setAlpha(mVeilSurface, 1);
+ }
mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
}
/**
+ * Animate veil's alpha to 1, fading it in.
+ */
+ public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+ SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ showVeil(t, parentSurface, taskBounds, true /* fadeIn */);
+ }
+
+ /**
* Update veil bounds to match bounds changes.
* @param newBounds bounds to update veil to.
*/
@@ -147,6 +167,16 @@ public class ResizeVeil {
*/
public void updateResizeVeil(Rect newBounds) {
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ updateResizeVeil(t, newBounds);
+ }
+
+ /**
+ * Calls relayout to update task and veil bounds.
+ *
+ * @param t a transaction to be applied in sync with the veil draw.
+ * @param newBounds bounds to update veil to.
+ */
+ public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
relayout(newBounds, t);
mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 58c78e6a5b9f..c9c58de6e82a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -53,33 +53,33 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes in this region, the positioner will not attempt to
+ // If a task move (not resize) finishes with the positions y less than this value, do not
// finalize the bounds there using WCT#setBounds
- private final Rect mDisallowedAreaForEndBounds = new Rect();
+ private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
- Rect disallowedAreaForEndBounds,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Transitions transitions) {
- this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
- dragStartListener, SurfaceControl.Transaction::new, transitions);
+ Transitions transitions,
+ int disallowedAreaForEndBoundsHeight) {
+ this(taskOrganizer, windowDecoration, displayController, dragStartListener,
+ SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
- Rect disallowedAreaForEndBounds,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
+ Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
+ int disallowedAreaForEndBoundsHeight) {
mTaskOrganizer = taskOrganizer;
mDesktopWindowDecoration = windowDecoration;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
- mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds);
mTransactionSupplier = supplier;
mTransitions = transitions;
+ mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -98,10 +98,14 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
}
mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+ if (mStableBounds.isEmpty()) {
+ mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(mStableBounds);
+ }
}
@Override
- public void onDragPositioningMove(float x, float y) {
+ public Rect onDragPositioningMove(float x, float y) {
PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint);
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
@@ -110,14 +114,14 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
- mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t,
- x, y);
+ mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
t.apply();
}
+ return new Rect(mRepositionTaskBounds);
}
@Override
- public void onDragPositioningEnd(float x, float y) {
+ public Rect onDragPositioningEnd(float x, float y) {
PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y,
mRepositionStartPoint);
if (isResizing()) {
@@ -138,9 +142,9 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
// won't be called.
mDesktopWindowDecoration.hideResizeVeil();
}
- } else if (!mDisallowedAreaForEndBounds.contains((int) x, (int) y)) {
- DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
+ } else if (y > mDisallowedAreaForEndBoundsHeight) {
+ DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
}
@@ -148,6 +152,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
mCtrlType = CTRL_TYPE_UNDEFINED;
mTaskBoundsAtDragStart.setEmpty();
mRepositionStartPoint.set(0, 0);
+ return new Rect(mRepositionTaskBounds);
}
private boolean isResizing() {
@@ -163,7 +168,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
startTransaction.apply();
mDesktopWindowDecoration.hideResizeVeil();
mCtrlType = CTRL_TYPE_UNDEFINED;
- finishCallback.onTransitionFinished(null, null);
+ finishCallback.onTransitionFinished(null);
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 9f03d9aec166..ae1a3d914be3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -22,6 +22,7 @@ import android.view.SurfaceControl;
import android.window.TransitionInfo;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.splitscreen.SplitScreenController;
/**
* The interface used by some {@link com.android.wm.shell.ShellTaskOrganizer.TaskListener} to help
@@ -39,6 +40,11 @@ public interface WindowDecorViewModel {
void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter);
/**
+ * Sets the {@link SplitScreenController} if available.
+ */
+ void setSplitScreenController(SplitScreenController splitScreenController);
+
+ /**
* Creates a window decoration for the given task. Can be {@code null} for Fullscreen tasks but
* not Freeform ones.
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ac5ff2075901..0b0d9d5086f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -64,6 +64,24 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
implements AutoCloseable {
/**
+ * The Z-order of {@link #mCaptionContainerSurface}.
+ * <p>
+ * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
+ * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
+ * prior to caption view itself, treating corner inputs as resize events rather than
+ * repositioning.
+ */
+ static final int CAPTION_LAYER_Z_ORDER = -1;
+ /**
+ * The Z-order of the task input sink in {@link DragPositioningCallback}.
+ * <p>
+ * This task input sink is used to prevent undesired dispatching of motion events out of task
+ * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
+ * input events first.
+ */
+ static final int INPUT_SINK_Z_ORDER = -2;
+
+ /**
* System-wide context. Only used to create context with overridden configurations.
*/
final Context mContext;
@@ -237,7 +255,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = taskBounds.width();
+
startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+ .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
.show(mCaptionContainerSurface);
if (ViewRootImpl.CAPTION_ON_SHELL) {
@@ -248,6 +268,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
wct.addInsetsSource(mTaskInfo.token,
mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+ wct.addInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
+ mCaptionInsetsRect);
} else {
startT.hide(mCaptionContainerSurface);
}
@@ -264,6 +287,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.setColor(mTaskSurface, mTmpColor)
.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
+ .setShadowRadius(mTaskSurface, shadowRadius)
.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
@@ -301,6 +325,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
+ int getCaptionHeightId() {
+ return Resources.ID_NULL;
+ }
+
/**
* Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
* registers {@link #mOnDisplaysChangedListener} if it doesn't.
@@ -345,6 +373,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
wct.removeInsetsSource(mTaskInfo.token,
mOwner, 0 /* index */, WindowInsets.Type.captionBar());
+ wct.removeInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures());
mTaskOrganizer.applyTransaction(wct);
}
@@ -413,6 +443,21 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mSurfaceControlTransactionSupplier);
}
+ /**
+ * Adds caption inset source to a WCT
+ */
+ public void addCaptionInset(WindowContainerTransaction wct) {
+ final int captionHeightId = getCaptionHeightId();
+ if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) {
+ return;
+ }
+
+ final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
+ final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
+ wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(),
+ captionInsets);
+ }
+
static class RelayoutParams {
RunningTaskInfo mRunningTaskInfo;
int mLayoutResId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 1aacb4dd739c..a9eb8829bd2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -28,6 +28,7 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
private val openMenuButton: View = rootView.requireViewById(R.id.open_menu_button)
private val closeWindowButton: ImageButton = rootView.requireViewById(R.id.close_window)
private val expandMenuButton: ImageButton = rootView.requireViewById(R.id.expand_menu_button)
+ private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
@@ -37,6 +38,7 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
openMenuButton.setOnClickListener(onCaptionButtonClickListener)
openMenuButton.setOnTouchListener(onCaptionTouchListener)
closeWindowButton.setOnClickListener(onCaptionButtonClickListener)
+ maximizeWindowButton.setOnClickListener(onCaptionButtonClickListener)
closeWindowButton.setOnTouchListener(onCaptionTouchListener)
appNameTextView.text = appName
appIconImageView.setImageDrawable(appIcon)
@@ -51,6 +53,8 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
closeWindowButton.imageTintList = ColorStateList.valueOf(
getCaptionCloseButtonColor(taskInfo))
+ maximizeWindowButton.imageTintList = ColorStateList.valueOf(
+ getCaptionMaximizeButtonColor(taskInfo))
expandMenuButton.imageTintList = ColorStateList.valueOf(
getCaptionExpandButtonColor(taskInfo))
appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
@@ -72,6 +76,14 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
}
}
+ private fun getCaptionMaximizeButtonColor(taskInfo: RunningTaskInfo): Int {
+ return if (shouldUseLightCaptionColors(taskInfo)) {
+ context.getColor(R.color.desktop_mode_caption_maximize_button_light)
+ } else {
+ context.getColor(R.color.desktop_mode_caption_maximize_button_dark)
+ }
+ }
+
private fun getCaptionExpandButtonColor(taskInfo: RunningTaskInfo): Int {
return if (shouldUseLightCaptionColors(taskInfo)) {
context.getColor(R.color.desktop_mode_caption_expand_button_light)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index b4ecbf5f3e32..49e8d15dcc02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -1,31 +1,38 @@
package com.android.wm.shell.windowdecor.viewholder
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.graphics.Color
import android.view.View
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
/**
* Encapsulates the root [View] of a window decoration and its children to facilitate looking up
* children (via findViewById) and updating to the latest data from [RunningTaskInfo].
*/
internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
- val context: Context = rootView.context
+ val context: Context = rootView.context
- /**
- * A signal to the view holder that new data is available and that the views should be updated
- * to reflect it.
- */
- abstract fun bindData(taskInfo: RunningTaskInfo)
+ /**
+ * A signal to the view holder that new data is available and that the views should be updated to
+ * reflect it.
+ */
+ abstract fun bindData(taskInfo: RunningTaskInfo)
- /**
- * Whether the caption items should use the 'light' color variant so that there's good contrast
- * with the caption background color.
- */
- protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
- return taskInfo.taskDescription
- ?.let { taskDescription ->
- Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
- } ?: false
- }
+ /**
+ * Whether the caption items should use the 'light' color variant so that there's good contrast
+ * with the caption background color.
+ */
+ protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
+ return taskInfo.taskDescription
+ ?.let { taskDescription ->
+ if (Color.alpha(taskDescription.statusBarColor) != 0 &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
+ } else {
+ taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+ }
+ } ?: false
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 82bcec4b63da..b062fbd510ca 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -23,14 +23,67 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-android_test {
- name: "WMShellFlickerTests",
+filegroup {
+ name: "WMShellFlickerTestsUtils-src",
+ srcs: ["src/com/android/wm/shell/flicker/utils/*.kt"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsBase-src",
+ srcs: ["src/com/android/wm/shell/flicker/*.kt"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsBubbles-src",
+ srcs: ["src/com/android/wm/shell/flicker/bubble/*.kt"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip-src",
+ srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsSplitScreen-src",
srcs: [
- "src/**/*.java",
- "src/**/*.kt",
+ "src/com/android/wm/shell/flicker/splitscreen/*.kt",
+ "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerServiceTests-src",
+ srcs: [
+ "src/com/android/wm/shell/flicker/service/**/*.kt",
],
- manifest: "AndroidManifest.xml",
- test_config: "AndroidTest.xml",
+}
+
+java_library {
+ name: "wm-shell-flicker-utils",
+ platform_apis: true,
+ optimize: {
+ enabled: false,
+ },
+ srcs: [
+ ":WMShellFlickerTestsUtils-src",
+ ],
+ static_libs: [
+ "androidx.test.ext.junit",
+ "flickertestapplib",
+ "flickerlib",
+ "flickerlib-helpers",
+ "platform-test-annotations",
+ "wm-flicker-common-app-helpers",
+ "wm-flicker-common-assertions",
+ "launcher-helper-lib",
+ "launcher-aosp-tapl",
+ ],
+}
+
+java_defaults {
+ name: "WMShellFlickerTestsDefault",
+ manifest: "manifests/AndroidManifest.xml",
+ test_config_template: "AndroidTestTemplate.xml",
platform_apis: true,
certificate: "platform",
optimize: {
@@ -39,17 +92,85 @@ android_test {
test_suites: ["device-tests"],
libs: ["android.test.runner"],
static_libs: [
+ "wm-shell-flicker-utils",
"androidx.test.ext.junit",
+ "flickertestapplib",
"flickerlib",
- "flickerlib-apphelpers",
"flickerlib-helpers",
- "truth",
- "app-helpers-core",
+ "platform-test-annotations",
+ "wm-flicker-common-app-helpers",
+ "wm-flicker-common-assertions",
"launcher-helper-lib",
"launcher-aosp-tapl",
- "wm-flicker-common-assertions",
- "wm-flicker-common-app-helpers",
- "platform-test-annotations",
- "flickertestapplib",
+ ],
+ data: [
+ ":FlickerTestApp",
+ "trace_config/*",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsOther",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestOther.xml"],
+ package_name: "com.android.wm.shell.flicker",
+ instrumentation_target_package: "com.android.wm.shell.flicker",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ exclude_srcs: [
+ ":WMShellFlickerTestsBubbles-src",
+ ":WMShellFlickerTestsPip-src",
+ ":WMShellFlickerTestsSplitScreen-src",
+ ":WMShellFlickerServiceTests-src",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsBubbles",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestBubbles.xml"],
+ package_name: "com.android.wm.shell.flicker.bubbles",
+ instrumentation_target_package: "com.android.wm.shell.flicker.bubbles",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsBubbles-src",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestPip.xml"],
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsPip-src",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsSplitScreen",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
+ package_name: "com.android.wm.shell.flicker.splitscreen",
+ instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsSplitScreen-src",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerServiceTests",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestService.xml"],
+ package_name: "com.android.wm.shell.flicker.service",
+ instrumentation_target_package: "com.android.wm.shell.flicker.service",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerServiceTests-src",
],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
deleted file mode 100644
index b5937ae80f0a..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright 2020 Google Inc. All Rights Reserved.
- -->
-<configuration description="Runs WindowManager Shell Flicker Tests">
- <option name="test-tag" value="FlickerTests" />
- <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
- <!-- keeps the screen on during tests -->
- <option name="screen-always-on" value="on" />
- <!-- prevents the phone from restarting -->
- <option name="force-skip-system-props" value="true" />
- <!-- set WM tracing verbose level to all -->
- <option name="run-command" value="cmd window tracing level all" />
- <!-- set WM tracing to frame (avoid incomplete states) -->
- <option name="run-command" value="cmd window tracing frame" />
- <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
- <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
- <!-- ensure lock screen mode is swipe -->
- <option name="run-command" value="locksettings set-disabled false" />
- <!-- restart launcher to activate TAPL -->
- <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
- <!-- Ensure output directory is empty at the start -->
- <option name="run-command" value="rm -rf /sdcard/flicker" />
- <!-- Increase trace size: 20mb for WM and 80mb for SF -->
- <option name="run-command" value="cmd window tracing size 20480" />
- <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
- <option name="run-command" value="settings put system show_touches 1" />
- <option name="run-command" value="settings put system pointer_location 1" />
- <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
- <option name="teardown-command" value="settings delete system show_touches" />
- <option name="teardown-command" value="settings delete system pointer_location" />
- <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="WMShellFlickerTests.apk"/>
- <option name="test-file-name" value="FlickerTestApp.apk" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.wm.shell.flicker"/>
- <option name="shell-timeout" value="6600s" />
- <option name="test-timeout" value="6000s" />
- <option name="hidden-api-checks" value="false" />
- </test>
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/sdcard/flicker" />
- <option name="collect-on-run-ended-only" value="true" />
- <option name="clean-up" value="true" />
- </metrics_collector>
-</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
new file mode 100644
index 000000000000..c8a96377800f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+ <option name="test-tag" value="FlickerTests"/>
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- keeps the screen on during tests -->
+ <option name="screen-always-on" value="on"/>
+ <!-- prevents the phone from restarting -->
+ <option name="force-skip-system-props" value="true"/>
+ <!-- set WM tracing verbose level to all -->
+ <option name="run-command" value="cmd window tracing level all"/>
+ <!-- set WM tracing to frame (avoid incomplete states) -->
+ <option name="run-command" value="cmd window tracing frame"/>
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+ <!-- ensure lock screen mode is swipe -->
+ <option name="run-command" value="locksettings set-disabled false"/>
+ <!-- restart launcher to activate TAPL -->
+ <option name="run-command"
+ value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+ <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+ <option name="run-command" value="cmd window tracing size 20480"/>
+ <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="test-user-token" value="%TEST_USER%"/>
+ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
+ <option name="run-command" value="settings put system show_touches 1"/>
+ <option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="teardown-command"
+ value="settings delete secure show_ime_with_hard_keyboard"/>
+ <option name="teardown-command" value="settings delete system show_touches"/>
+ <option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command"
+ value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="{MODULE}.apk"/>
+ <option name="test-file-name" value="FlickerTestApp.apk"/>
+ </target_preparer>
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file"
+ key="trace_config.textproto"
+ value="/data/misc/perfetto-traces/trace_config.textproto"
+ />
+ <!--Install the content provider automatically when we push some file in sdcard folder.-->
+ <!--Needed to avoid the installation during the test suite.-->
+ <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="{PACKAGE}"/>
+ <option name="shell-timeout" value="6600s"/>
+ <option name="test-timeout" value="6000s"/>
+ <option name="hidden-api-checks" value="false"/>
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+ <option name="instrumentation-arg"
+ key="perfetto_config_file"
+ value="trace_config.textproto"
+ />
+ <option name="instrumentation-arg" key="per_run" value="true"/>
+ </test>
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.server.wm.flicker.service/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
index 4721741611cf..6a87de47def4 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
@@ -15,6 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="com.android.wm.shell.flicker">
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
@@ -57,10 +58,11 @@
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
- </application>
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker"
- android:label="WindowManager Shell Flicker Tests">
- </instrumentation>
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+ </application>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
new file mode 100644
index 000000000000..437871f1bb8f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.flicker.bubble">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.bubble"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
new file mode 100644
index 000000000000..cf642f63a41d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.flicker">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
new file mode 100644
index 000000000000..5a8155a66d30
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.flicker.pip">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.pip"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
new file mode 100644
index 000000000000..c7aca1a72696
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.flicker.service">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.service"
+ android:label="WindowManager Flicker Service Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
new file mode 100644
index 000000000000..887d8db3042f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.flicker.splitscreen">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.splitscreen"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
index e06e074ee98a..0f3e0f5ef043 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
@@ -19,14 +19,14 @@ package com.android.wm.shell.flicker
import android.app.Instrumentation
import android.tools.device.flicker.junit.FlickerBuilderProvider
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
abstract class BaseBenchmarkTest
@JvmOverloads
constructor(
- protected open val flicker: FlickerTest,
+ protected open val flicker: LegacyFlickerTest,
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index c98c5a0ad1a6..735fbfb341f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -18,9 +18,10 @@ package com.android.wm.shell.flicker
import android.app.Instrumentation
import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.utils.ICommonAssertions
/**
* Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR],
@@ -30,7 +31,7 @@ import com.android.launcher3.tapl.LauncherInstrumentation
abstract class BaseTest
@JvmOverloads
constructor(
- override val flicker: FlickerTest,
+ override val flicker: LegacyFlickerTest,
instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
tapl: LauncherInstrumentation = LauncherInstrumentation()
) : BaseBenchmarkTest(flicker, instrumentation, tapl), ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 61781565270b..36acb58d6139 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -17,28 +17,29 @@
package com.android.wm.shell.flicker.appcompat
import android.content.Context
-import android.system.helpers.CommandsHelper
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import com.android.server.wm.flicker.helpers.setRotation
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import com.android.server.wm.flicker.helpers.LetterboxAppHelper
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.legacy.IFlickerTestData
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerKeepVisible
-import org.junit.After
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.utils.appWindowKeepVisible
+import com.android.wm.shell.flicker.utils.layerKeepVisible
+
import org.junit.Assume
import org.junit.Before
-import org.junit.runners.Parameterized
+import org.junit.Rule
-abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
protected val letterboxApp = LetterboxAppHelper(instrumentation)
- lateinit var cmdHelper: CommandsHelper
- private lateinit var letterboxStyle: HashMap<String, String>
+
+ @JvmField
+ @Rule
+ val letterboxRule: LetterboxRule = LetterboxRule()
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -48,62 +49,17 @@ abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) {
letterboxApp.launchViaIntent(wmHelper)
setEndRotation()
}
- teardown {
- letterboxApp.exit(wmHelper)
- }
+ teardown { letterboxApp.exit(wmHelper) }
}
@Before
fun before() {
- cmdHelper = CommandsHelper.getInstance(instrumentation)
- Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest())
- letterboxStyle = mapLetterboxStyle()
- setLetterboxEducationEnabled(false)
+ Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
}
- @After
- fun after() {
- resetLetterboxEducationEnabled()
- }
+ fun FlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation)
- private fun mapLetterboxStyle(): HashMap<String, String> {
- val res = cmdHelper.executeShellCommand("wm get-letterbox-style")
- val lines = res.lines()
- val map = HashMap<String, String>()
- for (line in lines) {
- val keyValuePair = line.split(":")
- if (keyValuePair.size == 2) {
- val key = keyValuePair[0].trim()
- map[key] = keyValuePair[1].trim()
- }
- }
- return map
- }
-
- private fun getLetterboxStyle(): HashMap<String, String> {
- if (!::letterboxStyle.isInitialized) {
- letterboxStyle = mapLetterboxStyle()
- }
- return letterboxStyle
- }
-
- private fun resetLetterboxEducationEnabled() {
- val enabled = getLetterboxStyle().getValue("Is education enabled")
- cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
- }
-
- private fun setLetterboxEducationEnabled(enabled: Boolean) {
- cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
- }
-
- private fun isIgnoreOrientationRequest(): Boolean {
- val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request")
- return res != null && res.contains("true")
- }
-
- fun IFlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation)
-
- fun IFlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation)
+ fun FlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation)
/** Checks that app entering letterboxed state have rounded corners */
fun assertLetterboxAppAtStartHasRoundedCorners() {
@@ -118,7 +74,7 @@ abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) {
/** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */
private fun assumeLetterboxRoundedCornersEnabled() {
- Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0")
+ Assume.assumeTrue(letterboxRule.hasCornerRadius)
}
fun assertLetterboxAppVisibleAtStartAndEnd() {
@@ -126,25 +82,21 @@ abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) {
flicker.appWindowIsVisibleAtEnd(letterboxApp)
}
+ fun assertLetterboxAppKeepVisible() {
+ assertLetterboxAppWindowKeepVisible()
+ assertLetterboxAppLayerKeepVisible()
+ }
+
fun assertAppLetterboxedAtEnd() =
- flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) }
+ flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) }
fun assertAppLetterboxedAtStart() =
- flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) }
+ flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) }
+
+ fun assertAppStaysLetterboxed() =
+ flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
fun assertLetterboxAppLayerKeepVisible() = flicker.layerKeepVisible(letterboxApp)
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
- * navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTest> {
- return FlickerTestFactory.rotationTests()
- }
- }
+ fun assertLetterboxAppWindowKeepVisible() = flicker.appWindowKeepVisible(letterboxApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
new file mode 100644
index 000000000000..5a1136f97c6f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.app.Instrumentation
+import android.system.helpers.CommandsHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * JUnit Rule to handle letterboxStyles and states
+ */
+class LetterboxRule(
+ private val withLetterboxEducationEnabled: Boolean = false,
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+) : TestRule {
+
+ private val execAdb: (String) -> String = {cmd -> cmdHelper.executeShellCommand(cmd)}
+ private lateinit var _letterboxStyle: MutableMap<String, String>
+
+ val letterboxStyle: Map<String, String>
+ get() {
+ if (!::_letterboxStyle.isInitialized) {
+ _letterboxStyle = mapLetterboxStyle()
+ }
+ return _letterboxStyle
+ }
+
+ val cornerRadius: Int?
+ get() = asInt(letterboxStyle["Corner radius"])
+
+ val hasCornerRadius: Boolean
+ get() {
+ val radius = cornerRadius
+ return radius != null && radius > 0
+ }
+
+ val isIgnoreOrientationRequest: Boolean
+ get() = execAdb("wm get-ignore-orientation-request")?.contains("true") ?: false
+
+ override fun apply(base: Statement?, description: Description?): Statement {
+ resetLetterboxStyle()
+ _letterboxStyle = mapLetterboxStyle()
+ val isLetterboxEducationEnabled = _letterboxStyle.getValue("Is education enabled")
+ var hasLetterboxEducationStateChanged = false
+ if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) {
+ hasLetterboxEducationStateChanged = true
+ execAdb("wm set-letterbox-style --isEducationEnabled " +
+ withLetterboxEducationEnabled)
+ }
+ return try {
+ object : Statement() {
+ @Throws(Throwable::class)
+ override fun evaluate() {
+ base!!.evaluate()
+ }
+ }
+ } finally {
+ if (hasLetterboxEducationStateChanged) {
+ execAdb("wm set-letterbox-style --isEducationEnabled " +
+ isLetterboxEducationEnabled
+ )
+ }
+ resetLetterboxStyle()
+ }
+ }
+
+ private fun mapLetterboxStyle(): HashMap<String, String> {
+ val res = execAdb("wm get-letterbox-style")
+ val lines = res.lines()
+ val map = HashMap<String, String>()
+ for (line in lines) {
+ val keyValuePair = line.split(":")
+ if (keyValuePair.size == 2) {
+ val key = keyValuePair[0].trim()
+ map[key] = keyValuePair[1].trim()
+ }
+ }
+ return map
+ }
+
+ private fun resetLetterboxStyle() {
+ execAdb("wm reset-letterbox-style")
+ }
+
+ private fun asInt(str: String?): Int? = try {
+ str?.toInt()
+ } catch (e: NumberFormatException) {
+ null
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index c2141a370f10..67d5718e6c1f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -17,10 +17,12 @@
package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
+import android.tools.common.flicker.assertions.FlickerTest
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import org.junit.Test
import org.junit.runner.RunWith
@@ -29,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test launching app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTests:OpenAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest`
*
* Actions:
* ```
@@ -45,7 +47,7 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) {
+class OpenAppInSizeCompatModeTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -70,9 +72,7 @@ class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker)
@Test
fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners()
- @Postsubmit
- @Test
- fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd()
+ @Postsubmit @Test fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd()
/**
* Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
@@ -90,4 +90,18 @@ class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker)
.isInvisible(ComponentNameMatcher.ROTATION)
}
}
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory.rotationTests()
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
new file mode 100644
index 000000000000..e6ca261a317f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching app in size compat mode.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest`
+ *
+ * Actions:
+ * ```
+ * Launch a letteboxed app and then a transparent activity from it. We test the bounds
+ * are the same.
+ * ```
+ *
+ * Notes:
+ * ```
+ * Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [BaseTest]
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseAppCompat(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ letterboxTranslucentLauncherApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found")
+ }
+ teardown {
+ letterboxTranslucentApp.exit(wmHelper)
+ letterboxTranslucentLauncherApp.exit(wmHelper)
+ }
+ }
+
+ /**
+ * Checks the transparent activity is launched on top of the opaque one
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() {
+ flicker.assertWm {
+ this.isAppWindowOnTop(letterboxTranslucentLauncherApp)
+ .then()
+ .isAppWindowOnTop(letterboxTranslucentApp)
+ }
+ }
+
+ /**
+ * Checks that the activity is letterboxed
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityIsLetterboxed() {
+ flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
+ }
+
+ /**
+ * Checks that the translucent activity inherits bounds from the opaque one.
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityInheritsBoundsFromOpaqueActivity() {
+ flicker.assertLayersEnd {
+ this.visibleRegion(letterboxTranslucentApp)
+ .coversExactly(visibleRegion(letterboxTranslucentLauncherApp).region)
+ }
+ }
+
+ /**
+ * Checks that the translucent activity has rounded corners
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityHasRoundedCorners() {
+ flicker.assertLayersEnd {
+ this.hasRoundedCorners(letterboxTranslucentApp)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory
+ .nonRotationTests(supportedRotations = listOf(Rotation.ROTATION_90))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
new file mode 100644
index 000000000000..d3f3c5b7c672
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switching to letterboxed app from launcher
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest`
+ *
+ * Actions:
+ * ```
+ * Launch a letterboxed app
+ * Navigate home to show launcher
+ * Swipe right from the bottom of the screen to quick switch back to the app
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) :
+ BaseAppCompat(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setExpectedRotationCheckEnabled(false)
+
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+
+ letterboxApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper
+ .StateSyncBuilder()
+ .withHomeActivityVisible()
+ .withWindowSurfaceDisappeared(letterboxApp)
+ .waitForAndVerify()
+
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+ }
+ transitions {
+ tapl.workspace.quickSwitchToPreviousApp()
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(letterboxApp)
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+ teardown { letterboxApp.exit(wmHelper) }
+ }
+
+ /**
+ * Checks that [letterboxApp] is the top window at the end of the transition once we have fully
+ * quick switched from the launcher back to the [letterboxApp].
+ */
+ @Postsubmit
+ @Test
+ fun endsWithAppBeingOnTop() {
+ flicker.assertWmEnd { this.isAppWindowOnTop(letterboxApp) }
+ }
+
+ /** Checks that the transition starts with the home activity being tagged as visible. */
+ @Postsubmit
+ @Test
+ fun startsWithHomeActivityFlaggedVisible() {
+ flicker.assertWmStart { this.isHomeActivityVisible() }
+ }
+
+ /**
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows
+ * filling/covering exactly display size
+ */
+ @Postsubmit
+ @Test
+ fun startsWithLauncherWindowsCoverFullScreen() {
+ flicker.assertWmStart {
+ this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers
+ * filling/covering exactly the display size.
+ */
+ @Postsubmit
+ @Test
+ fun startsWithLauncherLayersCoverFullScreen() {
+ flicker.assertLayersStart {
+ this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top
+ * window.
+ */
+ @Postsubmit
+ @Test
+ fun startsWithLauncherBeingOnTop() {
+ flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
+ }
+
+ /**
+ * Checks that the transition ends with the home activity being flagged as not visible. By this
+ * point we should have quick switched away from the launcher back to the [letterboxApp].
+ */
+ @Postsubmit
+ @Test
+ fun endsWithHomeActivityFlaggedInvisible() {
+ flicker.assertWmEnd { this.isHomeActivityInvisible() }
+ }
+
+ /**
+ * Checks that [letterboxApp]'s window starts off invisible and becomes visible at some point
+ * before the end of the transition and then stays visible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun appWindowBecomesAndStaysVisible() {
+ flicker.assertWm {
+ this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp)
+ }
+ }
+
+ /**
+ * Checks that [letterboxApp]'s layer starts off invisible and becomes visible at some point
+ * before the end of the transition and then stays visible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun appLayerBecomesAndStaysVisible() {
+ flicker.assertLayers { this.isInvisible(letterboxApp).then().isVisible(letterboxApp) }
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes
+ * invisible at some point before the end of the transition and then stays invisible until the
+ * end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun launcherWindowBecomesAndStaysInvisible() {
+ flicker.assertWm {
+ this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
+ }
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes
+ * invisible at some point before the end of the transition and then stays invisible until the
+ * end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun launcherLayerBecomesAndStaysInvisible() {
+ flicker.assertLayers {
+ this.isVisible(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isInvisible(ComponentNameMatcher.LAUNCHER)
+ }
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app
+ * window is visible. Ensures that at any point, either the launcher or [letterboxApp] windows
+ * are at least partially visible.
+ */
+ @Postsubmit
+ @Test
+ fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
+ flicker.assertWm {
+ this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+ .then()
+ .isAppWindowVisible(letterboxApp)
+ }
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer
+ * is visible. Ensures that at any point, either the launcher or [letterboxApp] layers are at
+ * least partially visible.
+ */
+ @Postsubmit
+ @Test
+ fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
+ flicker.assertLayers {
+ this.isVisible(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+ .then()
+ .isVisible(letterboxApp)
+ }
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.LETTERBOX] layer is visible as soon as the
+ * [letterboxApp] layer is visible at the end of the transition once we have fully quick
+ * switched from the launcher back to the [letterboxApp].
+ */
+ @Postsubmit
+ @Test
+ fun appAndLetterboxLayersBothVisibleOnceLauncherIsInvisible() {
+ flicker.assertLayers {
+ this.isVisible(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+ .then()
+ .isVisible(letterboxApp)
+ .isVisible(ComponentNameMatcher.LETTERBOX)
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ companion object {
+ /** {@inheritDoc} */
+ private var startDisplayBounds = Rect.EMPTY
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
+ supportedRotations = listOf(Rotation.ROTATION_90)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
new file mode 100644
index 000000000000..68fa8d2fc2e8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.RequiresDevice
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a fixed portrait letterboxed app in landscape and repositioning to the right.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest`
+ *
+ * Actions:
+ *
+ * ```
+ * Launch a fixed portrait app in landscape to letterbox app
+ * Double tap to the right to reposition app and wait for app to move
+ * ```
+ *
+ * Notes:
+ *
+ * ```
+ * Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [BaseTest]
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class RepositionFixedPortraitAppTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
+
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation).bounds
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ setStartRotation()
+ letterboxApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ letterboxApp.repositionHorizontally(displayBounds, true)
+ letterboxApp.waitForAppToMoveHorizontallyTo(wmHelper, displayBounds, true)
+ }
+ teardown {
+ letterboxApp.repositionHorizontally(displayBounds, false)
+ letterboxApp.exit(wmHelper)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners()
+
+ @Postsubmit @Test fun letterboxAppLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
+
+ @Postsubmit @Test fun appStaysLetterboxed() = assertAppStaysLetterboxed()
+
+ @Postsubmit @Test fun appKeepVisible() = assertLetterboxAppKeepVisible()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_90)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index b0e1a42306df..fcb6931af9a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -17,9 +17,11 @@
package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
+import android.tools.common.flicker.assertions.FlickerTest
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
import androidx.test.filters.RequiresDevice
import org.junit.Test
@@ -29,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test restarting app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTests:RestartAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest`
*
* Actions:
* ```
@@ -46,7 +48,7 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) {
+class RestartAppInSizeCompatModeTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -69,13 +71,9 @@ class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flick
}
}
- @Postsubmit
- @Test
- fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
+ @Postsubmit @Test fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
- @Postsubmit
- @Test
- fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart()
+ @Postsubmit @Test fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart()
@Postsubmit
@Test
@@ -88,4 +86,18 @@ class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flick
val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
flicker.assertLayersEnd { visibleRegion(letterboxApp).coversAtMost(displayBounds) }
}
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory.rotationTests()
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
new file mode 100644
index 000000000000..ea0392cee95a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.content.Context
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.helpers.FIND_TIMEOUT
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.BaseTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+
+abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
+ protected val context: Context = instrumentation.context
+ protected val letterboxTranslucentLauncherApp = LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
+ component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
+ )
+ protected val letterboxTranslucentApp = LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.TransparentActivity.LABEL,
+ component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
+ )
+
+ @JvmField
+ @Rule
+ val letterboxRule: LetterboxRule = LetterboxRule()
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
+ }
+
+ protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? =
+ device.wait(
+ Until.findObject(By.text("Launch Transparent")),
+ FIND_TIMEOUT
+ )
+
+ protected fun FlickerTestData.goBack() = device.pressBack()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index bab81d79c804..5c7d1d8df2e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -23,9 +23,9 @@ import android.content.pm.PackageManager
import android.os.ServiceManager
import android.tools.common.Rotation
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.SYSTEMUI_PACKAGE
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
@@ -35,7 +35,7 @@ import com.android.wm.shell.flicker.BaseTest
import org.junit.runners.Parameterized
/** Base configurations for Bubble flicker tests */
-abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
protected val testApp = LaunchBubbleHelper(instrumentation)
@@ -72,28 +72,31 @@ abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) {
uid,
NotificationManager.BUBBLE_PREFERENCE_NONE
)
- testApp.exit()
+ device.wait(
+ Until.gone(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
+ FIND_OBJECT_TIMEOUT
+ )
+ testApp.exit(wmHelper)
}
extraSpec(this)
}
}
- protected fun IFlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? =
+ protected fun FlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? =
device.wait(Until.findObject(By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
- protected fun IFlickerTestData.waitAndGetCancelAllBtn(): UiObject2? =
+ protected fun FlickerTestData.waitAndGetCancelAllBtn(): UiObject2? =
device.wait(Until.findObject(By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
- const val FIND_OBJECT_TIMEOUT = 2000L
+ const val FIND_OBJECT_TIMEOUT = 4000L
const val SYSTEM_UI_PACKAGE = SYSTEMUI_PACKAGE
const val BUBBLE_RES_NAME = "bubble_view"
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index 2474ecf74cf9..bc565bc5fd42 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -21,7 +21,7 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
@@ -44,7 +44,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FlakyTest(bugId = 217777115)
-open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class ChangeActiveActivityFromBubbleTest(flicker: LegacyFlickerTest) :
+ BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
index bdfdad59c600..abc6b9f9a746 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.bubble
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class ChangeActiveActivityFromBubbleTestCfArm(flicker: FlickerTest) :
+open class ChangeActiveActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) :
ChangeActiveActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 8474ce0e64e5..3f28ae848d1f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -19,9 +19,10 @@ package com.android.wm.shell.flicker.bubble
import android.content.Context
import android.graphics.Point
import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.test.filters.RequiresDevice
@@ -44,7 +45,7 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class DragToDismissBubbleScreenTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val displaySize = DisplayMetrics()
@@ -73,4 +74,14 @@ open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScree
open fun testAppIsAlwaysVisible() {
flicker.assertLayers { this.isVisible(testApp) }
}
+
+ @Presubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ flicker.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(testApp)
+ )
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
index 62fa7b4516c7..ee55eca31072 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.bubble
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class DragToDismissBubbleScreenTestCfArm(flicker: FlickerTest) :
+class DragToDismissBubbleScreenTestCfArm(flicker: LegacyFlickerTest) :
DragToDismissBubbleScreenTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 889e1771593d..26aca1830889 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -21,7 +21,7 @@ import android.platform.test.annotations.Postsubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.view.WindowInsets
import android.view.WindowManager
import androidx.test.filters.RequiresDevice
@@ -48,7 +48,8 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class OpenActivityFromBubbleOnLocksreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+class OpenActivityFromBubbleOnLocksreenTest(flicker: LegacyFlickerTest) :
+ BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -80,7 +81,7 @@ class OpenActivityFromBubbleOnLocksreenTest(flicker: FlickerTest) : BaseBubbleSc
instrumentation.uiAutomation.syncInputTransactions()
val showBubble =
device.wait(
- Until.findObject(By.res("com.android.systemui", "bubble_view")),
+ Until.findObject(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
FIND_OBJECT_TIMEOUT
)
showBubble?.click() ?: error("Bubble notify not found")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
index 07ba41333071..508539411aa0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
@@ -42,7 +42,7 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class OpenActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class OpenActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
index 6c61710d6284..6a46d23ad2a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
@@ -17,10 +17,11 @@
package com.android.wm.shell.flicker.bubble
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class OpenActivityFromBubbleTestCfArm(flicker: FlickerTest) : OpenActivityFromBubbleTest(flicker)
+class OpenActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) :
+ OpenActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
index 29f76d01af83..a926bb7d85c3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
@@ -41,7 +41,7 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class SendBubbleNotificationTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -55,6 +55,7 @@ open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(f
FIND_OBJECT_TIMEOUT
)
?: error("No bubbles found")
+ device.waitForIdle()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
index e323ebf3b5c8..a401cb494822 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
@@ -17,11 +17,11 @@
package com.android.wm.shell.flicker.bubble
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class SendBubbleNotificationTestCfArm(flicker: FlickerTest) :
+open class SendBubbleNotificationTestCfArm(flicker: LegacyFlickerTest) :
SendBubbleNotificationTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index f7ce87088040..c335d3dc7f4b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -16,26 +16,19 @@
package com.android.wm.shell.flicker.pip
-import android.app.Instrumentation
-import android.os.SystemClock
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.common.Rotation
-import android.tools.device.apphelpers.StandardAppHelper
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
import android.tools.device.traces.parsers.toFlickerComponent
import androidx.test.filters.RequiresDevice
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -69,17 +62,16 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) :
- AutoEnterPipOnGoToHomeTest(flicker) {
+class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) :
+ AutoEnterPipOnGoToHomeTest(flicker) {
private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
/** Second app used to enter split screen mode */
- protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation)
- fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper =
- SimpleAppHelper(
- instrumentation,
- ActivityOptions.SplitScreen.Primary.LABEL,
- ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
- )
+ private val secondAppForSplitScreen =
+ SimpleAppHelper(
+ instrumentation,
+ ActivityOptions.SplitScreen.Primary.LABEL,
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+ )
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
@@ -88,14 +80,7 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) :
secondAppForSplitScreen.launchViaIntent(wmHelper)
pipApp.launchViaIntent(wmHelper)
tapl.goHome()
- enterSplitScreen()
- // wait until split screen is established
- wmHelper
- .StateSyncBuilder()
- .withWindowSurfaceAppeared(pipApp)
- .withWindowSurfaceAppeared(secondAppForSplitScreen)
- .withSplitDividerVisible()
- .waitForAndVerify()
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen)
pipApp.enableAutoEnterForPipActivity()
}
teardown {
@@ -107,46 +92,6 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) :
transitions { tapl.goHome() }
}
- // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils
- private val TIMEOUT_MS = 3_000L
- private val overviewSnapshotSelector: BySelector
- get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot")
- private fun enterSplitScreen() {
- // Note: The initial split position in landscape is different between tablet and phone.
- // In landscape, tablet will let the first app split to right side, and phone will
- // split to left side.
- if (tapl.isTablet) {
- // TAPL's currentTask on tablet is sometimes not what we expected if the overview
- // contains more than 3 task views. We need to use uiautomator directly to find the
- // second task to split.
- tapl.workspace.switchToOverview().overviewActions.clickSplit()
- val snapshots = tapl.device.wait(Until.findObjects(overviewSnapshotSelector),
- TIMEOUT_MS)
- if (snapshots == null || snapshots.size < 1) {
- error("Fail to find a overview snapshot to split.")
- }
-
- // Find the second task in the upper right corner in split select mode by sorting
- // 'left' in descending order and 'top' in ascending order.
- snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
- t2.getVisibleBounds().left - t1.getVisibleBounds().left
- }
- snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
- t1.getVisibleBounds().top - t2.getVisibleBounds().top
- }
- snapshots[0].click()
- } else {
- tapl.workspace
- .switchToOverview()
- .currentTask
- .tapMenu()
- .tapSplitMenuItem()
- .currentTask
- .open()
- }
- SystemClock.sleep(TIMEOUT_MS)
- }
-
@Presubmit
@Test
override fun pipOverlayLayerAppearThenDisappear() {
@@ -190,11 +135,10 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
- // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index b95732e43357..2f7a25ea586d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import org.junit.Assume
import org.junit.FixMethodOrder
@@ -53,10 +53,9 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
- override val thisTransition: FlickerBuilder.() -> Unit = {
- transitions { tapl.goHome() }
- }
+open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
+ EnterPipViaAppUiButtonTest(flicker) {
+ override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index afcc1729ed16..68bc9a28967e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -20,7 +20,7 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import org.junit.FixMethodOrder
import org.junit.Test
@@ -53,7 +53,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipBySwipingDownTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
+open class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions {
val pipRegion = wmHelper.getWindowRegion(pipApp).bounds
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
index 02f60100d069..7a668897fbbe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
@@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -28,20 +28,20 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ClosePipBySwipingDownTestCfArm(flicker: FlickerTest) : ClosePipBySwipingDownTest(flicker) {
+class ClosePipBySwipingDownTestCfArm(flicker: LegacyFlickerTest) :
+ ClosePipBySwipingDownTest(flicker) {
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
- * and navigation modes.
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
index e52b71e602f9..a17144b7cef3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
@@ -20,14 +20,14 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.common.traces.component.ComponentNameMatcher.Companion.LAUNCHER
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.helpers.setRotation
import org.junit.Test
import org.junit.runners.Parameterized
/** Base class for exiting pip (closing pip window) without returning to the app */
-abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class ClosePipTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
setup { this.setRotation(flicker.scenario.startRotation) }
teardown { this.setRotation(Rotation.ROTATION_0) }
@@ -74,15 +74,14 @@ abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker)
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
- * and navigation modes.
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 86fe583c94e6..dc48696f3197 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import org.junit.FixMethodOrder
import org.junit.Test
@@ -53,7 +53,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipWithDismissButtonTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
+open class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.closePipWindow(wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
index 05262feceba5..718b14babc4f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
@@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -28,21 +28,20 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipWithDismissButtonTestCfArm(flicker: FlickerTest) :
+open class ClosePipWithDismissButtonTestCfArm(flicker: LegacyFlickerTest) :
ClosePipWithDismissButtonTest(flicker) {
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
- * and navigation modes.
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 01d67cc35a14..5e392628aa6a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import org.junit.Assume
import org.junit.FixMethodOrder
@@ -44,10 +44,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
- override val thisTransition: FlickerBuilder.() -> Unit = {
- transitions { tapl.goHome() }
- }
+open class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+ override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
index 90f99c0c4cae..2b3e76a964c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
@@ -17,7 +17,7 @@
package com.android.wm.shell.flicker.pip
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -27,4 +27,5 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipOnUserLeaveHintTestCfArm(flicker: FlickerTest) : EnterPipOnUserLeaveHintTest(flicker)
+class EnterPipOnUserLeaveHintTestCfArm(flicker: LegacyFlickerTest) :
+ EnterPipOnUserLeaveHintTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 5480144ba1ce..ec35837bc8dd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -21,11 +21,12 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.entireScreenCovered
@@ -68,15 +69,13 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flicker) {
+open class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val testApp = FixedOrientationAppHelper(instrumentation)
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
override val thisTransition: FlickerBuilder.() -> Unit = {
- teardown {
- testApp.exit(wmHelper)
- }
+ teardown { testApp.exit(wmHelper) }
transitions {
// Enter PiP, and assert that the PiP is within bounds now that the device is back
// in portrait
@@ -95,14 +94,13 @@ open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flic
setup {
// Launch a portrait only app on the fullscreen stack
testApp.launchViaIntent(
- wmHelper,
- stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
+ wmHelper,
+ stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
)
// Launch the PiP activity fixed as landscape, but don't enter PiP
pipApp.launchViaIntent(
- wmHelper,
- stringExtras =
- mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+ wmHelper,
+ stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
)
}
}
@@ -207,13 +205,13 @@ open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flic
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ return LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
index 58416660826f..92642197e9be 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
@@ -17,9 +17,10 @@
package com.android.wm.shell.flicker.pip
import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -29,19 +30,19 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipToOtherOrientationCfArm(flicker: FlickerTest) :
+open class EnterPipToOtherOrientationCfArm(flicker: LegacyFlickerTest) :
EnterPipToOtherOrientation(flicker) {
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ return LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index cdbdb85a9195..6d20740e239c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -20,16 +20,14 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import org.junit.Test
import org.junit.runners.Parameterized
-abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
- setup {
- pipApp.launchViaIntent(wmHelper)
- }
+ setup { pipApp.launchViaIntent(wmHelper) }
}
/** Checks [pipApp] window remains visible throughout the animation */
@@ -126,15 +124,14 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker)
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
- * and navigation modes.
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index 95725b64a48a..76c811cbbeea 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.pip
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -50,7 +50,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipViaAppUiButtonTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
+open class EnterPipViaAppUiButtonTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.clickEnterPipButton(wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
index 4390f0bb70b2..78e80497747c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
@@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -28,20 +28,20 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipViaAppUiButtonTestCfArm(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
+class EnterPipViaAppUiButtonTestCfArm(flicker: LegacyFlickerTest) :
+ EnterPipViaAppUiButtonTest(flicker) {
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
- * and navigation modes.
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 5ac9829b6c8f..dfffba831dc3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -19,14 +19,14 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import org.junit.Test
import org.junit.runners.Parameterized
/** Base class for pip expand tests */
-abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class ExitPipToAppTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
protected val testApp = SimpleAppHelper(instrumentation)
/**
@@ -130,15 +130,14 @@ abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flic
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 0b3d16a8087d..b80b7483ba4d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.pip
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -52,7 +52,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
+open class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
+ ExitPipToAppTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
setup {
// launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
index eccb85d98798..e25c0d6eddc0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
@@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -28,21 +28,20 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipToAppViaExpandButtonTestCfArm(flicker: FlickerTest) :
+class ExitPipToAppViaExpandButtonTestCfArm(flicker: LegacyFlickerTest) :
ExitPipToAppViaExpandButtonTest(flicker) {
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index bb2d40becdc9..f003ed8a77e0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.pip
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -51,7 +51,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
+open class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
setup {
// launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
index 6ab6a1f0bb73..be19f3cd1970 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
@@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -28,20 +28,20 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipToAppViaIntentTestCfArm(flicker: FlickerTest) : ExitPipToAppViaIntentTest(flicker) {
+class ExitPipToAppViaIntentTestCfArm(flicker: LegacyFlickerTest) :
+ ExitPipToAppViaIntentTest(flicker) {
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
- * and navigation modes.
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index fd16b6ea6ada..a1d3a117482e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -21,8 +21,8 @@ import android.tools.common.Rotation
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import org.junit.FixMethodOrder
import org.junit.Test
@@ -55,7 +55,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) {
+open class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.doubleClickPipWindow(wmHelper) }
}
@@ -142,15 +142,14 @@ open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flic
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
index c09623490041..3095cac94598 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
@@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -28,21 +28,20 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnDoubleClickTestTestCfArm(flicker: FlickerTest) :
+class ExpandPipOnDoubleClickTestTestCfArm(flicker: LegacyFlickerTest) :
ExpandPipOnDoubleClickTest(flicker) {
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index 253aa4cae5c7..8c8d280aea9a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -20,8 +20,8 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import org.junit.FixMethodOrder
import org.junit.Test
@@ -34,7 +34,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
+open class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) }
}
@@ -55,15 +55,14 @@ open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicke
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
index e064bf2ee921..1a1ce6823f3b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
@@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -28,20 +28,20 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnPinchOpenTestCfArm(flicker: FlickerTest) : ExpandPipOnPinchOpenTest(flicker) {
+class ExpandPipOnPinchOpenTestCfArm(flicker: LegacyFlickerTest) :
+ ExpandPipOnPinchOpenTest(flicker) {
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 094060f86691..421ad757f76a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -19,9 +19,9 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.Direction
+import com.android.wm.shell.flicker.utils.Direction
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,7 +55,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipDownOnShelfHeightChange(flicker: FlickerTest) : MovePipShelfHeightTransition(flicker) {
+class MovePipDownOnShelfHeightChange(flicker: LegacyFlickerTest) :
+ MovePipShelfHeightTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
teardown { testApp.exit(wmHelper) }
transitions { testApp.launchViaIntent(wmHelper) }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index ff51c27bf116..dffc822e7aec 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -18,11 +18,12 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.ImeAppHelper
@@ -38,7 +39,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) {
+open class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val imeApp = ImeAppHelper(instrumentation)
override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -80,7 +81,7 @@ open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransitio
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ return LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
index d3d77d20662e..63292a4f2ca3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
@@ -17,9 +17,10 @@
package com.android.wm.shell.flicker.pip
import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -28,7 +29,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipOnImeVisibilityChangeTestCfArm(flicker: FlickerTest) :
+class MovePipOnImeVisibilityChangeTestCfArm(flicker: LegacyFlickerTest) :
MovePipOnImeVisibilityChangeTest(flicker) {
companion object {
private const val TAG_IME_VISIBLE = "imeIsVisible"
@@ -36,7 +37,7 @@ class MovePipOnImeVisibilityChangeTestCfArm(flicker: FlickerTest) :
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ return LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index 109354ab5c79..a8fb63de244b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -19,15 +19,15 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.common.flicker.subject.region.RegionSubject
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
-import com.android.wm.shell.flicker.Direction
+import com.android.wm.shell.flicker.utils.Direction
import org.junit.Test
import org.junit.runners.Parameterized
/** Base class for pip tests with Launcher shelf height change */
-abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class MovePipShelfHeightTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
protected val testApp = FixedOrientationAppHelper(instrumentation)
/** Checks [pipApp] window remains visible throughout the animation */
@@ -111,15 +111,14 @@ abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransitio
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 27b061b67a85..992f1bc4ace3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -19,9 +19,9 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.Direction
+import com.android.wm.shell.flicker.utils.Direction
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,14 +55,13 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class MovePipUpOnShelfHeightChangeTest(flicker: FlickerTest) :
+open class MovePipUpOnShelfHeightChangeTest(flicker: LegacyFlickerTest) :
MovePipShelfHeightTransition(flicker) {
- override val thisTransition: FlickerBuilder.() -> Unit =
- {
- setup { testApp.launchViaIntent(wmHelper) }
- transitions { tapl.pressHome() }
- teardown { testApp.exit(wmHelper) }
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup { testApp.launchViaIntent(wmHelper) }
+ transitions { tapl.pressHome() }
+ teardown { testApp.exit(wmHelper) }
+ }
/** Checks that the visible region of [pipApp] window always moves up during the animation. */
@Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 9f81ba8eee87..0c6fc5636a5b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -16,12 +16,12 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import org.junit.FixMethodOrder
import org.junit.Test
@@ -34,7 +34,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) {
+class PipDragTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private var isDraggedLeft: Boolean = true
override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -62,7 +62,7 @@ class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) {
}
}
- @Postsubmit
+ @Presubmit
@Test
fun pipLayerMovesAwayFromEdge() {
flicker.assertLayers {
@@ -81,13 +81,11 @@ class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index 9fe9f52fd4af..de64f78a31eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.graphics.Rect
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.setRotation
@@ -38,7 +38,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) {
+class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
// represents the direction in which the pip window should be snapping
private var willSnapRight: Boolean = true
@@ -80,7 +80,7 @@ class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) {
/**
* Checks that the visible region area of [pipApp] moves to closest edge during the animation.
*/
- @Postsubmit
+ @Presubmit
@Test
fun pipLayerMovesToClosestEdge() {
flicker.assertLayers {
@@ -99,15 +99,14 @@ class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 60bf5ffdc7af..0295741bc441 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -16,13 +16,12 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import org.junit.FixMethodOrder
import org.junit.Test
@@ -35,14 +34,13 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 270677470)
-class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
+class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
}
/** Checks that the visible region area of [pipApp] always decreases during the animation. */
- @Postsubmit
+ @Presubmit
@Test
fun pipLayerAreaDecreases() {
flicker.assertLayers {
@@ -57,15 +55,14 @@ class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index 17a178f78de3..096af39488e9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -22,7 +22,7 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import android.tools.device.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.PipAppHelper
@@ -32,7 +32,7 @@ import com.android.wm.shell.flicker.BaseTest
import com.google.common.truth.Truth
import org.junit.Test
-abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
protected val pipApp = PipAppHelper(instrumentation)
protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
@@ -78,16 +78,16 @@ abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) {
/** Defines the default method of entering PiP */
protected open val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
- pipApp.launchViaIntentAndWaitForPip(wmHelper,
- stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"))
+ pipApp.launchViaIntentAndWaitForPip(
+ wmHelper,
+ stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
+ )
}
}
/** Defines the default teardown required to clean up after the test */
protected open val defaultTeardown: FlickerBuilder.() -> Unit = {
- teardown {
- pipApp.exit(wmHelper)
- }
+ teardown { pipApp.exit(wmHelper) }
}
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index c618e5a24fdf..c315e744bd55 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -21,10 +21,11 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -46,7 +47,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransition(flicker) {
+open class SetRequestedOrientationWhilePinned(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
@@ -69,20 +70,19 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit
setup {
// Launch the PiP activity fixed as landscape.
pipApp.launchViaIntent(
- wmHelper,
- stringExtras =
- mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+ wmHelper,
+ stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
)
// Enter PiP.
broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
// System bar may fade out during fixed rotation.
wmHelper
- .StateSyncBuilder()
- .withPipShown()
- .withRotation(Rotation.ROTATION_0)
- .withNavOrTaskBarVisible()
- .withStatusBarVisible()
- .waitForAndVerify()
+ .StateSyncBuilder()
+ .withPipShown()
+ .withRotation(Rotation.ROTATION_0)
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
}
}
@@ -150,7 +150,7 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ return LegacyFlickerTestFactory.nonRotationTests(
supportedRotations = listOf(Rotation.ROTATION_0)
)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 43d6c8f26126..0ff9cfff873e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -17,10 +17,11 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.assertions.FlickerTest
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -58,7 +59,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker) {
+open class ShowPipAndRotateDisplay(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
@@ -154,13 +155,13 @@ open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
- * and navigation modes.
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
- return FlickerTestFactory.rotationTests()
+ return LegacyFlickerTestFactory.rotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
index b7a2c47e3b32..25164711b2e5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
@@ -16,9 +16,10 @@
package com.android.wm.shell.flicker.pip
+import android.tools.common.flicker.assertions.FlickerTest
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -27,18 +28,18 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowPipAndRotateDisplayCfArm(flicker: FlickerTest) : ShowPipAndRotateDisplay(flicker) {
+class ShowPipAndRotateDisplayCfArm(flicker: LegacyFlickerTest) : ShowPipAndRotateDisplay(flicker) {
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
- * and navigation modes.
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
- return FlickerTestFactory.rotationTests()
+ return LegacyFlickerTestFactory.rotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 0432a8497fbe..d4cd6da4acb1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -20,8 +20,8 @@ import android.graphics.Rect
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.UiObject2
import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.wait
+import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.utils.wait
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
index 90406c510bad..4402e2153e9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
@@ -21,11 +21,11 @@ import android.app.PendingIntent
import android.os.Bundle
import android.service.notification.StatusBarNotification
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.NotificationListener.Companion.findNotification
-import com.android.wm.shell.flicker.NotificationListener.Companion.startNotificationListener
-import com.android.wm.shell.flicker.NotificationListener.Companion.stopNotificationListener
-import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToAppear
-import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToDisappear
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.findNotification
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.startNotificationListener
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.stopNotificationListener
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.waitForNotificationToAppear
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.waitForNotificationToDisappear
import org.junit.After
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 6104b7bdacba..47bff8de377e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -24,7 +24,7 @@ import android.tools.device.helpers.wakeUpAndGoToHomeScreen
import android.tools.device.traces.parsers.WindowManagerStateHelper
import android.view.Surface.ROTATION_0
import android.view.Surface.rotationToString
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assume.assumeTrue
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
index b0adbe1d07ce..4aee61ade10e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
@@ -22,7 +22,7 @@ import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME
/** Id of the root view in the com.android.wm.shell.pip.tv.PipMenuActivity */
private const val TV_PIP_MENU_ROOT_ID = "tv_pip_menu"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
new file mode 100644
index 000000000000..610cedefe594
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service
+
+import android.app.Instrumentation
+import android.platform.test.rule.NavigationModeRule
+import android.platform.test.rule.PressHomeRule
+import android.platform.test.rule.UnlockScreenRule
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.flicker.rules.LaunchAppRule
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.RuleChain
+
+object Utils {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain {
+ return RuleChain.outerRule(UnlockScreenRule())
+ .around(
+ NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false)
+ )
+ .around(
+ LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
+ )
+ .around(RemoveAllTasksButHomeRule())
+ .around(
+ ChangeDisplayOrientationRule(
+ rotation,
+ resetOrientationAfterTest = false,
+ clearCacheAfterParsing = false
+ )
+ )
+ .around(PressHomeRule())
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..566adec75615
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..92b62273d8cb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..e6d56b5c94d3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark :
+ DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..6752c58bd568
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavPortraitBenchmark :
+ DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..7c9ab9939dd0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..4b795713cb23
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..04950799732e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..71ef48bea686
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..c78729c6dc92
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..30bce2f657b1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..b33ea7c89158
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..07a86a57117b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..9a1d12787b9d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..266e268a3537
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..83fc30bceb7b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..b2f19299c7f0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..dae92dddbfec
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..732047ba38ad
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..1de7efd7970a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..1a046aa5b09e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..6e88f0eddee8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..d26a29c80583
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..4a552b0aed6a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..b7376eaea66d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..b2d05e4a2632
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..6de31b1315e4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..aab18a6d27b9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark :
+ SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..b074f2c161c9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
+ SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 000000000000..c402aa4444d8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
new file mode 100644
index 000000000000..840401c23a91
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
new file mode 100644
index 000000000000..a5c512267508
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
new file mode 100644
index 000000000000..092fb6720e57
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
new file mode 100644
index 000000000000..8cb25fe531b8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavLandscape :
+ DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
new file mode 100644
index 000000000000..fa1be63296e0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavPortrait :
+ DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
new file mode 100644
index 000000000000..aa35237b615f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavLandscape :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
new file mode 100644
index 000000000000..e195360cdc36
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavPortrait :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
new file mode 100644
index 000000000000..c1b3aade11cd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
new file mode 100644
index 000000000000..c6e2e854ab89
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
new file mode 100644
index 000000000000..5f771c7545c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
new file mode 100644
index 000000000000..729a401fe34c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
new file mode 100644
index 000000000000..6e4cf9f55cfd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
new file mode 100644
index 000000000000..cc2870213e8d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
new file mode 100644
index 000000000000..736604f02377
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
new file mode 100644
index 000000000000..8df8dfaab071
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
new file mode 100644
index 000000000000..378f055ef830
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
new file mode 100644
index 000000000000..b33d26288d33
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
new file mode 100644
index 000000000000..b1d3858b9dbb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavLandscape :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
new file mode 100644
index 000000000000..6d824c74c1fe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavPortrait :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
new file mode 100644
index 000000000000..f1d3d0cf2716
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavLandscape :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios([])
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
new file mode 100644
index 000000000000..a867bac8c0eb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavPortrait :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios([])
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
new file mode 100644
index 000000000000..76247ba10037
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
new file mode 100644
index 000000000000..e179da81e4db
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
new file mode 100644
index 000000000000..20f554f7d154
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavLandscape :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
new file mode 100644
index 000000000000..f7776ee3244a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavPortrait :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
new file mode 100644
index 000000000000..00f607343b3b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavLandscape :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
new file mode 100644
index 000000000000..b3340e77dbfa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavPortrait :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
new file mode 100644
index 000000000000..3da61e5e310c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
new file mode 100644
index 000000000000..627ae1843314
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
new file mode 100644
index 000000000000..7cbc1c3c272c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
new file mode 100644
index 000000000000..2eb81e0d0492
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
new file mode 100644
index 000000000000..e530f6369609
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CopyContentInSplit
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+ private val textEditApp = SplitScreenUtils.getIme(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp)
+ }
+
+ @Test
+ open fun copyContentInSplit() {
+ SplitScreenUtils.copyContentInSplit(instrumentation, device, primaryApp, textEditApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
new file mode 100644
index 000000000000..e9fc43746d27
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DismissSplitScreenByDivider
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ }
+
+ @Test
+ open fun dismissSplitScreenByDivider() {
+ if (tapl.isTablet) {
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = false,
+ dragToBottom = true
+ )
+ } else {
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = true,
+ dragToBottom = true
+ )
+ }
+ wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
new file mode 100644
index 000000000000..416692c37b34
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DismissSplitScreenByGoHome
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ }
+
+ @Test
+ open fun dismissSplitScreenByGoHome() {
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
new file mode 100644
index 000000000000..494a246d2f50
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DragDividerToResize
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ }
+
+ @Test
+ open fun dragDividerToResize() {
+ SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
new file mode 100644
index 000000000000..369bdfc1103d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromAllApps
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ tapl.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun enterSplitScreenByDragFromAllApps() {
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
new file mode 100644
index 000000000000..776c397cc354
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromNotification
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+ private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ // Send a notification
+ sendNotificationApp.launchViaIntent(wmHelper)
+ sendNotificationApp.postNotification(wmHelper)
+ tapl.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun enterSplitScreenByDragFromNotification() {
+ SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ sendNotificationApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
new file mode 100644
index 000000000000..5d67dc7e231b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromShortcut
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(tapl.isTablet)
+
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ tapl.goHome()
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+ primaryApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun enterSplitScreenByDragFromShortcut() {
+ tapl.launchedAppState.taskbar
+ .getAppIcon(secondaryApp.appName)
+ .openDeepShortcutMenu()
+ .getMenuItem("Split Screen Secondary Activity")
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+ // TODO: Do we want this check in here? Add to the other tests?
+ // flicker.splitScreenEntered(
+ // primaryApp,
+ // secondaryApp,
+ // fromOtherApp = false,
+ // appExistAtStart = false
+ // )
+ }
+
+ @After
+ fun teardwon() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
new file mode 100644
index 000000000000..5bbb42fd1864
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromTaskbar
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ tapl.goHome()
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+ primaryApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun enterSplitScreenByDragFromTaskbar() {
+ tapl.launchedAppState.taskbar
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
new file mode 100644
index 000000000000..c2100f641a55
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenFromOverview
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ primaryApp.launchViaIntent(wmHelper)
+ secondaryApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+
+ @Test
+ open fun enterSplitScreenFromOverview() {
+ SplitScreenUtils.splitFromOverview(tapl, device)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
new file mode 100644
index 000000000000..70f3bed9afdc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchAppByDoubleTapDivider
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ tapl.workspace.switchToOverview().dismissAllTasks()
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ }
+
+ @Test
+ open fun switchAppByDoubleTapDivider() {
+ SplitScreenUtils.doubleTapDividerToSwitch(device)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+ waitForLayersToSwitch(wmHelper)
+ waitForWindowsToSwitch(wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+
+ private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
+ wmHelper
+ .StateSyncBuilder()
+ .add("appWindowsSwitched") {
+ val primaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ primaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ secondaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+
+ if (isLandscape(rotation)) {
+ return@add if (isTablet()) {
+ secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+ } else {
+ primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+ }
+ } else {
+ return@add if (isTablet()) {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ } else {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ }
+ }
+ }
+ .waitForAndVerify()
+ }
+
+ private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
+ wmHelper
+ .StateSyncBuilder()
+ .add("appLayersSwitched") {
+ val primaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ primaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ secondaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
+
+ val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+ val secondaryVisibleRegion =
+ secondaryAppLayer.visibleRegion?.bounds ?: return@add false
+
+ if (isLandscape(rotation)) {
+ return@add if (isTablet()) {
+ secondaryVisibleRegion.right <= primaryVisibleRegion.left
+ } else {
+ primaryVisibleRegion.right <= secondaryVisibleRegion.left
+ }
+ } else {
+ return@add if (isTablet()) {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ } else {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ }
+ }
+ }
+ .waitForAndVerify()
+ }
+
+ private fun isLandscape(rotation: Rotation): Boolean {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ return displayBounds.width > displayBounds.height
+ }
+
+ private fun isTablet(): Boolean {
+ val sizeDp: Point = device.displaySizeDp
+ val LARGE_SCREEN_DP_THRESHOLD = 600
+ return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
new file mode 100644
index 000000000000..86f394da0231
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromAnotherApp
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+ private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ thirdApp.launchViaIntent(wmHelper)
+ wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
+ }
+
+ @Test
+ open fun switchBackToSplitFromAnotherApp() {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
new file mode 100644
index 000000000000..d7b611e04d9d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromHome
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+
+ @Test
+ open fun switchBackToSplitFromHome() {
+ tapl.workspace.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
new file mode 100644
index 000000000000..bf4c381b1c3a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromRecent
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+
+ @Test
+ open fun switchBackToSplitFromRecent() {
+ tapl.workspace.switchToOverview().currentTask.open()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
new file mode 100644
index 000000000000..4a9c32f10415
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBetweenSplitPairs
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+ private val thirdApp = SplitScreenUtils.getIme(instrumentation)
+ private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
+ }
+
+ @Test
+ open fun switchBetweenSplitPairs() {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ thirdApp.exit(wmHelper)
+ fourthApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
new file mode 100644
index 000000000000..383a6b39a2b6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class UnlockKeyguardToSplitScreen {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+
+ SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @Test
+ open fun unlockKeyguardToSplitScreen() {
+ device.sleep()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ device.wakeUp()
+ device.pressMenu()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index d1f0980786bf..3702be9541a3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -17,25 +17,20 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.EdgeExtensionComponentMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
-import com.android.wm.shell.flicker.appWindowKeepVisible
-import com.android.wm.shell.flicker.layerKeepVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowKeepVisible
+import com.android.wm.shell.flicker.utils.layerKeepVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsKeepVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,13 +40,13 @@ import org.junit.runners.Parameterized
/**
* Test copy content from the left to the right side of the split-screen.
*
- * To run this test: `atest WMShellFlickerTests:CopyContentInSplit`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:CopyContentInSplit`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CopyContentInSplit(override val flicker: FlickerTest) :
+class CopyContentInSplit(override val flicker: LegacyFlickerTest) :
CopyContentInSplitBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -60,21 +55,6 @@ class CopyContentInSplit(override val flicker: FlickerTest) :
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(textEditApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(textEditApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
-
- // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -136,8 +116,6 @@ class CopyContentInSplit(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 4505b9978b76..8b906305506f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -21,17 +21,17 @@ import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.helpers.WindowUtils
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesInvisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesInvisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesInvisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,13 +41,13 @@ import org.junit.runners.Parameterized
/**
* Test dismiss split screen by dragging the divider bar.
*
- * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByDivider`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:DismissSplitScreenByDivider`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByDivider(override val flicker: FlickerTest) :
+class DismissSplitScreenByDivider(override val flicker: LegacyFlickerTest) :
DismissSplitScreenByDividerBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index e05b22141e4d..50f6a382a702 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -20,15 +20,15 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.layerBecomesInvisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.utils.layerBecomesInvisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesInvisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,13 +38,13 @@ import org.junit.runners.Parameterized
/**
* Test dismiss split screen by go home.
*
- * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByGoHome`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:DismissSplitScreenByGoHome`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByGoHome(override val flicker: FlickerTest) :
+class DismissSplitScreenByGoHome(override val flicker: LegacyFlickerTest) :
DismissSplitScreenByGoHomeBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -154,8 +154,6 @@ class DismissSplitScreenByGoHome(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 63c5d14d85e1..ca9c13009848 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -17,23 +17,18 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
-import com.android.wm.shell.flicker.appWindowKeepVisible
-import com.android.wm.shell.flicker.layerKeepVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowKeepVisible
+import com.android.wm.shell.flicker.utils.layerKeepVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsChanges
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,13 +38,13 @@ import org.junit.runners.Parameterized
/**
* Test resize split by dragging the divider bar.
*
- * To run this test: `atest WMShellFlickerTests:DragDividerToResize`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:DragDividerToResize`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DragDividerToResize(override val flicker: FlickerTest) :
+class DragDividerToResize(override val flicker: LegacyFlickerTest) :
DragDividerToResizeBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -58,19 +53,6 @@ class DragDividerToResize(override val flicker: FlickerTest) :
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -127,8 +109,6 @@ class DragDividerToResize(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index e55868675da7..f8d1e1f1f498 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -22,19 +22,19 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,13 +45,13 @@ import org.junit.runners.Parameterized
* Test enter split screen by dragging app icon from all apps. This test is only for large screen
* devices.
*
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromAllApps`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromAllApps`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromAllApps(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromAllApps(override val flicker: LegacyFlickerTest) :
EnterSplitScreenByDragFromAllAppsBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -160,11 +160,10 @@ class EnterSplitScreenByDragFromAllApps(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index ab8ecc54e71c..ff5d93550541 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -22,18 +22,18 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,13 +44,13 @@ import org.junit.runners.Parameterized
* Test enter split screen by dragging app icon from notification. This test is only for large
* screen devices.
*
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromNotification`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromNotification(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromNotification(override val flicker: LegacyFlickerTest) :
EnterSplitScreenByDragFromNotificationBenchmark(flicker), ICommonAssertions {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -162,11 +162,10 @@ class EnterSplitScreenByDragFromNotification(override val flicker: FlickerTest)
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index 516ca97bc531..7c710777087d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -21,17 +21,17 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,13 +41,13 @@ import org.junit.runners.Parameterized
/**
* Test enter split screen by dragging a shortcut. This test is only for large screen devices.
*
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromShortcut`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromShortcut(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromShortcut(override val flicker: LegacyFlickerTest) :
EnterSplitScreenByDragFromShortcutBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
@@ -105,11 +105,10 @@ class EnterSplitScreenByDragFromShortcut(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 4af7e248b660..83717062b05e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -22,19 +22,19 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,13 +45,13 @@ import org.junit.runners.Parameterized
* Test enter split screen by dragging app icon from taskbar. This test is only for large screen
* devices.
*
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromTaskbar`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromTaskbar(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromTaskbar(override val flicker: LegacyFlickerTest) :
EnterSplitScreenByDragFromTaskbarBenchmark(flicker), ICommonAssertions {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -163,10 +163,9 @@ class EnterSplitScreenByDragFromTaskbar(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index faad9e82ffef..0bfdbb4de7c5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -20,17 +20,17 @@ import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,13 +40,13 @@ import org.junit.runners.Parameterized
/**
* Test enter split screen from Overview.
*
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromOverview`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenFromOverview`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenFromOverview(override val flicker: FlickerTest) :
+class EnterSplitScreenFromOverview(override val flicker: LegacyFlickerTest) :
EnterSplitScreenFromOverviewBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -106,8 +106,6 @@ class EnterSplitScreenFromOverview(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index 195b73a14a72..7ce995ac64aa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -18,11 +18,12 @@ package com.android.wm.shell.flicker.splitscreen
import android.content.Context
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.BaseBenchmarkTest
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
-abstract class SplitScreenBase(flicker: FlickerTest) : BaseBenchmarkTest(flicker) {
+abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(flicker) {
protected val context: Context = instrumentation.context
protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index 03b8a75a1f32..fac97c8cc8c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -16,25 +16,21 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerKeepVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerKeepVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,13 +40,13 @@ import org.junit.runners.Parameterized
/**
* Test double tap the divider bar to switch the two apps.
*
- * To run this test: `atest WMShellFlickerTests:SwitchAppByDoubleTapDivider`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchAppByDoubleTapDivider`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) :
+class SwitchAppByDoubleTapDivider(override val flicker: LegacyFlickerTest) :
SwitchAppByDoubleTapDividerBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -59,19 +55,6 @@ class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) :
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -120,11 +103,10 @@ class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 078d95de1dd0..88bbc0e7880b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -21,15 +21,15 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,13 +39,13 @@ import org.junit.runners.Parameterized
/**
* Test quick switch to split pair from another app.
*
- * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromAnotherApp`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromAnotherApp`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromAnotherApp(override val flicker: FlickerTest) :
+class SwitchBackToSplitFromAnotherApp(override val flicker: LegacyFlickerTest) :
SwitchBackToSplitFromAnotherAppBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -149,11 +149,10 @@ class SwitchBackToSplitFromAnotherApp(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 7c84243e00d7..e85dc24a7781 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -21,15 +21,15 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,13 +39,13 @@ import org.junit.runners.Parameterized
/**
* Test quick switch to split pair from home.
*
- * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromHome`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromHome(override val flicker: FlickerTest) :
+class SwitchBackToSplitFromHome(override val flicker: LegacyFlickerTest) :
SwitchBackToSplitFromHomeBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -149,11 +149,10 @@ class SwitchBackToSplitFromHome(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index 7c46d3e099a2..f7a9ed073002 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -21,15 +21,15 @@ import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,13 +39,13 @@ import org.junit.runners.Parameterized
/**
* Test switch back to split pair from recent.
*
- * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromRecent`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromRecent(override val flicker: FlickerTest) :
+class SwitchBackToSplitFromRecent(override val flicker: LegacyFlickerTest) :
SwitchBackToSplitFromRecentBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -149,11 +149,10 @@ class SwitchBackToSplitFromRecent(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 674ba40f6a1f..66f9b85ea572 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -17,27 +17,21 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
-import com.android.wm.shell.flicker.layerBecomesInvisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesInvisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsSnapToDivider
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,13 +41,13 @@ import org.junit.runners.Parameterized
/**
* Test quick switch between two split pairs.
*
- * To run this test: `atest WMShellFlickerTests:SwitchBetweenSplitPairs`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBetweenSplitPairs`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBetweenSplitPairs(override val flicker: FlickerTest) :
+class SwitchBetweenSplitPairs(override val flicker: LegacyFlickerTest) :
SwitchBetweenSplitPairsBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -62,21 +56,6 @@ class SwitchBetweenSplitPairs(override val flicker: FlickerTest) :
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(thirdApp)
- flicker.appWindowIsVisibleAtStart(fourthApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.appWindowIsInvisibleAtEnd(thirdApp)
- flicker.appWindowIsInvisibleAtEnd(fourthApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerInvisibleAtMiddle() =
@@ -223,8 +202,6 @@ class SwitchBetweenSplitPairs(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index 676c150815ad..851391d37323 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -21,15 +21,15 @@ import android.tools.common.NavBar
import android.tools.common.flicker.subject.region.RegionSubject
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitscreen.benchmark.UnlockKeyguardToSplitScreenBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,21 +37,21 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test unlocking insecure keyguard to back to split screen tasks and verify the transition behavior.
+ * Test unlocking insecure keyguard to back to split screen tasks and verify the transition
+ * behavior.
*
- * To run this test: `atest WMShellFlickerTests:UnlockKeyguardToSplitScreen`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:UnlockKeyguardToSplitScreen`
*/
@RequiresDevice
@Postsubmit
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) :
- UnlockKeyguardToSplitScreenBenchmark(flicker), ICommonAssertions {
+class UnlockKeyguardToSplitScreen(override val flicker: LegacyFlickerTest) :
+ UnlockKeyguardToSplitScreenBenchmark(flicker), ICommonAssertions {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
- defaultSetup(this)
defaultTeardown(this)
thisTransition(this)
}
@@ -65,33 +65,35 @@ class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) :
@Test
fun primaryAppBoundsIsVisibleAtEnd() =
- flicker.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp,
- landscapePosLeft = false,
- portraitPosTop = false
- )
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Test
fun secondaryAppBoundsIsVisibleAtEnd() =
- flicker.splitAppLayerBoundsIsVisibleAtEnd(
- secondaryApp,
- landscapePosLeft = true,
- portraitPosTop = true
- )
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp,
+ landscapePosLeft = true,
+ portraitPosTop = true
+ )
- @Test
- fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
+ @Test fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
- @Test
- fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
+ @Test fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
@Test
fun notOverlapsForPrimaryAndSecondaryAppLayers() {
flicker.assertLayers {
this.invoke("notOverlapsForPrimaryAndSecondaryLayers") {
- val primaryAppRegions = it.subjects.filter { subject ->
- subject.name.contains(primaryApp.toLayerName()) && subject.isVisible
- }.mapNotNull { primaryApp -> primaryApp.layer.visibleRegion }.toTypedArray()
+ val primaryAppRegions =
+ it.subjects
+ .filter { subject ->
+ subject.name.contains(primaryApp.toLayerName()) && subject.isVisible
+ }
+ .mapNotNull { primaryApp -> primaryApp.layer.visibleRegion }
+ .toTypedArray()
val primaryAppRegionArea = RegionSubject(primaryAppRegions, it.timestamp)
it.visibleRegion(secondaryApp).notOverlaps(primaryAppRegionArea.region)
@@ -102,10 +104,9 @@ class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) :
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
- supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index c3c5f88eaa29..e5c1e75a75f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -16,18 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +33,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) :
+abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val textEditApp = SplitScreenUtils.getIme(instrumentation)
protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
@@ -54,26 +51,9 @@ open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) :
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index 37cd18fad521..e4e1af9d24ce 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScreenBase(flicker) {
+abstract class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) :
+ SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
@@ -60,24 +57,9 @@ open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScr
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index 0ec6dc96bcd9..b2dd02bf2c41 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest) :
+abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -47,24 +43,9 @@ open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index 190e2e765bcc..078859166dbc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -16,19 +16,16 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +34,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) :
+abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -45,32 +42,14 @@ open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) :
transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
- // robust enough to get the correct end state.
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 3a1d1a4415c3..884e4513e893 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: FlickerTest) :
+abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
@@ -57,38 +53,18 @@ open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: Flic
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
index 2033b7d64416..e5c40b69726c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,8 +35,9 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: FlickerTest) :
- SplitScreenBase(flicker) {
+abstract class EnterSplitScreenByDragFromNotificationBenchmark(
+ override val flicker: LegacyFlickerTest
+) : SplitScreenBase(flicker) {
protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -58,21 +55,6 @@ open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker:
teardown { sendNotificationApp.exit(wmHelper) }
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
@@ -81,11 +63,10 @@ open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker:
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index b7a7110afe25..04510014c437 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,8 +35,9 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) :
- SplitScreenBase(flicker) {
+abstract class EnterSplitScreenByDragFromShortcutBenchmark(
+ override val flicker: LegacyFlickerTest
+) : SplitScreenBase(flicker) {
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
@@ -62,33 +59,13 @@ open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) :
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index b1ce62f99e6c..9e0ca1b20f09 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: FlickerTest) :
+abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -56,26 +52,6 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Flic
}
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
@@ -84,10 +60,9 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Flic
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index 14f07453b7d1..06b4fe7e0eb4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTest) :
+abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -56,24 +52,9 @@ open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTe
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index 65fb1358a9b0..007b7518b16e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -16,21 +16,18 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +36,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,14 +50,6 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTes
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
wmHelper
.StateSyncBuilder()
@@ -134,22 +123,13 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTes
return displayBounds.width > displayBounds.height
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
- // robust enough to get the correct end state.
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index b333aba447a2..10c8eebf7d1d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
@@ -55,27 +51,13 @@ open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: Flicke
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index a27540efdad7..a6e750fed70e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,27 +49,13 @@ open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest)
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index 18bf4ff054e0..7e8d5fb83157 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,27 +49,13 @@ open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTes
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index c5fe61e26733..56edad1ded19 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -16,17 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -35,7 +32,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thirdApp = SplitScreenUtils.getIme(instrumentation)
protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -64,14 +61,9 @@ open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) :
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit @Test open fun cujCompleted() {}
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
index 5f16e5b4d65e..065d4d62be42 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
@@ -19,11 +19,11 @@ package com.android.wm.shell.flicker.splitscreen.benchmark
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -33,11 +33,11 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: FlickerTest) :
- SplitScreenBase(flicker) {
+abstract class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) :
+ SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup { SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp) }
transitions {
device.sleep()
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
@@ -47,21 +47,12 @@ open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: FlickerTes
}
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests(
- supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
- }
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
index 798cc95c020f..e5c124cbe775 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
@@ -16,25 +16,25 @@
@file:JvmName("CommonAssertions")
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
import android.tools.common.Rotation
import android.tools.common.datatypes.Region
import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
import android.tools.common.flicker.subject.layers.LayersTraceSubject
import android.tools.common.traces.component.IComponentMatcher
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.helpers.WindowUtils
-fun FlickerTest.appPairsDividerIsVisibleAtEnd() {
+fun LegacyFlickerTest.appPairsDividerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
}
-fun FlickerTest.appPairsDividerIsInvisibleAtEnd() {
+fun LegacyFlickerTest.appPairsDividerIsInvisibleAtEnd() {
assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
}
-fun FlickerTest.appPairsDividerBecomesVisible() {
+fun LegacyFlickerTest.appPairsDividerBecomesVisible() {
assertLayers {
this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -42,7 +42,7 @@ fun FlickerTest.appPairsDividerBecomesVisible() {
}
}
-fun FlickerTest.splitScreenEntered(
+fun LegacyFlickerTest.splitScreenEntered(
component1: IComponentMatcher,
component2: IComponentMatcher,
fromOtherApp: Boolean,
@@ -69,7 +69,7 @@ fun FlickerTest.splitScreenEntered(
splitScreenDividerIsVisibleAtEnd()
}
-fun FlickerTest.splitScreenDismissed(
+fun LegacyFlickerTest.splitScreenDismissed(
component1: IComponentMatcher,
component2: IComponentMatcher,
toHome: Boolean
@@ -87,27 +87,27 @@ fun FlickerTest.splitScreenDismissed(
splitScreenDividerIsInvisibleAtEnd()
}
-fun FlickerTest.splitScreenDividerIsVisibleAtStart() {
+fun LegacyFlickerTest.splitScreenDividerIsVisibleAtStart() {
assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTest.splitScreenDividerIsVisibleAtEnd() {
+fun LegacyFlickerTest.splitScreenDividerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTest.splitScreenDividerIsInvisibleAtStart() {
+fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtStart() {
assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTest.splitScreenDividerIsInvisibleAtEnd() {
+fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtEnd() {
assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTest.splitScreenDividerBecomesVisible() {
+fun LegacyFlickerTest.splitScreenDividerBecomesVisible() {
layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
}
-fun FlickerTest.splitScreenDividerBecomesInvisible() {
+fun LegacyFlickerTest.splitScreenDividerBecomesInvisible() {
assertLayers {
this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
@@ -115,23 +115,23 @@ fun FlickerTest.splitScreenDividerBecomesInvisible() {
}
}
-fun FlickerTest.layerBecomesVisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.layerBecomesVisible(component: IComponentMatcher) {
assertLayers { this.isInvisible(component).then().isVisible(component) }
}
-fun FlickerTest.layerBecomesInvisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.layerBecomesInvisible(component: IComponentMatcher) {
assertLayers { this.isVisible(component).then().isInvisible(component) }
}
-fun FlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) {
+fun LegacyFlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) {
assertLayersEnd { this.isVisible(component) }
}
-fun FlickerTest.layerKeepVisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.layerKeepVisible(component: IComponentMatcher) {
assertLayers { this.isVisible(component) }
}
-fun FlickerTest.splitAppLayerBoundsBecomesVisible(
+fun LegacyFlickerTest.splitAppLayerBoundsBecomesVisible(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
@@ -150,7 +150,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesVisible(
}
}
-fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
+fun LegacyFlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
assertLayers {
this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
.then()
@@ -161,7 +161,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMat
}
}
-fun FlickerTest.splitAppLayerBoundsBecomesInvisible(
+fun LegacyFlickerTest.splitAppLayerBoundsBecomesInvisible(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
@@ -180,7 +180,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesInvisible(
}
}
-fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd(
+fun LegacyFlickerTest.splitAppLayerBoundsIsVisibleAtEnd(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
@@ -195,7 +195,7 @@ fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd(
}
}
-fun FlickerTest.splitAppLayerBoundsKeepVisible(
+fun LegacyFlickerTest.splitAppLayerBoundsKeepVisible(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
@@ -210,7 +210,7 @@ fun FlickerTest.splitAppLayerBoundsKeepVisible(
}
}
-fun FlickerTest.splitAppLayerBoundsChanges(
+fun LegacyFlickerTest.splitAppLayerBoundsChanges(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
@@ -304,7 +304,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
}
}
-fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowBecomesVisible(component: IComponentMatcher) {
assertWm {
this.isAppWindowInvisible(component)
.then()
@@ -316,39 +316,39 @@ fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) {
}
}
-fun FlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) {
assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) }
}
-fun FlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) {
assertWmStart { this.isAppWindowVisible(component) }
}
-fun FlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
assertWmEnd { this.isAppWindowVisible(component) }
}
-fun FlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
assertWmStart { this.isAppWindowInvisible(component) }
}
-fun FlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
assertWmEnd { this.isAppWindowInvisible(component) }
}
-fun FlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) {
assertWmStart { this.notContains(component) }
}
-fun FlickerTest.appWindowKeepVisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowKeepVisible(component: IComponentMatcher) {
assertWm { this.isAppWindowVisible(component) }
}
-fun FlickerTest.dockedStackDividerIsVisibleAtEnd() {
+fun LegacyFlickerTest.dockedStackDividerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) }
}
-fun FlickerTest.dockedStackDividerBecomesVisible() {
+fun LegacyFlickerTest.dockedStackDividerBecomesVisible() {
assertLayers {
this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -356,7 +356,7 @@ fun FlickerTest.dockedStackDividerBecomesVisible() {
}
}
-fun FlickerTest.dockedStackDividerBecomesInvisible() {
+fun LegacyFlickerTest.dockedStackDividerBecomesInvisible() {
assertLayers {
this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -364,11 +364,11 @@ fun FlickerTest.dockedStackDividerBecomesInvisible() {
}
}
-fun FlickerTest.dockedStackDividerNotExistsAtEnd() {
+fun LegacyFlickerTest.dockedStackDividerNotExistsAtEnd() {
assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) }
}
-fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd(
+fun LegacyFlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd(
rotation: Rotation,
primaryComponent: IComponentMatcher
) {
@@ -380,7 +380,7 @@ fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd(
}
}
-fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd(
+fun LegacyFlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd(
rotation: Rotation,
primaryComponent: IComponentMatcher
) {
@@ -392,7 +392,7 @@ fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd(
}
}
-fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd(
+fun LegacyFlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd(
rotation: Rotation,
secondaryComponent: IComponentMatcher
) {
@@ -404,7 +404,7 @@ fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd(
}
}
-fun FlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd(
+fun LegacyFlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd(
rotation: Rotation,
secondaryComponent: IComponentMatcher
) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
index 3bc1e2acd015..3b66d6addacd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
@@ -16,7 +16,7 @@
@file:JvmName("CommonConstants")
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
index 02d9a056afbf..7f58cedce63d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
@@ -32,7 +32,7 @@ import org.junit.Assume
import org.junit.Test
interface ICommonAssertions {
- val flicker: FlickerTest
+ val flicker: LegacyFlickerTest
/** Checks that all parts of the screen are covered during the transition */
@Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MultiWindowUtils.kt
index 87b94ff8668b..9b3a480c06b1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MultiWindowUtils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
import android.app.Instrumentation
import android.content.Context
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/NotificationListener.kt
index e0ef92457f58..529c1254a64c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/NotificationListener.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index 1063dfd8d737..3f8a1ae6bd79 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.splitscreen
+package com.android.wm.shell.flicker.utils
import android.app.Instrumentation
import android.graphics.Point
@@ -39,11 +39,10 @@ import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.NotificationAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
import org.junit.Assert.assertNotNull
-internal object SplitScreenUtils {
+object SplitScreenUtils {
private const val TIMEOUT_MS = 3_000L
private const val DRAG_DURATION_MS = 1_000L
private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
@@ -112,6 +111,16 @@ internal object SplitScreenUtils {
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
+ fun enterSplitViaIntent(
+ wmHelper: WindowManagerStateHelper,
+ primaryApp: StandardAppHelper,
+ secondaryApp: StandardAppHelper
+ ) {
+ val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
+ primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
+ waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
// Note: The initial split position in landscape is different between tablet and phone.
// In landscape, tablet will let the first app split to right side, and phone will
@@ -314,14 +323,14 @@ internal object SplitScreenUtils {
dividerBar.drag(
Point(
if (dragToRight) {
- displayBounds.width * 4 / 5
+ displayBounds.right
} else {
- displayBounds.width * 1 / 5
+ displayBounds.left
},
if (dragToBottom) {
- displayBounds.height * 4 / 5
+ displayBounds.bottom
} else {
- displayBounds.height * 1 / 5
+ displayBounds.top
}
)
)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/WaitUtils.kt
index 556cb06f3ca1..cf2df4e17cb7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/WaitUtils.kt
@@ -16,7 +16,7 @@
@file:JvmName("WaitUtils")
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.utils
import android.os.SystemClock
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
new file mode 100644
index 000000000000..406ada97a07d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+ size_kb: 63488
+ fill_policy: RING_BUFFER
+}
+
+data_sources {
+ config {
+ name: "linux.process_stats"
+ target_buffer: 0
+ # polled per-process memory counters and process/thread names.
+ # If you don't want the polled counters, remove the "process_stats_config"
+ # section, but keep the data source itself as it still provides on-demand
+ # thread/process naming for ftrace data below.
+ process_stats_config {
+ scan_all_processes_on_start: true
+ }
+ }
+}
+
+data_sources: {
+ config {
+ name: "linux.ftrace"
+ ftrace_config {
+ ftrace_events: "ftrace/print"
+ ftrace_events: "task/task_newtask"
+ ftrace_events: "task/task_rename"
+ atrace_categories: "ss"
+ atrace_categories: "wm"
+ atrace_categories: "am"
+ atrace_categories: "aidl"
+ atrace_categories: "input"
+ atrace_categories: "binder_driver"
+ atrace_categories: "sched_process_exit"
+ atrace_apps: "com.android.server.wm.flicker.testapp"
+ atrace_apps: "com.android.systemui"
+ atrace_apps: "com.android.wm.shell.flicker"
+ atrace_apps: "com.android.wm.shell.flicker.other"
+ atrace_apps: "com.android.wm.shell.flicker.bubbles"
+ atrace_apps: "com.android.wm.shell.flicker.pip"
+ atrace_apps: "com.android.wm.shell.flicker.splitscreen"
+ atrace_apps: "com.google.android.apps.nexuslauncher"
+ }
+ }
+}
+
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 34ca1d0ecd9d..d09a90cd7dc7 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -43,6 +43,7 @@ android_test {
"frameworks-base-testutils",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
+ "mockito-kotlin2",
"mockito-target-extended-minus-junit4",
"truth",
"testables",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
index 8278c67a9b4f..0dc16f44340f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
@@ -64,18 +64,27 @@ public class BubbleOverflowTest extends ShellTestCase {
}
@Test
- public void test_initialize() {
+ public void test_initialize_forStack() {
assertThat(mOverflow.getExpandedView()).isNull();
- mOverflow.initialize(mBubbleController);
+ mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
assertThat(mOverflow.getExpandedView()).isNotNull();
assertThat(mOverflow.getExpandedView().getBubbleKey()).isEqualTo(BubbleOverflow.KEY);
+ assertThat(mOverflow.getBubbleBarExpandedView()).isNull();
+ }
+
+ @Test
+ public void test_initialize_forBubbleBar() {
+ mOverflow.initialize(mBubbleController, /* forBubbleBar= */ true);
+
+ assertThat(mOverflow.getBubbleBarExpandedView()).isNotNull();
+ assertThat(mOverflow.getExpandedView()).isNull();
}
@Test
public void test_cleanUpExpandedState() {
- mOverflow.createExpandedView();
+ mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
assertThat(mOverflow.getExpandedView()).isNotNull();
mOverflow.cleanUpExpandedState();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
index 09d474d1f97c..a97c19f17412 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.desktopmode;
+package com.android.wm.shell;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -25,16 +25,16 @@ import android.window.WindowContainerToken;
/**
* {@link WindowContainerToken} wrapper that supports a mock binder
*/
-class MockToken {
+public class MockToken {
private final WindowContainerToken mToken;
- MockToken() {
+ public MockToken() {
mToken = mock(WindowContainerToken.class);
IBinder binder = mock(IBinder.class);
when(mToken.asBinder()).thenReturn(binder);
}
- WindowContainerToken token() {
+ public WindowContainerToken token() {
return mToken;
}
}
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 4fca8b46a069..2d9304705738 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
@@ -92,7 +92,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
.build();
final Animator animator = mAnimRunner.createAnimator(
info, mStartTransaction, mFinishTransaction,
- () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */),
+ () -> mFinishCallback.onTransitionFinished(null /* wct */),
new ArrayList());
// The animation should be empty when it is behind starting window.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
index ab1ccd4599a2..0b2265d4ce9c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -75,7 +75,7 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
assertNotNull(mAnimRunner);
mAnimSpec = mAnimRunner.mAnimationSpec;
assertNotNull(mAnimSpec);
- mFinishCallback = (wct, wctCB) -> {};
+ mFinishCallback = (wct) -> {};
spyOn(mController);
spyOn(mAnimRunner);
spyOn(mAnimSpec);
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 ba34f1f74cd3..270dbc49835f 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
@@ -217,12 +217,10 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
doReturn(animator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
mController.startAnimation(mTransition, info, mStartTransaction,
mFinishTransaction, mFinishCallback);
- verify(mFinishCallback, never()).onTransitionFinished(any(), any());
+ verify(mFinishCallback, never()).onTransitionFinished(any());
mController.mergeAnimation(mTransition, info, new SurfaceControl.Transaction(),
- mTransition,
- (wct, cb) -> {
- });
- verify(mFinishCallback).onTransitionFinished(any(), any());
+ mTransition, (wct) -> {});
+ verify(mFinishCallback).onTransitionFinished(any());
}
@Test
@@ -238,9 +236,9 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
mController.startAnimation(mTransition, info, mStartTransaction,
mFinishTransaction, mFinishCallback);
- verify(mFinishCallback, never()).onTransitionFinished(any(), any());
+ verify(mFinishCallback, never()).onTransitionFinished(any());
mController.onAnimationFinished(mTransition);
- verify(mFinishCallback).onTransitionFinished(any(), any());
+ verify(mFinishCallback).onTransitionFinished(any());
// Should not call finish when the finish has already been called.
assertThrows(IllegalStateException.class,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
new file mode 100644
index 000000000000..e35995775f76
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.app.ActivityTaskManager
+import android.content.pm.LauncherApps
+import android.os.Handler
+import android.os.Looper
+import android.util.SparseArray
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.bubbles.storage.BubbleEntity
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
+import com.android.wm.shell.common.HandlerExecutor
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+class BubbleDataRepositoryTest : ShellTestCase() {
+
+ private val user0BubbleEntities = listOf(
+ BubbleEntity(
+ userId = 0,
+ packageName = "com.example.messenger",
+ shortcutId = "shortcut-1",
+ key = "0k1",
+ desiredHeight = 120,
+ desiredHeightResId = 0,
+ title = null,
+ taskId = 1,
+ locus = null,
+ isDismissable = true
+ ),
+ BubbleEntity(
+ userId = 10,
+ packageName = "com.example.chat",
+ shortcutId = "alice and bob",
+ key = "0k2",
+ desiredHeight = 0,
+ desiredHeightResId = 16537428,
+ title = "title",
+ taskId = 2,
+ locus = null
+ ),
+ BubbleEntity(
+ userId = 0,
+ packageName = "com.example.messenger",
+ shortcutId = "shortcut-2",
+ key = "0k3",
+ desiredHeight = 120,
+ desiredHeightResId = 0,
+ title = null,
+ taskId = ActivityTaskManager.INVALID_TASK_ID,
+ locus = null
+ )
+ )
+
+ private val user1BubbleEntities = listOf(
+ BubbleEntity(
+ userId = 1,
+ packageName = "com.example.messenger",
+ shortcutId = "shortcut-1",
+ key = "1k1",
+ desiredHeight = 120,
+ desiredHeightResId = 0,
+ title = null,
+ taskId = 3,
+ locus = null,
+ isDismissable = true
+ ),
+ BubbleEntity(
+ userId = 12,
+ packageName = "com.example.chat",
+ shortcutId = "alice and bob",
+ key = "1k2",
+ desiredHeight = 0,
+ desiredHeightResId = 16537428,
+ title = "title",
+ taskId = 4,
+ locus = null
+ ),
+ BubbleEntity(
+ userId = 1,
+ packageName = "com.example.messenger",
+ shortcutId = "shortcut-2",
+ key = "1k3",
+ desiredHeight = 120,
+ desiredHeightResId = 0,
+ title = null,
+ taskId = ActivityTaskManager.INVALID_TASK_ID,
+ locus = null
+ ),
+ BubbleEntity(
+ userId = 12,
+ packageName = "com.example.chat",
+ shortcutId = "alice",
+ key = "1k4",
+ desiredHeight = 0,
+ desiredHeightResId = 16537428,
+ title = "title",
+ taskId = 5,
+ locus = null
+ )
+ )
+
+ private val testHandler = Handler(Looper.getMainLooper())
+ private val mainExecutor = HandlerExecutor(testHandler)
+ private val launcherApps = mock<LauncherApps>()
+
+ private val persistedBubbles = SparseArray<List<BubbleEntity>>()
+
+ private lateinit var dataRepository: BubbleDataRepository
+ private lateinit var persistentRepository: BubblePersistentRepository
+
+ @Before
+ fun setup() {
+ persistentRepository = BubblePersistentRepository(mContext)
+ dataRepository = spy(BubbleDataRepository(launcherApps, mainExecutor, persistentRepository))
+
+ persistedBubbles.put(0, user0BubbleEntities)
+ persistedBubbles.put(1, user1BubbleEntities)
+ }
+
+ @After
+ fun teardown() {
+ // Clean up any persisted bubbles for the next run
+ persistentRepository.persistsToDisk(SparseArray())
+ }
+
+ @Test
+ fun testFilterForActiveUsersAndPersist_allValid() {
+ // Matches all the users in user0BubbleEntities & user1BubbleEntities
+ val activeUserIds = listOf(0, 10, 1, 12)
+
+ val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist(
+ activeUserIds, persistedBubbles)
+
+ // No invalid users, so no changes
+ assertThat(persistedBubbles).isEqualTo(validEntitiesByUser)
+
+ // No invalid users, so no persist to disk happened
+ verify(dataRepository, never()).persistToDisk(any())
+ }
+
+ @Test
+ fun testFilterForActiveUsersAndPersist_invalidParent() {
+ // When we start, we do have user 0 bubbles.
+ assertThat(persistedBubbles.get(0)).isNotEmpty()
+
+ val activeUserIds = listOf(10, 1, 12) // Missing user 0
+ val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist(
+ activeUserIds, persistedBubbles)
+
+ // We no longer have any user 0 bubbles.
+ assertThat(validEntitiesByUser.get(0)).isNull()
+ // User 1 bubbles should be the same.
+ assertThat(validEntitiesByUser.get(1)).isEqualTo(user1BubbleEntities)
+
+ // Verify that persist to disk happened with the new valid entities list.
+ verify(dataRepository).persistToDisk(validEntitiesByUser)
+ }
+
+ @Test
+ fun testFilterForActiveUsersAndPersist_invalidChild() {
+ // Build a list to compare against (remove all user 12 bubbles)
+ val (user1EntitiesWithUser12, user1EntitiesWithoutUser12) =
+ user1BubbleEntities.partition { it.userId == 12 }
+
+ // Verify we start with user 12 bubbles
+ assertThat(persistedBubbles.get(1).containsAll(user1EntitiesWithUser12)).isTrue()
+
+ val activeUserIds = listOf(0, 10, 1) // Missing user 1's child user 12
+ val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist(
+ activeUserIds, persistedBubbles)
+
+ // We no longer have any user 12 bubbles.
+ assertThat(validEntitiesByUser.get(1)).isEqualTo(user1EntitiesWithoutUser12)
+
+ // Verify that persist to disk happened with the new valid entities list.
+ verify(dataRepository).persistToDisk(validEntitiesByUser)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
new file mode 100644
index 000000000000..58d9a6486ff2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.View.LAYOUT_DIRECTION_LTR;
+import static android.view.View.LAYOUT_DIRECTION_RTL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests operations and the resulting state managed by {@link BubblePositioner}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class BubblePositionerTest extends ShellTestCase {
+
+ private static final int MIN_WIDTH_FOR_TABLET = 600;
+
+ private BubblePositioner mPositioner;
+ private Configuration mConfiguration;
+
+ @Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mConfiguration = spy(new Configuration());
+ TestableResources testableResources = mContext.getOrCreateTestableResources();
+ testableResources.overrideConfiguration(mConfiguration);
+
+ mPositioner = new BubblePositioner(mContext, mWindowManager);
+ }
+
+ @Test
+ public void testUpdate() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1000, 1200);
+ Rect availableRect = new Rect(screenBounds);
+ availableRect.inset(insets);
+
+ new WindowManagerConfig()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .setUpConfig();
+ mPositioner.update();
+
+ assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect);
+ assertThat(mPositioner.isLandscape()).isFalse();
+ assertThat(mPositioner.isLargeScreen()).isFalse();
+ assertThat(mPositioner.getInsets()).isEqualTo(insets);
+ }
+
+ @Test
+ public void testShowBubblesVertically_phonePortrait() {
+ new WindowManagerConfig().setOrientation(ORIENTATION_PORTRAIT).setUpConfig();
+ mPositioner.update();
+
+ assertThat(mPositioner.showBubblesVertically()).isFalse();
+ }
+
+ @Test
+ public void testShowBubblesVertically_phoneLandscape() {
+ new WindowManagerConfig().setOrientation(ORIENTATION_LANDSCAPE).setUpConfig();
+ mPositioner.update();
+
+ assertThat(mPositioner.isLandscape()).isTrue();
+ assertThat(mPositioner.showBubblesVertically()).isTrue();
+ }
+
+ @Test
+ public void testShowBubblesVertically_tablet() {
+ new WindowManagerConfig().setLargeScreen().setUpConfig();
+ mPositioner.update();
+
+ assertThat(mPositioner.showBubblesVertically()).isTrue();
+ }
+
+ /** If a resting position hasn't been set, calling it will return the default position. */
+ @Test
+ public void testGetRestingPosition_returnsDefaultPosition() {
+ new WindowManagerConfig().setUpConfig();
+ mPositioner.update();
+
+ PointF restingPosition = mPositioner.getRestingPosition();
+ PointF defaultPosition = mPositioner.getDefaultStartPosition();
+
+ assertThat(restingPosition).isEqualTo(defaultPosition);
+ }
+
+ /** If a resting position has been set, it'll return that instead of the default position. */
+ @Test
+ public void testGetRestingPosition_returnsRestingPosition() {
+ new WindowManagerConfig().setUpConfig();
+ mPositioner.update();
+
+ PointF restingPosition = new PointF(100, 100);
+ mPositioner.setRestingPosition(restingPosition);
+
+ assertThat(mPositioner.getRestingPosition()).isEqualTo(restingPosition);
+ }
+
+ /** Test that the default resting position on phone is in upper left. */
+ @Test
+ public void testGetRestingPosition_bubble_onPhone() {
+ new WindowManagerConfig().setUpConfig();
+ mPositioner.update();
+
+ RectF allowableStackRegion =
+ mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+ PointF restingPosition = mPositioner.getRestingPosition();
+
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
+ assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+ }
+
+ @Test
+ public void testGetRestingPosition_bubble_onPhone_RTL() {
+ new WindowManagerConfig().setLayoutDirection(LAYOUT_DIRECTION_RTL).setUpConfig();
+ mPositioner.update();
+
+ RectF allowableStackRegion =
+ mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+ PointF restingPosition = mPositioner.getRestingPosition();
+
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
+ assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+ }
+
+ /** Test that the default resting position on tablet is middle left. */
+ @Test
+ public void testGetRestingPosition_chatBubble_onTablet() {
+ new WindowManagerConfig().setLargeScreen().setUpConfig();
+ mPositioner.update();
+
+ RectF allowableStackRegion =
+ mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+ PointF restingPosition = mPositioner.getRestingPosition();
+
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
+ assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+ }
+
+ @Test
+ public void testGetRestingPosition_chatBubble_onTablet_RTL() {
+ new WindowManagerConfig().setLargeScreen().setLayoutDirection(
+ LAYOUT_DIRECTION_RTL).setUpConfig();
+ mPositioner.update();
+
+ RectF allowableStackRegion =
+ mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+ PointF restingPosition = mPositioner.getRestingPosition();
+
+ assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
+ assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+ }
+
+ /** Test that the default resting position on tablet is middle right. */
+ @Test
+ public void testGetDefaultPosition_appBubble_onTablet() {
+ new WindowManagerConfig().setLargeScreen().setUpConfig();
+ mPositioner.update();
+
+ RectF allowableStackRegion =
+ mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+ PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
+
+ assertThat(startPosition.x).isEqualTo(allowableStackRegion.right);
+ assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
+ }
+
+ @Test
+ public void testGetRestingPosition_appBubble_onTablet_RTL() {
+ new WindowManagerConfig().setLargeScreen().setLayoutDirection(
+ LAYOUT_DIRECTION_RTL).setUpConfig();
+ mPositioner.update();
+
+ RectF allowableStackRegion =
+ mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+ PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
+
+ assertThat(startPosition.x).isEqualTo(allowableStackRegion.left);
+ assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
+ }
+
+ @Test
+ public void testHasUserModifiedDefaultPosition_false() {
+ new WindowManagerConfig().setLargeScreen().setLayoutDirection(
+ LAYOUT_DIRECTION_RTL).setUpConfig();
+ mPositioner.update();
+
+ assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
+
+ mPositioner.setRestingPosition(mPositioner.getDefaultStartPosition());
+
+ assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
+ }
+
+ @Test
+ public void testHasUserModifiedDefaultPosition_true() {
+ new WindowManagerConfig().setLargeScreen().setLayoutDirection(
+ LAYOUT_DIRECTION_RTL).setUpConfig();
+ mPositioner.update();
+
+ assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
+
+ mPositioner.setRestingPosition(new PointF(0, 100));
+
+ assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue();
+ }
+
+ /**
+ * Calculates the Y position bubbles should be placed based on the config. Based on
+ * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
+ * {@link BubbleStackView.RelativeStackPosition}.
+ */
+ private float getDefaultYPosition() {
+ final boolean isTablet = mPositioner.isLargeScreen();
+
+ // On tablet the position is centered, on phone it is an offset from the top.
+ final float desiredY = isTablet
+ ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f)
+ : mContext.getResources().getDimensionPixelOffset(
+ R.dimen.bubble_stack_starting_offset_y);
+ // Since we're visually centering the bubbles on tablet, use total screen height rather
+ // than the available height.
+ final float height = isTablet
+ ? mPositioner.getScreenRect().height()
+ : mPositioner.getAvailableRect().height();
+ float offsetPercent = desiredY / height;
+ offsetPercent = Math.max(0f, Math.min(1f, offsetPercent));
+ final RectF allowableStackRegion =
+ mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+ return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent;
+ }
+
+ /**
+ * Sets up window manager to return config values based on what you need for the test.
+ * By default it sets up a portrait phone without any insets.
+ */
+ private class WindowManagerConfig {
+ private Rect mScreenBounds = new Rect(0, 0, 1000, 2000);
+ private boolean mIsLargeScreen = false;
+ private int mOrientation = ORIENTATION_PORTRAIT;
+ private int mLayoutDirection = LAYOUT_DIRECTION_LTR;
+ private Insets mInsets = Insets.of(0, 0, 0, 0);
+
+ public WindowManagerConfig setScreenBounds(Rect screenBounds) {
+ mScreenBounds = screenBounds;
+ return this;
+ }
+
+ public WindowManagerConfig setLargeScreen() {
+ mIsLargeScreen = true;
+ return this;
+ }
+
+ public WindowManagerConfig setOrientation(int orientation) {
+ mOrientation = orientation;
+ return this;
+ }
+
+ public WindowManagerConfig setLayoutDirection(int layoutDirection) {
+ mLayoutDirection = layoutDirection;
+ return this;
+ }
+
+ public WindowManagerConfig setInsets(Insets insets) {
+ mInsets = insets;
+ return this;
+ }
+
+ public void setUpConfig() {
+ mConfiguration.smallestScreenWidthDp = mIsLargeScreen
+ ? MIN_WIDTH_FOR_TABLET
+ : MIN_WIDTH_FOR_TABLET - 1;
+ mConfiguration.orientation = mOrientation;
+
+ when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection);
+ WindowInsets windowInsets = mock(WindowInsets.class);
+ when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(mInsets);
+ when(mWindowMetrics.getWindowInsets()).thenReturn(windowInsets);
+ when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds);
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
new file mode 100644
index 000000000000..d38b848fbb4d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.drawable.ColorDrawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.core.content.ContextCompat;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class BubbleBarHandleViewTest extends ShellTestCase {
+ private BubbleBarHandleView mHandleView;
+
+ @Before
+ public void setup() {
+ mHandleView = new BubbleBarHandleView(mContext);
+ }
+
+ @Test
+ public void testUpdateHandleColor_lightBg() {
+ mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */);
+
+ assertTrue(mHandleView.getClipToOutline());
+ assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
+ ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
+ assertEquals(bgDrawable.getColor(),
+ ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark));
+ }
+
+ @Test
+ public void testUpdateHandleColor_darkBg() {
+ mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */);
+
+ assertTrue(mHandleView.getClipToOutline());
+ assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
+ ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
+ assertEquals(bgDrawable.getColor(),
+ ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt
new file mode 100644
index 000000000000..9dc816b65d2e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LaunchAdjacentControllerTest : ShellTestCase() {
+
+ private lateinit var controller: LaunchAdjacentController
+
+ @Mock private lateinit var syncQueue: SyncTransactionQueue
+
+ @Before
+ fun setUp() {
+ controller = LaunchAdjacentController(syncQueue)
+ }
+
+ @Test
+ fun newInstance_enabledByDefault() {
+ assertThat(controller.launchAdjacentEnabled).isTrue()
+ }
+
+ @Test
+ fun setLaunchAdjacentRoot_launchAdjacentEnabled_setsFlagRoot() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ val wct = getLatestTransactionOrFail()
+ assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+ }
+
+ @Test
+ fun setLaunchAdjacentRoot_launchAdjacentDisabled_doesNotUpdateFlagRoot() {
+ val token = MockToken().token()
+ controller.launchAdjacentEnabled = false
+ controller.setLaunchAdjacentRoot(token)
+ verify(syncQueue, never()).queue(any())
+ }
+
+ @Test
+ fun clearLaunchAdjacentRoot_launchAdjacentEnabled_clearsFlagRoot() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ controller.clearLaunchAdjacentRoot()
+ val wct = getLatestTransactionOrFail()
+ assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+ }
+
+ @Test
+ fun clearLaunchAdjacentRoot_launchAdjacentDisabled_clearsFlagRoot() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ controller.launchAdjacentEnabled = false
+ clearInvocations(syncQueue)
+
+ controller.clearLaunchAdjacentRoot()
+ val wct = getLatestTransactionOrFail()
+ assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+ }
+
+ @Test
+ fun setLaunchAdjacentEnabled_wasDisabledWithContainerSet_setsFlagRoot() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ controller.launchAdjacentEnabled = false
+ clearInvocations(syncQueue)
+
+ controller.launchAdjacentEnabled = true
+ val wct = getLatestTransactionOrFail()
+ assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+ }
+
+ @Test
+ fun setLaunchAdjacentEnabled_containerNotSet_doesNotUpdateFlagRoot() {
+ controller.launchAdjacentEnabled = false
+ controller.launchAdjacentEnabled = true
+ verify(syncQueue, never()).queue(any())
+ }
+
+ @Test
+ fun setLaunchAdjacentEnabled_multipleTimes_setsFlagRootOnce() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ controller.launchAdjacentEnabled = true
+ controller.launchAdjacentEnabled = true
+ // Only execute once
+ verify(syncQueue).queue(any())
+ }
+
+ @Test
+ fun setLaunchAdjacentDisabled_containerSet_clearsFlagRoot() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ controller.launchAdjacentEnabled = false
+ val wct = getLatestTransactionOrFail()
+ assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+ }
+
+ @Test
+ fun setLaunchAdjacentDisabled_containerNotSet_doesNotUpdateFlagRoot() {
+ controller.launchAdjacentEnabled = false
+ verify(syncQueue, never()).queue(any())
+ }
+
+ @Test
+ fun setLaunchAdjacentDisabled_multipleTimes_setsFlagRootOnce() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ clearInvocations(syncQueue)
+ controller.launchAdjacentEnabled = false
+ controller.launchAdjacentEnabled = false
+ // Only execute once
+ verify(syncQueue).queue(any())
+ }
+
+ private fun getLatestTransactionOrFail(): WindowContainerTransaction {
+ val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(syncQueue, atLeastOnce()).queue(arg.capture())
+ return arg.allValues.last().also { assertThat(it).isNotNull() }
+ }
+}
+
+private fun WindowContainerTransaction.getSetLaunchAdjacentFlagRootContainer(): IBinder {
+ return hierarchyOps
+ // Find the operation with the correct type
+ .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT }
+ // For set flag root operation, toTop is false
+ .filter { op -> !op.toTop }
+ .map { it.container }
+ .first()
+}
+
+private fun WindowContainerTransaction.getClearLaunchAdjacentFlagRootContainer(): IBinder {
+ return hierarchyOps
+ // Find the operation with the correct type
+ .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT }
+ // For clear flag root operation, toTop is true
+ .filter { op -> op.toTop }
+ .map { it.container }
+ .first()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
new file mode 100644
index 000000000000..145c8f0ab8af
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.split;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.view.InputDevice;
+import android.view.InsetsState;
+import android.view.MotionEvent;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link DividerView} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DividerViewTest extends ShellTestCase {
+ private @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
+ private @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler;
+ private @Mock DisplayController mDisplayController;
+ private @Mock DisplayImeController mDisplayImeController;
+ private @Mock ShellTaskOrganizer mTaskOrganizer;
+ private SplitLayout mSplitLayout;
+ private DividerView mDividerView;
+
+ @Before
+ @UiThreadTest
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ Configuration configuration = getConfiguration();
+ mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
+ mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
+ mTaskOrganizer, SplitLayout.PARALLAX_NONE);
+ SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
+ mContext,
+ configuration, mCallbacks);
+ splitWindowManager.init(mSplitLayout, new InsetsState());
+ mDividerView = spy((DividerView) splitWindowManager.getDividerView());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testHoverDividerView() {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
+ "true", false);
+
+ Rect dividerBounds = mSplitLayout.getDividerBounds();
+ int x = dividerBounds.centerX();
+ int y = dividerBounds.centerY();
+ long downTime = SystemClock.uptimeMillis();
+ mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_ENTER, x, y));
+
+ verify(mDividerView, times(1)).setHovering();
+
+ mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_EXIT, x, y));
+
+ verify(mDividerView, times(1)).releaseHovering();
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
+ "false", false);
+ }
+
+ private static MotionEvent getMotionEvent(long eventTime, int action, float x, float y) {
+ MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
+ properties.id = 0;
+ properties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
+ MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+ coords.pressure = 1;
+ coords.size = 1;
+ coords.x = x;
+ coords.y = y;
+
+ return MotionEvent.obtain(eventTime, eventTime, action, 1,
+ new MotionEvent.PointerProperties[]{properties},
+ new MotionEvent.PointerCoords[]{coords}, 0, 0, 1.0f, 1.0f, 0, 0,
+ InputDevice.SOURCE_TOUCHSCREEN, 0);
+ }
+
+ private static Configuration getConfiguration() {
+ final Configuration configuration = new Configuration();
+ configuration.unset();
+ configuration.orientation = ORIENTATION_LANDSCAPE;
+ configuration.windowConfiguration.setRotation(0);
+ configuration.windowConfiguration.setBounds(new Rect(0, 0, 1080, 2160));
+ return configuration;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 443cea245a4f..fe2da5dd19fc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -38,7 +38,6 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index a6501f05475f..f85d707d55f9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -41,6 +41,7 @@ import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
import android.view.InsetsSource;
import android.view.InsetsState;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -58,6 +59,9 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import dagger.Lazy;
+
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,8 +70,6 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import dagger.Lazy;
-
/**
* Tests for {@link CompatUIController}.
*
@@ -82,21 +84,39 @@ public class CompatUIControllerTest extends ShellTestCase {
private CompatUIController mController;
private ShellInit mShellInit;
- private @Mock ShellController mMockShellController;
- private @Mock DisplayController mMockDisplayController;
- private @Mock DisplayInsetsController mMockDisplayInsetsController;
- private @Mock DisplayLayout mMockDisplayLayout;
- private @Mock DisplayImeController mMockImeController;
- private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
- private @Mock SyncTransactionQueue mMockSyncQueue;
- private @Mock ShellExecutor mMockExecutor;
- private @Mock Lazy<Transitions> mMockTransitionsLazy;
- private @Mock CompatUIWindowManager mMockCompatLayout;
- private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
- private @Mock RestartDialogWindowManager mMockRestartDialogLayout;
- private @Mock DockStateReader mDockStateReader;
- private @Mock CompatUIConfiguration mCompatUIConfiguration;
- private @Mock CompatUIShellCommandHandler mCompatUIShellCommandHandler;
+ @Mock
+ private ShellController mMockShellController;
+ @Mock
+ private DisplayController mMockDisplayController;
+ @Mock
+ private DisplayInsetsController mMockDisplayInsetsController;
+ @Mock
+ private DisplayLayout mMockDisplayLayout;
+ @Mock
+ private DisplayImeController mMockImeController;
+ @Mock
+ private ShellTaskOrganizer.TaskListener mMockTaskListener;
+ @Mock
+ private SyncTransactionQueue mMockSyncQueue;
+ @Mock
+ private ShellExecutor mMockExecutor;
+ @Mock
+ private Lazy<Transitions> mMockTransitionsLazy;
+ @Mock
+ private CompatUIWindowManager mMockCompatLayout;
+ @Mock
+ private LetterboxEduWindowManager mMockLetterboxEduLayout;
+ @Mock
+ private RestartDialogWindowManager mMockRestartDialogLayout;
+ @Mock
+ private DockStateReader mDockStateReader;
+ @Mock
+ private CompatUIConfiguration mCompatUIConfiguration;
+ @Mock
+ private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
+
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -124,7 +144,7 @@ public class CompatUIControllerTest extends ShellTestCase {
mController = new CompatUIController(mContext, mShellInit, mMockShellController,
mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
- mCompatUIConfiguration, mCompatUIShellCommandHandler) {
+ mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
@@ -504,13 +524,175 @@ public class CompatUIControllerTest extends ShellTestCase {
.createLayout(anyBoolean());
}
+ @Test
+ public void testUpdateActiveTaskInfo_newTask_visibleAndFocused_updated() {
+ // Simulate user aspect ratio button being shown for previous task
+ mController.setHasShownUserAspectRatioSettingsButton(true);
+ Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
+
+ // Create new task
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
+ /* isFocused */ true);
+
+ // Simulate new task being shown
+ mController.updateActiveTaskInfo(taskInfo);
+
+ // Check topActivityTaskId is updated to the taskId of the new task and
+ // hasShownUserAspectRatioSettingsButton has been reset to false
+ Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId());
+ Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton());
+ }
+
+ @Test
+ public void testUpdateActiveTaskInfo_newTask_notVisibleOrFocused_notUpdated() {
+ // Create new task
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
+ /* isFocused */ true);
+
+ // Simulate task being shown
+ mController.updateActiveTaskInfo(taskInfo);
+
+ // Check topActivityTaskId is updated to the taskId of the new task and
+ // hasShownUserAspectRatioSettingsButton has been reset to false
+ Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId());
+ Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton());
+
+ // Simulate user aspect ratio button being shown
+ mController.setHasShownUserAspectRatioSettingsButton(true);
+ Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
+
+ final int newTaskId = TASK_ID + 1;
+
+ // Create visible but NOT focused task
+ final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
+ /* isFocused */ false);
+
+ // Simulate new task being shown
+ mController.updateActiveTaskInfo(taskInfo1);
+
+ // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton
+ // remains true
+ Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId());
+ Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
+
+ // Create focused but NOT visible task
+ final TaskInfo taskInfo2 = createTaskInfo(DISPLAY_ID, newTaskId,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ false,
+ /* isFocused */ true);
+
+ // Simulate new task being shown
+ mController.updateActiveTaskInfo(taskInfo2);
+
+ // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton
+ // remains true
+ Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId());
+ Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
+
+ // Create NOT focused but NOT visible task
+ final TaskInfo taskInfo3 = createTaskInfo(DISPLAY_ID, newTaskId,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ false,
+ /* isFocused */ false);
+
+ // Simulate new task being shown
+ mController.updateActiveTaskInfo(taskInfo3);
+
+ // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton
+ // remains true
+ Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId());
+ Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
+ }
+
+ @Test
+ public void testUpdateActiveTaskInfo_sameTask_notUpdated() {
+ // Create new task
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
+ /* isFocused */ true);
+
+ // Simulate new task being shown
+ mController.updateActiveTaskInfo(taskInfo);
+
+ // Check topActivityTaskId is updated to the taskId of the new task and
+ // hasShownUserAspectRatioSettingsButton has been reset to false
+ Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId());
+ Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton());
+
+ // Simulate user aspect ratio button being shown
+ mController.setHasShownUserAspectRatioSettingsButton(true);
+ Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
+
+ // Simulate same task being re-shown
+ mController.updateActiveTaskInfo(taskInfo);
+
+ // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton
+ // remains true
+ Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId());
+ Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
+ }
+
+ @Test
+ public void testUpdateActiveTaskInfo_transparentTask_notUpdated() {
+ // Create new task
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
+ /* isFocused */ true);
+
+ // Simulate new task being shown
+ mController.updateActiveTaskInfo(taskInfo);
+
+ // Check topActivityTaskId is updated to the taskId of the new task and
+ // hasShownUserAspectRatioSettingsButton has been reset to false
+ Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId());
+ Assert.assertFalse(mController.hasShownUserAspectRatioSettingsButton());
+
+ // Simulate user aspect ratio button being shown
+ mController.setHasShownUserAspectRatioSettingsButton(true);
+ Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
+
+ final int newTaskId = TASK_ID + 1;
+
+ // Create transparent task
+ final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
+ /* isFocused */ true, /* isTopActivityTransparent */ true);
+
+ // Simulate new task being shown
+ mController.updateActiveTaskInfo(taskInfo1);
+
+ // Check topActivityTaskId is NOT updated and hasShownUserAspectRatioSettingsButton
+ // remains true
+ Assert.assertEquals(TASK_ID, mController.getTopActivityTaskId());
+ Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
+ }
+
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
@CameraCompatControlState int cameraCompatControlState) {
+ return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState,
+ /* isVisible */ false, /* isFocused */ false,
+ /* isTopActivityTransparent */ false);
+ }
+
+ private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
+ @CameraCompatControlState int cameraCompatControlState, boolean isVisible,
+ boolean isFocused) {
+ return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState,
+ isVisible, isFocused, /* isTopActivityTransparent */ false);
+ }
+
+ private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
+ @CameraCompatControlState int cameraCompatControlState, boolean isVisible,
+ boolean isFocused, boolean isTopActivityTransparent) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.displayId = displayId;
taskInfo.topActivityInSizeCompat = hasSizeCompat;
taskInfo.cameraCompatControlState = cameraCompatControlState;
+ taskInfo.isVisible = isVisible;
+ taskInfo.isFocused = isFocused;
+ taskInfo.isTopActivityTransparent = isTopActivityTransparent;
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 5f294d53b662..3bce2b824e28 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -44,7 +44,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import junit.framework.Assert;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 78c3cbdaace6..4c837e635939 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -53,7 +53,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import junit.framework.Assert;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index 973a99c269ea..5867a8553d53 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -40,6 +40,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.BiConsumer;
+
/**
* Tests for {@link ReachabilityEduWindowManager}.
*
@@ -57,6 +59,8 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
private CompatUIConfiguration mCompatUIConfiguration;
@Mock
private DisplayLayout mDisplayLayout;
+ @Mock
+ private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback;
private TestShellExecutor mExecutor;
private TaskInfo mTaskInfo;
private ReachabilityEduWindowManager mWindowManager;
@@ -104,6 +108,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) {
return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue,
- mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor);
+ mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor,
+ mOnDismissCallback, flags -> 0);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
new file mode 100644
index 000000000000..f460d1b09e34
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
+import android.content.ComponentName;
+import android.testing.AndroidTestingRunner;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.SurfaceControlViewHost;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Tests for {@link UserAspectRatioSettingsLayout}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:UserAspectRatioSettingsLayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
+
+ private static final int TASK_ID = 1;
+
+ @Mock
+ private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock
+ private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener>
+ mOnUserAspectRatioSettingsButtonClicked;
+ @Mock
+ private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock
+ private SurfaceControlViewHost mViewHost;
+ @Captor
+ private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor;
+ @Captor
+ private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor;
+
+ private UserAspectRatioSettingsWindowManager mWindowManager;
+ private UserAspectRatioSettingsLayout mLayout;
+ private TaskInfo mTaskInfo;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
+ mSyncTransactionQueue, mTaskListener, new DisplayLayout(),
+ new CompatUIController.CompatUIHintsState(),
+ mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor(), flags -> 0,
+ () -> false, s -> {});
+
+ mLayout = (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.user_aspect_ratio_settings_layout, null);
+ mLayout.inject(mWindowManager);
+
+ spyOn(mWindowManager);
+ spyOn(mLayout);
+ doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+ doReturn(mLayout).when(mWindowManager).inflateLayout();
+ }
+
+ @Test
+ public void testOnClickForUserAspectRatioSettingsButton() {
+ final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
+ button.performClick();
+
+ verify(mWindowManager).onUserAspectRatioSettingsButtonClicked();
+ verify(mOnUserAspectRatioSettingsButtonClicked).accept(
+ mUserAspectRationTaskInfoCaptor.capture(),
+ mUserAspectRatioTaskListenerCaptor.capture());
+ final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result =
+ new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(),
+ mUserAspectRatioTaskListenerCaptor.getValue());
+ Assert.assertEquals(mTaskInfo, result.first);
+ Assert.assertEquals(mTaskListener, result.second);
+ }
+
+ @Test
+ public void testOnLongClickForUserAspectRatioButton() {
+ doNothing().when(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
+
+ final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
+ button.performLongClick();
+
+ verify(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
+ }
+
+ @Test
+ public void testOnClickForUserAspectRatioSettingsHint() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+ final LinearLayout sizeCompatHint = mLayout.findViewById(
+ R.id.user_aspect_ratio_settings_hint);
+ sizeCompatHint.performClick();
+
+ verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ false);
+ }
+
+ private static TaskInfo createTaskInfo(boolean hasSizeCompat,
+ @CameraCompatControlState int cameraCompatControlState) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = TASK_ID;
+ taskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.cameraCompatControlState = cameraCompatControlState;
+ taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
new file mode 100644
index 000000000000..5a4d6c812c17
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.view.WindowInsets.Type.navigationBars;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.util.Pair;
+import android.view.DisplayInfo;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+/**
+ * Tests for {@link UserAspectRatioSettingsWindowManager}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:UserAspectRatioSettingsWindowManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
+
+ private static final int TASK_ID = 1;
+
+ @Mock private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock
+ private Supplier<Boolean> mUserAspectRatioButtonShownChecker;
+ @Mock
+ private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener>
+ mOnUserAspectRatioSettingsButtonClicked;
+ @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock private UserAspectRatioSettingsLayout mLayout;
+ @Mock private SurfaceControlViewHost mViewHost;
+ @Captor
+ private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor;
+ @Captor
+ private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor;
+
+ private final Set<String> mPackageNameCache = new HashSet<>();
+
+ private UserAspectRatioSettingsWindowManager mWindowManager;
+ private TaskInfo mTaskInfo;
+
+ private TestShellExecutor mExecutor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mExecutor = new TestShellExecutor();
+ mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ false, /* topActivityBoundsLetterboxed */ true);
+ mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
+ mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0,
+ mUserAspectRatioButtonShownChecker, s -> {});
+ spyOn(mWindowManager);
+ doReturn(mLayout).when(mWindowManager).inflateLayout();
+ doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+ doReturn(false).when(mUserAspectRatioButtonShownChecker).get();
+ }
+
+ @Test
+ public void testCreateUserAspectRatioButton() {
+ // Doesn't create layout if show is false.
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ false));
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Doesn't create hint popup.
+ mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager).inflateLayout();
+ mExecutor.flushAll();
+ verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true);
+ verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+
+ // Creates hint popup.
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ mWindowManager.release();
+ mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = false;
+ assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager).inflateLayout();
+ assertNotNull(mLayout);
+ mExecutor.flushAll();
+ verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true);
+ verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ assertTrue(mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint);
+
+ // Returns false and doesn't create layout if mHasUserAspectRatioSettingsButton is false.
+ clearInvocations(mWindowManager);
+ mWindowManager.release();
+ mWindowManager.mHasUserAspectRatioSettingsButton = false;
+ assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
+ verify(mWindowManager, never()).inflateLayout();
+ }
+
+ @Test
+ public void testRelease() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+
+ mWindowManager.release();
+
+ verify(mViewHost).release();
+ }
+
+ @Test
+ public void testUpdateCompatInfo() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+
+ // No diff
+ clearInvocations(mWindowManager);
+ TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
+
+ verify(mWindowManager, never()).updateSurfacePosition();
+ verify(mWindowManager, never()).release();
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+
+
+ // Change task listener, recreate button.
+ clearInvocations(mWindowManager);
+ final ShellTaskOrganizer.TaskListener newTaskListener = mock(
+ ShellTaskOrganizer.TaskListener.class);
+ assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+ verify(mWindowManager).release();
+ verify(mWindowManager).createLayout(/* canShow= */ true);
+
+ // Change has eligibleForUserAspectRatioButton to false, dispose the component
+ clearInvocations(mWindowManager);
+ clearInvocations(mLayout);
+ taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ false, /* topActivityBoundsLetterboxed */ true);
+ assertFalse(
+ mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+ verify(mWindowManager).release();
+ }
+
+ @Test
+ public void testUpdateCompatInfoLayoutNotInflatedYet() {
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.createLayout(/* canShow= */ false);
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Change topActivityInSizeCompat to false and pass canShow true, layout shouldn't be
+ // inflated
+ clearInvocations(mWindowManager);
+ TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ false, /* topActivityBoundsLetterboxed */ true);
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated.
+ clearInvocations(mWindowManager);
+ taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true);
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+ }
+
+ @Test
+ public void testUpdateDisplayLayout() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+
+ mWindowManager.updateDisplayLayout(displayLayout1);
+ verify(mWindowManager).updateSurfacePosition();
+
+ // No update if the display bounds is the same.
+ clearInvocations(mWindowManager);
+ final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+ mWindowManager.updateDisplayLayout(displayLayout2);
+ verify(mWindowManager, never()).updateSurfacePosition();
+ }
+
+ @Test
+ public void testUpdateDisplayLayoutInsets() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ final DisplayLayout displayLayout = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+
+ mWindowManager.updateDisplayLayout(displayLayout);
+ verify(mWindowManager).updateSurfacePosition();
+
+ // Update if the insets change on the existing display layout
+ clearInvocations(mWindowManager);
+ InsetsState insetsState = new InsetsState();
+ insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000));
+ InsetsSource insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ insetsSource.setFrame(0, 1800, 1000, 2000);
+ insetsState.addSource(insetsSource);
+ displayLayout.setInsets(mContext.getResources(), insetsState);
+ mWindowManager.updateDisplayLayout(displayLayout);
+ verify(mWindowManager).updateSurfacePosition();
+ }
+
+ @Test
+ public void testUpdateVisibility() {
+ // Create button if it is not created.
+ mWindowManager.removeLayout();
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.updateVisibility(/* canShow= */ true);
+
+ verify(mWindowManager).createLayout(/* canShow= */ true);
+
+ // Hide button.
+ clearInvocations(mWindowManager);
+ doReturn(View.VISIBLE).when(mLayout).getVisibility();
+ mWindowManager.updateVisibility(/* canShow= */ false);
+
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+ verify(mLayout).setVisibility(View.GONE);
+
+ // Show button.
+ doReturn(View.GONE).when(mLayout).getVisibility();
+ mWindowManager.updateVisibility(/* canShow= */ true);
+
+ verify(mWindowManager, never()).createLayout(anyBoolean());
+ verify(mLayout).setVisibility(View.VISIBLE);
+ }
+
+ @Test
+ public void testLayoutHasUserAspectRatioSettingsButton() {
+ clearInvocations(mWindowManager);
+ spyOn(mWindowManager);
+ TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true);
+
+ // User aspect ratio settings button has not yet been shown.
+ doReturn(false).when(mUserAspectRatioButtonShownChecker).get();
+
+ // Check the layout has the user aspect ratio settings button.
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+ assertTrue(mWindowManager.mHasUserAspectRatioSettingsButton);
+
+ // User aspect ratio settings button has been shown and is still visible.
+ spyOn(mWindowManager);
+ doReturn(true).when(mWindowManager).isShowingButton();
+ doReturn(true).when(mUserAspectRatioButtonShownChecker).get();
+
+ // Check the layout still has the user aspect ratio settings button.
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+ assertTrue(mWindowManager.mHasUserAspectRatioSettingsButton);
+
+ // User aspect ratio settings button has been shown and has timed out so is no longer
+ // visible.
+ doReturn(false).when(mWindowManager).isShowingButton();
+ doReturn(true).when(mUserAspectRatioButtonShownChecker).get();
+
+ // Check the layout no longer has the user aspect ratio button.
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+ assertFalse(mWindowManager.mHasUserAspectRatioSettingsButton);
+ }
+
+ @Test
+ public void testAttachToParentSurface() {
+ final SurfaceControl.Builder b = new SurfaceControl.Builder();
+ mWindowManager.attachToParentSurface(b);
+
+ verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b);
+ }
+
+ @Test
+ public void testOnUserAspectRatioButtonClicked() {
+ mWindowManager.onUserAspectRatioSettingsButtonClicked();
+
+ verify(mOnUserAspectRatioSettingsButtonClicked).accept(
+ mUserAspectRationTaskInfoCaptor.capture(),
+ mUserAspectRatioTaskListenerCaptor.capture());
+ final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result =
+ new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(),
+ mUserAspectRatioTaskListenerCaptor.getValue());
+ Assert.assertEquals(mTaskInfo, result.first);
+ Assert.assertEquals(mTaskListener, result.second);
+ }
+
+ @Test
+ public void testOnUserAspectRatioButtonLongClicked_showHint() {
+ // Not create hint popup.
+ mWindowManager.mHasUserAspectRatioSettingsButton = true;
+ mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+ mWindowManager.createLayout(/* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+ verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+
+ mWindowManager.onUserAspectRatioSettingsButtonLongClicked();
+
+ verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+ }
+
+ @Test
+ public void testWhenDockedStateHasChanged_needsToBeRecreated() {
+ ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
+ newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
+
+ Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
+ }
+
+ private static TaskInfo createTaskInfo(boolean eligibleForUserAspectRatioButton,
+ boolean topActivityBoundsLetterboxed) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = TASK_ID;
+ taskInfo.topActivityEligibleForUserAspectRatioButton = eligibleForUserAspectRatioButton;
+ taskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed;
+ taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
+ taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index d6387ee5ae13..605a762a395f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -59,6 +59,7 @@ import android.window.WindowContainerTransaction.HierarchyOp;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.MockToken;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 3bc2f0e8674e..3fe78efdf2b1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -129,6 +129,18 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
}
@Test
+ fun addListener_notifiesStashed() {
+ repo.setStashed(DEFAULT_DISPLAY, true)
+ val listener = TestVisibilityListener()
+ val executor = TestShellExecutor()
+ repo.addVisibleTasksListener(listener, executor)
+ executor.flushAll()
+
+ assertThat(listener.stashedOnDefaultDisplay).isTrue()
+ assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
+ }
+
+ @Test
fun addListener_tasksOnDifferentDisplay_doesNotNotify() {
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
val listener = TestVisibilityListener()
@@ -313,6 +325,65 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
assertThat(tasks.first()).isEqualTo(6)
}
+ @Test
+ fun setStashed_stateIsUpdatedForTheDisplay() {
+ repo.setStashed(DEFAULT_DISPLAY, true)
+ assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue()
+ assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse()
+
+ repo.setStashed(DEFAULT_DISPLAY, false)
+ assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse()
+ }
+
+ @Test
+ fun setStashed_notifyListener() {
+ val listener = TestVisibilityListener()
+ val executor = TestShellExecutor()
+ repo.addVisibleTasksListener(listener, executor)
+ repo.setStashed(DEFAULT_DISPLAY, true)
+ executor.flushAll()
+ assertThat(listener.stashedOnDefaultDisplay).isTrue()
+ assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
+
+ repo.setStashed(DEFAULT_DISPLAY, false)
+ executor.flushAll()
+ assertThat(listener.stashedOnDefaultDisplay).isFalse()
+ assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2)
+ }
+
+ @Test
+ fun setStashed_secondCallDoesNotNotify() {
+ val listener = TestVisibilityListener()
+ val executor = TestShellExecutor()
+ repo.addVisibleTasksListener(listener, executor)
+ repo.setStashed(DEFAULT_DISPLAY, true)
+ repo.setStashed(DEFAULT_DISPLAY, true)
+ executor.flushAll()
+ assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
+ }
+
+ @Test
+ fun setStashed_tracksPerDisplay() {
+ val listener = TestVisibilityListener()
+ val executor = TestShellExecutor()
+ repo.addVisibleTasksListener(listener, executor)
+
+ repo.setStashed(DEFAULT_DISPLAY, true)
+ executor.flushAll()
+ assertThat(listener.stashedOnDefaultDisplay).isTrue()
+ assertThat(listener.stashedOnSecondaryDisplay).isFalse()
+
+ repo.setStashed(SECOND_DISPLAY, true)
+ executor.flushAll()
+ assertThat(listener.stashedOnDefaultDisplay).isTrue()
+ assertThat(listener.stashedOnSecondaryDisplay).isTrue()
+
+ repo.setStashed(DEFAULT_DISPLAY, false)
+ executor.flushAll()
+ assertThat(listener.stashedOnDefaultDisplay).isFalse()
+ assertThat(listener.stashedOnSecondaryDisplay).isTrue()
+ }
+
class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
@@ -332,6 +403,12 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
var visibleChangesOnDefaultDisplay = 0
var visibleChangesOnSecondaryDisplay = 0
+ var stashedOnDefaultDisplay = false
+ var stashedOnSecondaryDisplay = false
+
+ var stashedChangesOnDefaultDisplay = 0
+ var stashedChangesOnSecondaryDisplay = 0
+
override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
when (displayId) {
DEFAULT_DISPLAY -> {
@@ -345,6 +422,20 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
else -> fail("Visible task listener received unexpected display id: $displayId")
}
}
+
+ override fun onStashedChanged(displayId: Int, stashed: Boolean) {
+ when (displayId) {
+ DEFAULT_DISPLAY -> {
+ stashedOnDefaultDisplay = stashed
+ stashedChangesOnDefaultDisplay++
+ }
+ SECOND_DISPLAY -> {
+ stashedOnSecondaryDisplay = stashed
+ stashedChangesOnDefaultDisplay++
+ }
+ else -> fail("Visible task listener received unexpected display id: $displayId")
+ }
+ }
}
companion object {
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 1335ebf105a6..5d87cf8b25a6 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
@@ -39,21 +39,25 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
+import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.After
@@ -77,6 +81,7 @@ import org.mockito.Mockito.`when` as whenever
class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var shellCommandHandler: ShellCommandHandler
@Mock lateinit var shellController: ShellController
@Mock lateinit var displayController: DisplayController
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@@ -85,12 +90,17 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var transitions: Transitions
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
+ @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
+ ToggleResizeDesktopTaskTransitionHandler
+ @Mock lateinit var launchAdjacentController: LaunchAdjacentController
+ @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
+ private val shellExecutor = TestShellExecutor()
// Mock running tasks are registered here so we can get the list from mock shell task organizer
private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -114,6 +124,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
return DesktopTasksController(
context,
shellInit,
+ shellCommandHandler,
shellController,
displayController,
shellTaskOrganizer,
@@ -122,8 +133,10 @@ class DesktopTasksControllerTest : ShellTestCase() {
transitions,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
+ mToggleResizeDesktopTaskTransitionHandler,
desktopModeTaskRepository,
- TestShellExecutor()
+ launchAdjacentController,
+ shellExecutor
)
}
@@ -262,17 +275,28 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun moveToDesktop() {
+ fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
- controller.moveToDesktop(task)
- val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+ task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
+ controller.moveToDesktop(desktopModeWindowDecoration, task)
+ val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
}
@Test
+ fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
+ val task = setUpFullscreenTask()
+ task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+ controller.moveToDesktop(desktopModeWindowDecoration, task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
fun moveToDesktop_nonExistentTask_doesNothing() {
- controller.moveToDesktop(999)
+ controller.moveToDesktop(desktopModeWindowDecoration, 999)
verifyWCTNotExecuted()
}
@@ -283,9 +307,9 @@ class DesktopTasksControllerTest : ShellTestCase() {
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
- controller.moveToDesktop(fullscreenTask)
+ controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTask)
- with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ with(getLatestMoveToDesktopWct()) {
// Operations should include home task, freeform task
assertThat(hierarchyOps).hasSize(3)
assertReorderSequence(homeTask, freeformTask, fullscreenTask)
@@ -305,9 +329,9 @@ class DesktopTasksControllerTest : ShellTestCase() {
val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
markTaskHidden(freeformTaskSecond)
- controller.moveToDesktop(fullscreenTaskDefault)
+ controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTaskDefault)
- with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ with(getLatestMoveToDesktopWct()) {
// Check that hierarchy operations do not include tasks from second display
assertThat(hierarchyOps.map { it.container })
.doesNotContain(homeTaskSecond.token.asBinder())
@@ -317,12 +341,23 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun moveToFullscreen() {
+ fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
+ task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
controller.moveToFullscreen(task)
val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
+ val task = setUpFreeformTask()
+ task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+ controller.moveToFullscreen(task)
+ val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
@Test
@@ -345,6 +380,18 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun moveTaskToFront_postsWctWithReorderOp() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ controller.moveTaskToFront(task1)
+
+ val wct = getLatestWct(expectTransition = TRANSIT_TO_FRONT)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, task1)
+ }
+
+ @Test
fun moveToNextDisplay_noOtherDisplays() {
whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -451,6 +498,28 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
+ val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
+ markTaskHidden(stashedFreeformTask)
+
+ val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY)
+
+ controller.stashDesktopApps(DEFAULT_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+ assertThat(result).isNotNull()
+ result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask)
+ assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+
+ // Stashed state should be cleared
+ assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
+ }
+
+ @Test
fun handleRequest_freeformTask_freeformVisible_returnNull() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -501,6 +570,26 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
+ val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
+ markTaskHidden(stashedFreeformTask)
+
+ val freeformTask = createFreeformTask(DEFAULT_DISPLAY)
+
+ controller.stashDesktopApps(DEFAULT_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+ assertThat(result).isNotNull()
+ result?.assertReorderSequence(stashedFreeformTask, freeformTask)
+
+ // Stashed state should be cleared
+ assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
+ }
+
+ @Test
fun handleRequest_notOpenOrToFrontTransition_returnNull() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -539,6 +628,50 @@ class DesktopTasksControllerTest : ShellTestCase() {
assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
}
+ @Test
+ fun stashDesktopApps_stateUpdates() {
+ whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
+ controller.stashDesktopApps(DEFAULT_DISPLAY)
+
+ assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue()
+ assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse()
+ }
+
+ @Test
+ fun hideStashedDesktopApps_stateUpdates() {
+ whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
+ desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true)
+ desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true)
+ controller.hideStashedDesktopApps(DEFAULT_DISPLAY)
+
+ assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
+ // Check that second display is not affected
+ assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue()
+ }
+
+ @Test
+ fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() {
+ val task = setUpFreeformTask()
+ clearInvocations(launchAdjacentController)
+
+ markTaskVisible(task)
+ shellExecutor.flushAll()
+ verify(launchAdjacentController).launchAdjacentEnabled = false
+ }
+
+ @Test
+ fun desktopTasksVisibilityChange_invisible_setLaunchAdjacentEnabled() {
+ val task = setUpFreeformTask()
+ markTaskVisible(task)
+ clearInvocations(launchAdjacentController)
+
+ markTaskHidden(task)
+ shellExecutor.flushAll()
+ verify(launchAdjacentController).launchAdjacentEnabled = true
+ }
+
private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFreeformTask(displayId)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
@@ -590,6 +723,16 @@ class DesktopTasksControllerTest : ShellTestCase() {
return arg.value
}
+ private fun getLatestMoveToDesktopWct(): WindowContainerTransaction {
+ val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ if (ENABLE_SHELL_TRANSITIONS) {
+ verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
+ } else {
+ verify(shellTaskOrganizer).applyTransaction(arg.capture())
+ }
+ return arg.value
+ }
+
private fun verifyWCTNotExecuted() {
if (ENABLE_SHELL_TRANSITIONS) {
verify(transitions, never()).startTransition(anyInt(), any(), isNull())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index cf1ff3214d87..29a757c19d98 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.view.Display.DEFAULT_DISPLAY
+import com.android.wm.shell.MockToken
import com.android.wm.shell.TestRunningTaskInfoBuilder
class DesktopTestHelpers {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index 8592dea19289..772d97d8eb32 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -41,6 +42,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
import junit.framework.AssertionFailedError;
@@ -73,6 +75,10 @@ public class EnterDesktopTaskTransitionHandlerTest {
ShellExecutor mExecutor;
@Mock
SurfaceControl mSurfaceControl;
+ @Mock
+ MoveToDesktopAnimator mMoveToDesktopAnimator;
+ @Mock
+ PointF mPosition;
private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler;
@@ -82,6 +88,7 @@ public class EnterDesktopTaskTransitionHandlerTest {
doReturn(mExecutor).when(mTransitions).getMainExecutor();
doReturn(mAnimationT).when(mTransactionFactory).get();
+ doReturn(mPosition).when(mMoveToDesktopAnimator).getPosition();
mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions,
mTransactionFactory);
@@ -89,16 +96,20 @@ public class EnterDesktopTaskTransitionHandlerTest {
@Test
public void testEnterFreeformAnimation() {
- final int transitionType = Transitions.TRANSIT_ENTER_FREEFORM;
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
- .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
- mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+ .startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct,
+ mEnterDesktopTaskTransitionHandler);
+ doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
+
+ mEnterDesktopTaskTransitionHandler.startMoveToDesktop(wct,
+ mMoveToDesktopAnimator, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
- TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_FREEFORM, change);
+ TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE,
+ change);
assertTrue(mEnterDesktopTaskTransitionHandler
@@ -110,17 +121,18 @@ public class EnterDesktopTaskTransitionHandlerTest {
@Test
public void testTransitEnterDesktopModeAnimation() throws Throwable {
- final int transitionType = Transitions.TRANSIT_ENTER_DESKTOP_MODE;
+ final int transitionType = Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE;
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
.startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
- mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+ mEnterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
change.setEndAbsBounds(new Rect(0, 0, 1, 1));
- TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_DESKTOP_MODE, change);
+ TransitionInfo info = createTransitionInfo(
+ Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, change);
runOnUiThread(() -> {
try {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 7c1da35888b8..527dc0149716 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -133,8 +133,7 @@ public class DragAndDropPolicyTest extends ShellTestCase {
mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false);
mInsets = Insets.of(0, 0, 0, 0);
- mPolicy = spy(new DragAndDropPolicy(
- mContext, mActivityTaskManager, mSplitScreenStarter, mSplitScreenStarter));
+ mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter));
mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
setClipDataResizeable(mNonResizeableActivityClipData, false);
@@ -206,7 +205,10 @@ public class DragAndDropPolicyTest extends ShellTestCase {
@Test
public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
setRunningTask(mHomeTask);
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+ DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+ mLandscapeDisplayLayout, mActivityClipData);
+ dragSession.update();
+ mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
@@ -218,7 +220,10 @@ public class DragAndDropPolicyTest extends ShellTestCase {
@Test
public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
setRunningTask(mFullscreenAppTask);
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+ DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+ mLandscapeDisplayLayout, mActivityClipData);
+ dragSession.update();
+ mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
@@ -235,7 +240,10 @@ public class DragAndDropPolicyTest extends ShellTestCase {
@Test
public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
setRunningTask(mFullscreenAppTask);
- mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId);
+ DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+ mPortraitDisplayLayout, mActivityClipData);
+ dragSession.update();
+ mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
@@ -252,7 +260,10 @@ public class DragAndDropPolicyTest extends ShellTestCase {
@Test
public void testTargetHitRects() {
setRunningTask(mFullscreenAppTask);
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+ DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+ mLandscapeDisplayLayout, mActivityClipData);
+ dragSession.update();
+ mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = mPolicy.getTargets(mInsets);
for (Target t : targets) {
assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.top) == t);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index addc2338144f..46259a8b177f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -32,7 +32,13 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Before;
import org.junit.Test;
@@ -60,7 +66,8 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private DisplayInfo mDefaultDisplayInfo;
- private PipBoundsState mPipBoundsState; private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipBoundsState mPipBoundsState;
+ private SizeSpecSource mSizeSpecSource;
private PipDisplayLayoutState mPipDisplayLayoutState;
@@ -68,11 +75,12 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
public void setUp() throws Exception {
initializeMockResources();
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
- mPipSizeSpecHandler);
+ mPipDisplayLayoutState, mSizeSpecSource);
DisplayLayout layout =
new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
@@ -132,7 +140,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
@Test
public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
- final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO);
+ final Size defaultSize = mSizeSpecSource.getDefaultSize(DEFAULT_ASPECT_RATIO);
mPipBoundsState.setOverrideMinSize(null);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index f32000445ca9..d34e27b57071 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -35,7 +35,10 @@ import androidx.test.filters.SmallTest;
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Before;
import org.junit.Test;
@@ -58,6 +61,7 @@ public class PipBoundsStateTest extends ShellTestCase {
private static final int OVERRIDABLE_MIN_SIZE = 40;
private PipBoundsState mPipBoundsState;
+ private SizeSpecSource mSizeSpecSource;
private ComponentName mTestComponentName1;
private ComponentName mTestComponentName2;
@@ -69,8 +73,8 @@ public class PipBoundsStateTest extends ShellTestCase {
OVERRIDABLE_MIN_SIZE);
PipDisplayLayoutState pipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipBoundsState = new PipBoundsState(mContext,
- new PipSizeSpecHandler(mContext, pipDisplayLayoutState), pipDisplayLayoutState);
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, pipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, pipDisplayLayoutState);
mTestComponentName1 = new ComponentName(mContext, "component1");
mTestComponentName2 = new ComponentName(mContext, "component2");
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
index b9226d2b9b91..ac13d7ffcd61 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
@@ -25,6 +25,8 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import org.junit.Before;
import org.junit.Test;
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 842c699fa42d..4e2b7f6d16b2 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
@@ -52,8 +52,15 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import org.junit.Before;
@@ -87,7 +94,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
private PipBoundsState mPipBoundsState;
private PipTransitionState mPipTransitionState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private SizeSpecSource mSizeSpecSource;
private PipDisplayLayoutState mPipDisplayLayoutState;
private ComponentName mComponent1;
@@ -99,12 +106,12 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2");
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
- mPipSizeSpecHandler);
+ mPipDisplayLayoutState, mSizeSpecSource);
mMainExecutor = new TestShellExecutor();
mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue,
mPipTransitionState, mPipBoundsState, mPipDisplayLayoutState,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
index cc9e26b2c4f1..8c7b47ea7d84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
@@ -29,8 +29,9 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
index 1379aedc2284..3d5cd6939d1b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
@@ -32,7 +32,9 @@ import android.view.DisplayInfo;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.After;
import org.junit.Assert;
@@ -47,10 +49,10 @@ import java.util.Map;
import java.util.function.Function;
/**
- * Unit test against {@link PipSizeSpecHandler} with feature flag on.
+ * Unit test against {@link PhoneSizeSpecSource}
*/
@RunWith(AndroidTestingRunner.class)
-public class PipSizeSpecHandlerTest extends ShellTestCase {
+public class PhoneSizeSpecSourceTest extends ShellTestCase {
/** A sample overridden min edge size. */
private static final int OVERRIDE_MIN_EDGE_SIZE = 40;
/** A sample default min edge size */
@@ -75,7 +77,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
@Mock private Resources mResources;
private PipDisplayLayoutState mPipDisplayLayoutState;
- private TestPipSizeSpecHandler mPipSizeSpecHandler;
+ private SizeSpecSource mSizeSpecSource;
/**
* Sets up static Mockito session for SystemProperties and mocks necessary static methods.
@@ -110,17 +112,17 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
sExpectedDefaultSizes.put(16f / 9, new Size(600, 338));
sExpectedMinSizes.put(16f / 9, new Size(501, 282));
- sExpectedMaxSizes.put(4f / 3, new Size(892, 669));
- sExpectedDefaultSizes.put(4f / 3, new Size(535, 401));
+ sExpectedMaxSizes.put(4f / 3, new Size(893, 670));
+ sExpectedDefaultSizes.put(4f / 3, new Size(536, 402));
sExpectedMinSizes.put(4f / 3, new Size(447, 335));
- sExpectedMaxSizes.put(3f / 4, new Size(669, 892));
- sExpectedDefaultSizes.put(3f / 4, new Size(401, 535));
+ sExpectedMaxSizes.put(3f / 4, new Size(670, 893));
+ sExpectedDefaultSizes.put(3f / 4, new Size(402, 536));
sExpectedMinSizes.put(3f / 4, new Size(335, 447));
- sExpectedMaxSizes.put(9f / 16, new Size(562, 999));
- sExpectedDefaultSizes.put(9f / 16, new Size(337, 599));
- sExpectedMinSizes.put(9f / 16, new Size(281, 500));
+ sExpectedMaxSizes.put(9f / 16, new Size(563, 1001));
+ sExpectedDefaultSizes.put(9f / 16, new Size(338, 601));
+ sExpectedMinSizes.put(9f / 16, new Size(282, 501));
}
private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
@@ -158,10 +160,10 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
mPipDisplayLayoutState.setDisplayLayout(displayLayout);
setUpStaticSystemPropertiesSession();
- mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
// no overridden min edge size by default
- mPipSizeSpecHandler.setOverrideMinSize(null);
+ mSizeSpecSource.setOverrideMinSize(null);
}
@After
@@ -172,19 +174,19 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
@Test
public void testGetMaxSize() {
forEveryTestCaseCheck(sExpectedMaxSizes,
- (aspectRatio) -> mPipSizeSpecHandler.getMaxSize(aspectRatio));
+ (aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio));
}
@Test
public void testGetDefaultSize() {
forEveryTestCaseCheck(sExpectedDefaultSizes,
- (aspectRatio) -> mPipSizeSpecHandler.getDefaultSize(aspectRatio));
+ (aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio));
}
@Test
public void testGetMinSize() {
forEveryTestCaseCheck(sExpectedMinSizes,
- (aspectRatio) -> mPipSizeSpecHandler.getMinSize(aspectRatio));
+ (aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio));
}
@Test
@@ -192,8 +194,8 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
// an initial size with 16:9 aspect ratio
Size initSize = new Size(600, 337);
- Size expectedSize = new Size(337, 599);
- Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+ Size expectedSize = new Size(338, 601);
+ Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16);
Assert.assertEquals(expectedSize, actualSize);
}
@@ -201,26 +203,12 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
@Test
public void testGetSizeForAspectRatio_withOverrideMinSize() {
// an initial size with a 1:1 aspect ratio
- mPipSizeSpecHandler.setOverrideMinSize(new Size(OVERRIDE_MIN_EDGE_SIZE,
- OVERRIDE_MIN_EDGE_SIZE));
- // make sure initial size is same as override min size
- Size initSize = mPipSizeSpecHandler.getOverrideMinSize();
+ Size initSize = new Size(OVERRIDE_MIN_EDGE_SIZE, OVERRIDE_MIN_EDGE_SIZE);
+ mSizeSpecSource.setOverrideMinSize(initSize);
Size expectedSize = new Size(40, 71);
- Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+ Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16);
Assert.assertEquals(expectedSize, actualSize);
}
-
- static class TestPipSizeSpecHandler extends PipSizeSpecHandler {
-
- TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) {
- super(context, displayLayoutState);
- }
-
- @Override
- boolean supportsPipSizeLargeScreen() {
- return true;
- }
- }
}
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 85167cb97501..4eb519334e12 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
@@ -55,15 +55,16 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
@@ -76,7 +77,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
@@ -109,7 +109,6 @@ public class PipControllerTest extends ShellTestCase {
@Mock private PipMotionHelper mMockPipMotionHelper;
@Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
@Mock private PipBoundsState mMockPipBoundsState;
- @Mock private PipSizeSpecHandler mMockPipSizeSpecHandler;
@Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
@Mock private TaskStackListenerImpl mMockTaskStackListener;
@Mock private ShellExecutor mMockExecutor;
@@ -134,7 +133,7 @@ public class PipControllerTest extends ShellTestCase {
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+ mMockPipBoundsState, mMockPipDisplayLayoutState,
mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
@@ -226,7 +225,7 @@ public class PipControllerTest extends ShellTestCase {
assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+ mMockPipBoundsState, mMockPipDisplayLayoutState,
mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
@@ -330,21 +329,7 @@ public class PipControllerTest extends ShellTestCase {
}
@Test
- public void onKeepClearAreasChanged_featureDisabled_pipBoundsStateDoesntChange() {
- mPipController.setEnablePipKeepClearAlgorithm(false);
- final int displayId = 1;
- final Rect keepClearArea = new Rect(0, 0, 10, 10);
- when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
-
- mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
- displayId, Set.of(keepClearArea), Set.of());
-
- verify(mMockPipBoundsState, never()).setKeepClearAreas(Mockito.anySet(), Mockito.anySet());
- }
-
- @Test
- public void onKeepClearAreasChanged_featureEnabled_updatesPipBoundsState() {
- mPipController.setEnablePipKeepClearAlgorithm(true);
+ public void onKeepClearAreasChanged_updatesPipBoundsState() {
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
index 8ce3ca4bdc00..0f8db85dcef4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
@@ -29,7 +29,7 @@ import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipBoundsState;
import org.junit.Assert;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 1dfdbf6514ba..6777a5bd8ceb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -36,14 +36,16 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipUiEventLogger;
import org.junit.Before;
import org.junit.Test;
@@ -87,7 +89,7 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
private PipBoundsState mPipBoundsState;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private SizeSpecSource mSizeSpecSource;
private PipDisplayLayoutState mPipDisplayLayoutState;
@@ -97,13 +99,14 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
new PipKeepClearAlgorithmInterface() {};
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
- mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipSizeSpecHandler);
+ mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
+ mSizeSpecSource);
final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
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 10b1ddf1b868..9aaabd130527 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
@@ -18,7 +18,6 @@ package com.android.wm.shell.pip.phone;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -33,14 +32,16 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
@@ -92,7 +93,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
private PipSnapAlgorithm mPipSnapAlgorithm;
private PipMotionHelper mMotionHelper;
private PipResizeGestureHandler mPipResizeGestureHandler;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private SizeSpecSource mSizeSpecSource;
private PipDisplayLayoutState mPipDisplayLayoutState;
private DisplayLayout mDisplayLayout;
@@ -108,16 +109,16 @@ public class PipTouchHandlerTest extends ShellTestCase {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+ mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
- new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler);
+ new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
- mPipBoundsAlgorithm, mPipBoundsState, mPipSizeSpecHandler, mPipTaskOrganizer,
+ mPipBoundsAlgorithm, mPipBoundsState, mSizeSpecSource, mPipTaskOrganizer,
pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
// We aren't actually using ShellInit, so just call init directly
mPipTouchHandler.onInit();
@@ -162,8 +163,8 @@ public class PipTouchHandlerTest extends ShellTestCase {
// getting the expected min and max size
float aspectRatio = (float) mPipBounds.width() / mPipBounds.height();
- Size expectedMinSize = mPipSizeSpecHandler.getMinSize(aspectRatio);
- Size expectedMaxSize = mPipSizeSpecHandler.getMaxSize(aspectRatio);
+ Size expectedMinSize = mSizeSpecSource.getMinSize(aspectRatio);
+ Size expectedMaxSize = mSizeSpecSource.getMaxSize(aspectRatio);
assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds());
verify(mPipResizeGestureHandler, times(1))
@@ -172,16 +173,4 @@ public class PipTouchHandlerTest extends ShellTestCase {
verify(mPipResizeGestureHandler, times(1))
.updateMaxSize(expectedMaxSize.getWidth(), expectedMaxSize.getHeight());
}
-
- @Test
- public void updateMovementBounds_withImeAdjustment_movesPip() {
- mPipTouchHandler.setEnablePipKeepClearAlgorithm(false);
- mFromImeAdjustment = true;
- mPipTouchHandler.onImeVisibilityChanged(true /* imeVisible */, mImeHeight);
-
- mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds,
- mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
-
- verify(mMotionHelper, times(1)).animateToOffset(any(), anyInt());
- }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
index 02e6b8c71663..c40cd4069cab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -37,7 +37,7 @@ import android.testing.TestableLooper;
import android.util.Log;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipMediaController;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
index 7370ed71bbdd..94f2b9131ebd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
@@ -25,7 +25,7 @@ import android.testing.AndroidTestingRunner
import com.android.wm.shell.R
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
import com.android.wm.shell.pip.tv.TvPipBoundsController.POSITION_DEBOUNCE_TIMEOUT_MILLIS
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index f9b772345b14..82fe5f2f6afc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -26,9 +26,10 @@ import static org.junit.Assert.assertEquals;
import android.view.Gravity;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Before;
import org.junit.Test;
@@ -47,7 +48,7 @@ public class TvPipGravityTest extends ShellTestCase {
private TvPipBoundsState mTvPipBoundsState;
private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private SizeSpecSource mSizeSpecSource;
private PipDisplayLayoutState mPipDisplayLayoutState;
@Before
@@ -57,11 +58,11 @@ public class TvPipGravityTest extends ShellTestCase {
}
MockitoAnnotations.initMocks(this);
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
- mTvPipBoundsState = new TvPipBoundsState(mContext, mPipSizeSpecHandler,
+ mSizeSpecSource = new LegacySizeSpecSource(mContext, mPipDisplayLayoutState);
+ mTvPipBoundsState = new TvPipBoundsState(mContext, mSizeSpecSource,
mPipDisplayLayoutState);
mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState,
- mMockPipSnapAlgorithm, mPipSizeSpecHandler);
+ mMockPipSnapAlgorithm, mPipDisplayLayoutState, mSizeSpecSource);
setRTL(false);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
index aedf65ddc269..998060fdcac2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
@@ -22,10 +22,10 @@ import android.testing.AndroidTestingRunner
import android.util.Size
import android.view.Gravity
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index 68cb57c14d8c..b1befc46f383 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -41,6 +41,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/** Tests for {@link MainStage} */
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -61,7 +63,7 @@ public class MainStageTests extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
- mSyncQueue, mSurfaceSession, mIconProvider);
+ mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty());
mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
index 3b42a48b5a40..549bd3fcabfb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
@@ -46,6 +46,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import java.util.Optional;
+
/** Tests for {@link SideStage} */
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -66,7 +68,7 @@ public class SideStageTests extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mRootTask = new TestRunningTaskInfoBuilder().build();
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
- mSyncQueue, mSurfaceSession, mIconProvider);
+ mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty());
mSideStage.onTaskAppeared(mRootTask, mRootLeash);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index fb17d8799bda..568db919818c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -61,9 +61,11 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -71,6 +73,7 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import org.junit.Before;
import org.junit.Test;
@@ -102,6 +105,9 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Mock IconProvider mIconProvider;
@Mock StageCoordinator mStageCoordinator;
@Mock RecentTasksController mRecentTasks;
+ @Mock LaunchAdjacentController mLaunchAdjacentController;
+ @Mock WindowDecorViewModel mWindowDecorViewModel;
+ @Mock DesktopTasksController mDesktopTasksController;
@Captor ArgumentCaptor<Intent> mIntentCaptor;
private ShellController mShellController;
@@ -117,7 +123,8 @@ public class SplitScreenControllerTests extends ShellTestCase {
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
- mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index 4e446c684d86..a3009a55198f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -31,12 +31,14 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.util.Optional;
@@ -76,10 +78,13 @@ public class SplitTestUtils {
DisplayInsetsController insetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks) {
+ Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
+ Optional<WindowDecorViewModel> windowDecorViewModel) {
super(context, displayId, syncQueue, taskOrganizer, mainStage,
sideStage, displayController, imeController, insetsController, splitLayout,
- transitions, transactionPool, mainExecutor, recentTasks);
+ transitions, transactionPool, mainExecutor, recentTasks,
+ launchAdjacentController, windowDecorViewModel);
// Prepare root task for testing.
mRootTask = new TestRunningTaskInfoBuilder().build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 44c76de945b0..5efd9ad97a3e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -70,6 +70,7 @@ import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
@@ -77,6 +78,7 @@ import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import org.junit.Before;
import org.junit.Test;
@@ -101,7 +103,9 @@ public class SplitTransitionTests extends ShellTestCase {
@Mock private Transitions mTransitions;
@Mock private SurfaceSession mSurfaceSession;
@Mock private IconProvider mIconProvider;
+ @Mock private WindowDecorViewModel mWindowDecorViewModel;
@Mock private ShellExecutor mMainExecutor;
+ @Mock private LaunchAdjacentController mLaunchAdjacentController;
@Mock private DefaultMixedHandler mMixedHandler;
private SplitLayout mSplitLayout;
private MainStage mMainStage;
@@ -123,16 +127,17 @@ public class SplitTransitionTests extends ShellTestCase {
mSplitLayout = SplitTestUtils.createMockSplitLayout();
mMainStage = spy(new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
- mIconProvider));
+ mIconProvider, Optional.of(mWindowDecorViewModel)));
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mSideStage = spy(new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
- mIconProvider));
+ mIconProvider, Optional.of(mWindowDecorViewModel)));
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
- mTransactionPool, mMainExecutor, Optional.empty());
+ mTransactionPool, mMainExecutor, Optional.empty(),
+ mLaunchAdjacentController, Optional.empty());
mStageCoordinator.setMixedHandler(mMixedHandler);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 2dcdc74e8ae7..cc4db22e772c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -65,6 +65,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
@@ -107,6 +108,8 @@ public class StageCoordinatorTests extends ShellTestCase {
private DisplayInsetsController mDisplayInsetsController;
@Mock
private TransactionPool mTransactionPool;
+ @Mock
+ private LaunchAdjacentController mLaunchAdjacentController;
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -130,7 +133,7 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
- mMainExecutor, Optional.empty()));
+ mMainExecutor, Optional.empty(), mLaunchAdjacentController, Optional.empty()));
mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
@@ -344,13 +347,20 @@ public class StageCoordinatorTests extends ShellTestCase {
}
@Test
- public void testExitSplitScreenAfterFolded() {
- when(mMainStage.isActive()).thenReturn(true);
+ public void testExitSplitScreenAfterFoldedAndWakeUp() {
when(mMainStage.isFocused()).thenReturn(true);
when(mMainStage.getTopVisibleChildTaskId()).thenReturn(INVALID_TASK_ID);
+ mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
+ mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
+ when(mStageCoordinator.isSplitActive()).thenReturn(true);
+ when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
mStageCoordinator.onFoldedStateChanged(true);
+ assertEquals(mStageCoordinator.mTopStageAfterFoldDismiss, STAGE_TYPE_MAIN);
+
+ mStageCoordinator.onFinishedWakingUp();
+
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
} else {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 1a1bebd28aef..946a7ef7d8c3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -43,6 +43,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import org.junit.Before;
import org.junit.Test;
@@ -52,6 +53,8 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
* Tests for {@link StageTaskListener}
* Build/Install/Run:
@@ -71,6 +74,8 @@ public final class StageTaskListenerTests extends ShellTestCase {
private SyncTransactionQueue mSyncQueue;
@Mock
private IconProvider mIconProvider;
+ @Mock
+ private WindowDecorViewModel mWindowDecorViewModel;
@Captor
private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
private SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -89,7 +94,8 @@ public final class StageTaskListenerTests extends ShellTestCase {
mCallbacks,
mSyncQueue,
mSurfaceSession,
- mIconProvider);
+ mIconProvider,
+ Optional.of(mWindowDecorViewModel));
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootTask.parentTaskId = INVALID_TASK_ID;
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
@@ -155,7 +161,7 @@ public final class StageTaskListenerTests extends ShellTestCase {
childTask.supportsMultiWindow = false;
mStageTaskListener.onTaskInfoChanged(childTask);
- verify(mCallbacks).onNoLongerSupportMultiWindow();
+ verify(mCallbacks).onNoLongerSupportMultiWindow(childTask);
}
@Test
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 1b389565c066..0088051928fb 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
@@ -40,6 +40,7 @@ import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.Context;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.testing.AndroidTestingRunner;
@@ -220,6 +221,20 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
+ public void testSurfaceDestroyed_withTask_shouldNotHideTask_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
+ mTaskViewTaskController.setHideTaskWithSurface(false);
+
+ SurfaceHolder sh = mock(SurfaceHolder.class);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskView.surfaceCreated(sh);
+ reset(mViewListener);
+ mTaskView.surfaceDestroyed(sh);
+
+ verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+ }
+
+ @Test
public void testSurfaceDestroyed_withTask_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
SurfaceHolder sh = mock(SurfaceHolder.class);
@@ -563,4 +578,72 @@ public class TaskViewTest extends ShellTestCase {
mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
verify(mTaskViewTaskController, never()).cleanUpPendingTask();
}
+
+ @Test
+ public void testSetCaptionInsets_noTaskInitially() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ Rect insets = new Rect(0, 400, 0, 0);
+ mTaskView.setCaptionInsets(Insets.of(insets));
+ mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+ verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+ verify(mOrganizer, never()).applyTransaction(any());
+
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ reset(mOrganizer);
+ reset(mTaskViewTaskController);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+ mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+ verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+ verify(mOrganizer).applyTransaction(any());
+ }
+
+ @Test
+ public void testSetCaptionInsets_withTask() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+ reset(mTaskViewTaskController);
+ reset(mOrganizer);
+
+ Rect insets = new Rect(0, 400, 0, 0);
+ mTaskView.setCaptionInsets(Insets.of(insets));
+ mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+ verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+ verify(mOrganizer).applyTransaction(any());
+ }
+
+ @Test
+ public void testReleaseInOnTaskRemoval_noNPE() {
+ mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer,
+ mTaskViewTransitions, mSyncQueue));
+ mTaskView = new TaskView(mContext, mTaskViewTaskController);
+ mTaskView.setListener(mExecutor, new TaskView.Listener() {
+ @Override
+ public void onTaskRemovalStarted(int taskId) {
+ mTaskView.release();
+ }
+ });
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+
+ assertThat(mTaskViewTaskController.getTaskInfo()).isEqualTo(mTaskInfo);
+
+ mTaskViewTaskController.prepareCloseAnimation();
+
+ assertThat(mTaskViewTaskController.getTaskInfo()).isNull();
+ }
}
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 ff380e92322d..99a1ac663286 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
@@ -176,7 +176,7 @@ public class ShellTransitionTests extends ShellTestCase {
assertEquals(1, mDefaultHandler.activeCount());
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
- verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any());
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
}
@Test
@@ -299,7 +299,7 @@ public class ShellTransitionTests extends ShellTestCase {
assertTrue(remoteCalled[0]);
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
- verify(mOrganizer, times(1)).finishTransition(eq(transitToken), eq(remoteFinishWCT), any());
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken), eq(remoteFinishWCT));
}
@Test
@@ -449,7 +449,7 @@ public class ShellTransitionTests extends ShellTestCase {
assertTrue(remoteCalled[0]);
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
- verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any());
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
}
@Test
@@ -524,20 +524,20 @@ public class ShellTransitionTests extends ShellTestCase {
// default handler doesn't merge by default, so it shouldn't increment active count.
assertEquals(1, mDefaultHandler.activeCount());
assertEquals(0, mDefaultHandler.mergeCount());
- verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any());
- verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+ verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any());
+ verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
// first transition finished
- verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any());
- verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any());
+ verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
// But now the "queued" transition is running
assertEquals(1, mDefaultHandler.activeCount());
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
- verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any());
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any());
}
@Test
@@ -565,15 +565,15 @@ public class ShellTransitionTests extends ShellTestCase {
// it should still only have 1 active, but then show 1 merged
assertEquals(1, mDefaultHandler.activeCount());
assertEquals(1, mDefaultHandler.mergeCount());
- verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any());
+ verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any());
// We don't tell organizer it is finished yet (since we still want to maintain ordering)
- verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+ verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
// transition + merged all finished.
- verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any());
- verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any());
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any());
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any());
// Make sure nothing was queued
assertEquals(0, mDefaultHandler.activeCount());
}
@@ -599,8 +599,7 @@ public class ShellTransitionTests extends ShellTestCase {
requestStartTransition(transitions, transitTokenNotReady);
mDefaultHandler.setSimulateMerge(true);
- mDefaultHandler.mFinishes.get(0).second.onTransitionFinished(
- null /* wct */, null /* wctCB */);
+ mDefaultHandler.mFinishes.get(0).second.onTransitionFinished(null /* wct */);
// Make sure that the non-ready transition is not merged.
assertEquals(0, mDefaultHandler.mergeCount());
@@ -823,8 +822,8 @@ public class ShellTransitionTests extends ShellTestCase {
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
// first transition finished
- verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any());
- verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any());
+ verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
// But now the "queued" transition is running
assertEquals(1, mDefaultHandler.activeCount());
@@ -835,7 +834,7 @@ public class ShellTransitionTests extends ShellTestCase {
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
- verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any());
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any());
// runnable2 and runnable3 are executed after the second transition finishes because there
// are no other active transitions, runnable1 isn't executed again.
@@ -1449,13 +1448,13 @@ public class ShellTransitionTests extends ShellTestCase {
if (mFinishOnSync && info.getType() == TRANSIT_SLEEP) {
for (int i = 0; i < mFinishes.size(); ++i) {
if (mFinishes.get(i).first != mergeTarget) continue;
- mFinishes.remove(i).second.onTransitionFinished(null, null);
+ mFinishes.remove(i).second.onTransitionFinished(null);
return;
}
}
if (!(mSimulateMerge || mShouldMerge.contains(transition))) return;
mMerged.add(transition);
- finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ finishCallback.onTransitionFinished(null /* wct */);
}
@Nullable
@@ -1478,7 +1477,7 @@ public class ShellTransitionTests extends ShellTestCase {
mFinishes;
mFinishes = new ArrayList<>();
for (int i = finishes.size() - 1; i >= 0; --i) {
- finishes.get(i).second.onTransitionFinished(null /* wct */, null /* wctCB */);
+ finishes.get(i).second.onTransitionFinished(null /* wct */);
}
mShouldMerge.clear();
}
@@ -1486,7 +1485,7 @@ public class ShellTransitionTests extends ShellTestCase {
void finishOne() {
Pair<IBinder, Transitions.TransitionFinishCallback> fin = mFinishes.remove(0);
mMerged.clear();
- fin.second.onTransitionFinished(null /* wct */, null /* wctCB */);
+ fin.second.onTransitionFinished(null /* wct */);
}
int activeCount() {
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
new file mode 100644
index 000000000000..1d94c9c31de2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.unfold;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
+import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
+import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class UnfoldTransitionHandlerTest {
+
+ private UnfoldTransitionHandler mUnfoldTransitionHandler;
+
+ private final TestShellUnfoldProgressProvider mShellUnfoldProgressProvider =
+ new TestShellUnfoldProgressProvider();
+ private final TestTransactionPool mTransactionPool = new TestTransactionPool();
+
+ private FullscreenUnfoldTaskAnimator mFullscreenUnfoldTaskAnimator;
+ private SplitTaskUnfoldAnimator mSplitTaskUnfoldAnimator;
+ private Transitions mTransitions;
+
+ private final IBinder mTransition = new Binder();
+
+ @Before
+ public void before() {
+ final ShellExecutor executor = new TestSyncExecutor();
+ final ShellInit shellInit = new ShellInit(executor);
+
+ mFullscreenUnfoldTaskAnimator = mock(FullscreenUnfoldTaskAnimator.class);
+ mSplitTaskUnfoldAnimator = mock(SplitTaskUnfoldAnimator.class);
+ mTransitions = mock(Transitions.class);
+
+ mUnfoldTransitionHandler = new UnfoldTransitionHandler(
+ shellInit,
+ mShellUnfoldProgressProvider,
+ mFullscreenUnfoldTaskAnimator,
+ mSplitTaskUnfoldAnimator,
+ mTransactionPool,
+ executor,
+ mTransitions
+ );
+
+ shellInit.init();
+ }
+
+ @Test
+ public void handleRequest_physicalDisplayChange_handlesTransition() {
+ ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+ TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+ Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+ TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
+ triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
+
+ WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition,
+ requestInfo);
+
+ assertThat(result).isNotNull();
+ }
+
+ @Test
+ public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() {
+ ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+ TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+ Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(false);
+ TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
+ triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
+
+ WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition,
+ requestInfo);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void startAnimation_animationHasNotFinishedYet_doesNotFinishTheTransition() {
+ TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+ mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ mock(TransitionInfo.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ verify(finishCallback, never()).onTransitionFinished(any());
+ }
+
+ @Test
+ public void startAnimation_animationFinishes_finishesTheTransition() {
+ TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+ mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ mock(TransitionInfo.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+ mShellUnfoldProgressProvider.onStateChangeStarted();
+ mShellUnfoldProgressProvider.onStateChangeFinished();
+
+ verify(finishCallback).onTransitionFinished(any());
+ }
+
+ @Test
+ public void startAnimation_animationIsAlreadyFinished_finishesTheTransition() {
+ TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+ mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ mShellUnfoldProgressProvider.onStateChangeStarted();
+ mShellUnfoldProgressProvider.onStateChangeFinished();
+ mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ mock(TransitionInfo.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ verify(finishCallback).onTransitionFinished(any());
+ }
+
+ @Test
+ public void startAnimationSecondTimeAfterFold_animationAlreadyFinished_finishesTransition() {
+ TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ // First unfold
+ mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false);
+ mShellUnfoldProgressProvider.onStateChangeStarted();
+ mShellUnfoldProgressProvider.onStateChangeFinished();
+ mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+ mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ mock(TransitionInfo.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+ clearInvocations(finishCallback);
+
+ // Fold
+ mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ true);
+
+ // Second unfold
+ mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false);
+ mShellUnfoldProgressProvider.onStateChangeStarted();
+ mShellUnfoldProgressProvider.onStateChangeFinished();
+ mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+ mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ mock(TransitionInfo.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ verify(finishCallback).onTransitionFinished(any());
+ }
+
+ private TransitionRequestInfo createUnfoldTransitionRequestInfo() {
+ ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+ TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+ Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+ return new TransitionRequestInfo(TRANSIT_CHANGE,
+ triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
+ }
+
+ private static class TestShellUnfoldProgressProvider implements ShellUnfoldProgressProvider,
+ ShellUnfoldProgressProvider.UnfoldListener {
+
+ private final List<UnfoldListener> mListeners = new ArrayList<>();
+
+ @Override
+ public void addListener(Executor executor, UnfoldListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void onFoldStateChanged(boolean isFolded) {
+ mListeners.forEach(unfoldListener -> unfoldListener.onFoldStateChanged(isFolded));
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ mListeners.forEach(UnfoldListener::onStateChangeFinished);
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ mListeners.forEach(unfoldListener -> unfoldListener.onStateChangeProgress(progress));
+ }
+
+ @Override
+ public void onStateChangeStarted() {
+ mListeners.forEach(UnfoldListener::onStateChangeStarted);
+ }
+ }
+
+ private static class TestTransactionPool extends TransactionPool {
+ @Override
+ public SurfaceControl.Transaction acquire() {
+ return mock(SurfaceControl.Transaction.class);
+ }
+
+ @Override
+ public void release(SurfaceControl.Transaction t) {
+ }
+ }
+
+ private static class TestSyncExecutor implements ShellExecutor {
+ @Override
+ public void execute(Runnable runnable) {
+ runnable.run();
+ }
+
+ @Override
+ public void executeDelayed(Runnable runnable, long delayMillis) {
+ runnable.run();
+ }
+
+ @Override
+ public void removeCallbacks(Runnable runnable) {
+ }
+
+ @Override
+ public boolean hasCallback(Runnable runnable) {
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index 41bab95b7dd4..596d6dd3a3d2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -18,12 +18,15 @@ package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -53,7 +56,8 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -82,7 +86,6 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController;
@Mock private DisplayLayout mDisplayLayout;
- @Mock private SplitScreenController mSplitScreenController;
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private DesktopModeController mDesktopModeController;
@Mock private DesktopTasksController mDesktopTasksController;
@@ -92,6 +95,11 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
@Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
@Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory;
@Mock private SurfaceControl.Transaction mTransaction;
+ @Mock private Display mDisplay;
+ @Mock private ShellController mShellController;
+ @Mock private ShellInit mShellInit;
+ @Mock private DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
+ mDesktopModeKeyguardChangeListener;
private final List<InputManager> mMockInputManagers = new ArrayList<>();
private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel;
@@ -105,16 +113,18 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
mContext,
mMainHandler,
mMainChoreographer,
+ mShellInit,
mTaskOrganizer,
mDisplayController,
+ mShellController,
mSyncQueue,
mTransitions,
Optional.of(mDesktopModeController),
Optional.of(mDesktopTasksController),
- Optional.of(mSplitScreenController),
mDesktopModeWindowDecorFactory,
mMockInputMonitorFactory,
- mTransactionFactory
+ mTransactionFactory,
+ mDesktopModeKeyguardChangeListener
);
doReturn(mDesktopModeWindowDecoration)
@@ -123,12 +133,16 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
doReturn(mTransaction).when(mTransactionFactory).get();
doReturn(mDisplayLayout).when(mDisplayController).getDisplayLayout(anyInt());
doReturn(STABLE_INSETS).when(mDisplayLayout).stableInsets();
+ doNothing().when(mShellController).addKeyguardChangeListener(any());
when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
// InputChannel cannot be mocked because it passes to InputEventReceiver.
final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
inputChannels[0].dispose();
when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
+
+ mDesktopModeWindowDecoration.mDisplay = mDisplay;
+ doReturn(Display.DEFAULT_DISPLAY).when(mDisplay).getDisplayId();
}
@Test
@@ -254,6 +268,32 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
verify(mInputMonitor, times(1)).dispose();
}
+ @Test
+ public void testCaptionIsNotCreatedWhenKeyguardIsVisible() throws Exception {
+ doReturn(true).when(
+ mDesktopModeKeyguardChangeListener).isKeyguardVisibleAndOccluded();
+
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN);
+ taskInfo.isFocused = true;
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ runOnMainThread(() -> {
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mDesktopModeWindowDecorViewModel.onTaskOpening(
+ taskInfo, surfaceControl, startT, finishT);
+
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ mDesktopModeWindowDecorViewModel.onTaskChanging(
+ taskInfo, surfaceControl, startT, finishT);
+ });
+ verify(mDesktopModeWindowDecorFactory, never())
+ .create(any(), any(), any(), any(), any(), any(), any(), any());
+ }
+
private void runOnMainThread(Runnable r) throws Exception {
final Handler mainHandler = new Handler(Looper.getMainLooper());
final CountDownLatch latch = new CountDownLatch(1);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
index 8f84008e8d2d..3fbab0f9e2bb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
@@ -55,7 +55,7 @@ class DragDetectorTest {
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(eventHandler.handleMotionEvent(any())).thenReturn(true)
+ `when`(eventHandler.handleMotionEvent(any(), any())).thenReturn(true)
dragDetector = DragDetector(eventHandler)
dragDetector.setTouchSlop(SLOP)
@@ -72,13 +72,13 @@ class DragDetectorTest {
@Test
fun testNoMove_passesDownAndUp() {
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
@@ -86,12 +86,12 @@ class DragDetectorTest {
@Test
fun testMoveInSlop_touch_passesDownAndUp() {
- `when`(eventHandler.handleMotionEvent(argThat {
+ `when`(eventHandler.handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN
})).thenReturn(false)
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
@@ -99,12 +99,12 @@ class DragDetectorTest {
val newX = X + SLOP - 1
assertFalse(
dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
- verify(eventHandler, never()).handleMotionEvent(argThat {
+ verify(eventHandler, never()).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_MOVE
})
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
@@ -112,13 +112,13 @@ class DragDetectorTest {
@Test
fun testMoveInSlop_mouse_passesDownMoveAndUp() {
- `when`(eventHandler.handleMotionEvent(argThat {
+ `when`(eventHandler.handleMotionEvent(any(), argThat {
it.action == MotionEvent.ACTION_DOWN
})).thenReturn(false)
assertFalse(dragDetector.onMotionEvent(
createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_MOUSE
})
@@ -126,14 +126,14 @@ class DragDetectorTest {
val newX = X + SLOP - 1
assertTrue(dragDetector.onMotionEvent(
createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_MOUSE
})
assertTrue(dragDetector.onMotionEvent(
createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_MOUSE
})
@@ -141,25 +141,25 @@ class DragDetectorTest {
@Test
fun testMoveBeyondSlop_passesDownMoveAndUp() {
- `when`(eventHandler.handleMotionEvent(argThat {
+ `when`(eventHandler.handleMotionEvent(any(), argThat {
it.action == MotionEvent.ACTION_DOWN
})).thenReturn(false)
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
val newX = X + SLOP + 1
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN
})
@@ -167,12 +167,12 @@ class DragDetectorTest {
@Test
fun testPassesHoverEnter() {
- `when`(eventHandler.handleMotionEvent(argThat {
+ `when`(eventHandler.handleMotionEvent(any(), argThat {
it.action == MotionEvent.ACTION_HOVER_ENTER
})).thenReturn(false)
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y
})
}
@@ -180,7 +180,7 @@ class DragDetectorTest {
@Test
fun testPassesHoverMove() {
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y
})
}
@@ -188,7 +188,7 @@ class DragDetectorTest {
@Test
fun testPassesHoverExit() {
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT)))
- verify(eventHandler).handleMotionEvent(argThat {
+ verify(eventHandler).handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y
})
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 69604ddf0af1..6f0599aa8243 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -22,6 +22,7 @@ import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFI
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
@@ -71,16 +72,6 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- taskPositioner =
- FluidResizeTaskPositioner(
- mockShellTaskOrganizer,
- mockWindowDecoration,
- mockDisplayController,
- DISALLOWED_AREA_FOR_END_BOUNDS,
- mockDragStartListener,
- mockTransactionFactory
- )
-
whenever(taskToken.asBinder()).thenReturn(taskBinder)
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
@@ -101,6 +92,15 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
}
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+ taskPositioner = FluidResizeTaskPositioner(
+ mockShellTaskOrganizer,
+ mockWindowDecoration,
+ mockDisplayController,
+ mockDragStartListener,
+ mockTransactionFactory,
+ DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ )
}
@Test
@@ -544,7 +544,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
)
val newX = STARTING_BOUNDS.right.toFloat() + 5
- val newY = STARTING_BOUNDS.top.toFloat() + 5
+ val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -614,6 +614,38 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
})
}
+ @Test
+ fun testDragResize_drag_taskPositionedInStableBounds() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = STARTING_BOUNDS.left.toFloat()
+ val newY = STABLE_BOUNDS.top.toFloat() - 5
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
+ // but not in disallowed end bounds area.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ STABLE_BOUNDS.top
+ }
+ })
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -622,10 +654,11 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
private const val DEFAULT_MIN = 40
private const val DISPLAY_ID = 1
private const val NAVBAR_HEIGHT = 50
+ private const val CAPTION_HEIGHT = 50
+ private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
- private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
private val STABLE_INSETS = Rect(0, 50, 0, 0)
- private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 300, 300)
private val DISALLOWED_RESIZE_AREA = Rect(
DISPLAY_BOUNDS.left,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
@@ -633,7 +666,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
DISPLAY_BOUNDS.bottom)
private val STABLE_BOUNDS = Rect(
DISPLAY_BOUNDS.left,
- DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 4147dd8e6152..3465ddd9d101 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -89,17 +89,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- taskPositioner =
- VeiledResizeTaskPositioner(
- mockShellTaskOrganizer,
- mockDesktopWindowDecoration,
- mockDisplayController,
- DISALLOWED_AREA_FOR_END_BOUNDS,
- mockDragStartListener,
- mockTransactionFactory,
- mockTransitions
- )
-
whenever(taskToken.asBinder()).thenReturn(taskBinder)
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
@@ -119,6 +108,17 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
}
mockDesktopWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+ taskPositioner =
+ VeiledResizeTaskPositioner(
+ mockShellTaskOrganizer,
+ mockDesktopWindowDecoration,
+ mockDisplayController,
+ mockDragStartListener,
+ mockTransactionFactory,
+ mockTransitions,
+ DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ )
}
@Test
@@ -269,7 +269,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
)
val newX = STARTING_BOUNDS.left.toFloat() + 5
- val newY = STARTING_BOUNDS.top.toFloat() + 5
+ val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -334,6 +334,38 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
})
}
+ @Test
+ fun testDragResize_drag_taskPositionedInStableBounds() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = STARTING_BOUNDS.left.toFloat()
+ val newY = STABLE_BOUNDS.top.toFloat() - 5
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
+ // but not in disallowed end bounds area.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ STABLE_BOUNDS.top
+ }
+ })
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -342,12 +374,13 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
private const val DEFAULT_MIN = 40
private const val DISPLAY_ID = 1
private const val NAVBAR_HEIGHT = 50
+ private const val CAPTION_HEIGHT = 50
+ private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
- private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
- private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 50, 50)
+ private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
private val STABLE_BOUNDS = Rect(
DISPLAY_BOUNDS.left,
- DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
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 5a2326b9c393..7fc1c99bb44e 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
@@ -57,7 +57,6 @@ import android.window.SurfaceSyncGroup;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -316,7 +315,8 @@ public class WindowDecorationTests extends ShellTestCase {
releaseOrder.verify(t).remove(captionContainerSurface);
releaseOrder.verify(t).remove(decorContainerSurface);
releaseOrder.verify(t).apply();
- verify(mMockWindowContainerTransaction)
+ // Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
+ verify(mMockWindowContainerTransaction, Mockito.times(2))
.removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
}
@@ -410,15 +410,17 @@ public class WindowDecorationTests extends ShellTestCase {
verify(additionalWindowSurfaceBuilder).build();
verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0);
final int width = WindowDecoration.loadDimensionPixelSize(
- mContext.getResources(), mCaptionMenuWidthId);
+ windowDecor.mDecorWindowContext.getResources(), mCaptionMenuWidthId);
final int height = WindowDecoration.loadDimensionPixelSize(
- mContext.getResources(), mRelayoutParams.mCaptionHeightId);
+ windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId);
verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
- final int shadowRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(),
+ final int shadowRadius = WindowDecoration.loadDimensionPixelSize(
+ windowDecor.mDecorWindowContext.getResources(),
mCaptionMenuShadowRadiusId);
verify(mMockSurfaceControlAddWindowT)
.setShadowRadius(additionalWindowSurface, shadowRadius);
- final int cornerRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(),
+ final int cornerRadius = WindowDecoration.loadDimensionPixelSize(
+ windowDecor.mDecorWindowContext.getResources(),
mCaptionMenuCornerRadiusId);
verify(mMockSurfaceControlAddWindowT)
.setCornerRadius(additionalWindowSurface, cornerRadius);
@@ -513,8 +515,7 @@ public class WindowDecorationTests extends ShellTestCase {
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
- return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
- mMockDisplayController, mMockShellTaskOrganizer,
+ return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
taskInfo, testSurface,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
index 96bfb78eff0d..d1dd8df81a3e 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
@@ -23,6 +23,7 @@ import android.content.ComponentName
import android.util.Log
import com.android.dream.lowlight.dagger.LowLightDreamModule
import com.android.dream.lowlight.dagger.qualifiers.Application
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.TimeoutCancellationException
@@ -103,6 +104,11 @@ class LowLightDreamManager @Inject constructor(
)
} catch (ex: TimeoutCancellationException) {
Log.e(TAG, "timed out while waiting for low light animation", ex)
+ } catch (ex: CancellationException) {
+ Log.w(TAG, "low light transition animation cancelled")
+ // Catch the cancellation so that we still set the system dream component if the
+ // animation is cancelled, such as by a user tapping to wake as the transition to
+ // low light happens.
}
dreamManager.setSystemDreamComponent(
if (shouldEnterLowLight) lowLightDreamComponent else null
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
index 473603002b21..de1aee598667 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
@@ -110,15 +110,5 @@ class LowLightTransitionCoordinator @Inject constructor() {
}
}
animator.addListener(listener)
- continuation.invokeOnCancellation {
- try {
- animator.removeListener(listener)
- animator.cancel()
- } catch (exception: IndexOutOfBoundsException) {
- // TODO(b/285666217): remove this try/catch once a proper fix is implemented.
- // Cancelling the animator can cause an exception since we may be removing a
- // listener during the cancellation. See b/285666217 for more details.
- }
- }
}
}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt
new file mode 100644
index 000000000000..f69c84dafbb2
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.dream.lowlight.util
+
+import android.view.animation.Interpolator
+
+/**
+ * Interpolator wrapper that shortens another interpolator from its original duration to a portion
+ * of that duration.
+ *
+ * For example, an `originalDuration` of 1000 and a `newDuration` of 200 results in an animation
+ * that when played for 200ms is the exact same as the first 200ms of a 1000ms animation if using
+ * the original interpolator.
+ *
+ * This is useful for the transition between the user dream and the low light clock as some
+ * animations are defined in the spec to be longer than the total duration of the animation. For
+ * example, the low light clock exit translation animation is defined to last >1s while the actual
+ * fade out of the low light clock is only 250ms, meaning the clock isn't visible anymore after
+ * 250ms.
+ *
+ * Since the dream framework currently only allows one dream to be visible and running, we use this
+ * interpolator to play just the first 250ms of the translation animation. Simply reducing the
+ * duration of the animation would result in the text exiting much faster than intended, so a custom
+ * interpolator is needed.
+ */
+class TruncatedInterpolator(
+ private val baseInterpolator: Interpolator,
+ originalDuration: Float,
+ newDuration: Float
+) : Interpolator {
+ private val scaleFactor: Float
+
+ init {
+ scaleFactor = newDuration / originalDuration
+ }
+
+ override fun getInterpolation(input: Float): Float {
+ return baseInterpolator.getInterpolation(input * scaleFactor)
+ }
+}
diff --git a/libs/dream/lowlight/tests/Android.bp b/libs/dream/lowlight/tests/Android.bp
index c256e2f364fb..4dafd0aa6df4 100644
--- a/libs/dream/lowlight/tests/Android.bp
+++ b/libs/dream/lowlight/tests/Android.bp
@@ -27,6 +27,7 @@ android_test {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "animationlib",
"frameworks-base-testutils",
"junit",
"kotlinx_coroutines_test",
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
index 2a886bc31788..de84adb2e5c2 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
@@ -152,6 +152,21 @@ class LowLightDreamManagerTest {
verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
}
+ @Test
+ fun setAmbientLightMode_animationCancelled_SetsSystemDream() = testScope.runTest {
+ mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ runCurrent()
+ cancelEnterAnimations()
+ runCurrent()
+ // Animation never finishes, but we should still set the system dream
+ verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
+ }
+
+ private fun cancelEnterAnimations() {
+ val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
+ listener.onAnimationCancel(mEnterAnimator)
+ }
+
private fun completeEnterAnimations() {
val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
listener.onAnimationEnd(mEnterAnimator)
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
index 4c526a6ac69d..9ae304f9763a 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
@@ -158,26 +158,6 @@ class LowLightTransitionCoordinatorTest {
assertThat(job.isCancelled).isTrue()
}
- @Test
- fun shouldCancelAnimatorWhenJobCancelled() = testScope.runTest {
- whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
- val coordinator = LowLightTransitionCoordinator()
- coordinator.setLowLightEnterListener(mEnterListener)
- val job = launch {
- coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
- }
- runCurrent()
- // Animator listener is added and the runnable is not run yet.
- verify(mAnimator).addListener(mAnimatorListenerCaptor.capture())
- verify(mAnimator, never()).cancel()
- assertThat(job.isCompleted).isFalse()
-
- job.cancel()
- // We should have removed the listener and cancelled the animator
- verify(mAnimator).removeListener(mAnimatorListenerCaptor.value)
- verify(mAnimator).cancel()
- }
-
companion object {
private val TIMEOUT = 1.toDuration(DurationUnit.SECONDS)
}
diff --git a/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt
new file mode 100644
index 000000000000..190f02e97136
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.dream.lowlight.util
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TruncatedInterpolatorTest {
+ @Test
+ fun truncatedInterpolator_matchesRegularInterpolator() {
+ val originalInterpolator = Interpolators.EMPHASIZED
+ val truncatedInterpolator =
+ TruncatedInterpolator(originalInterpolator, ORIGINAL_DURATION_MS, NEW_DURATION_MS)
+
+ // Both interpolators should start at the same value.
+ var animationPercent = 0f
+ Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+ .isEqualTo(originalInterpolator.getInterpolation(animationPercent))
+
+ animationPercent = 1f
+ Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+ .isEqualTo(originalInterpolator.getInterpolation(animationPercent * DURATION_RATIO))
+
+ animationPercent = 0.25f
+ Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+ .isEqualTo(originalInterpolator.getInterpolation(animationPercent * DURATION_RATIO))
+ }
+
+ companion object {
+ private const val ORIGINAL_DURATION_MS: Float = 1000f
+ private const val NEW_DURATION_MS: Float = 200f
+ private const val DURATION_RATIO: Float = NEW_DURATION_MS / ORIGINAL_DURATION_MS
+ }
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index db581471e2ca..c87c15669a09 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -717,6 +717,7 @@ cc_test {
"tests/unit/EglManagerTests.cpp",
"tests/unit/FatVectorTests.cpp",
"tests/unit/GraphicsStatsServiceTests.cpp",
+ "tests/unit/HintSessionWrapperTests.cpp",
"tests/unit/JankTrackerTests.cpp",
"tests/unit/FrameMetricsReporterTests.cpp",
"tests/unit/LayerUpdateQueueTests.cpp",
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 613f52b32bea..651d526511ef 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -54,14 +54,13 @@ float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
return maxPQLux / GenericSdrWhiteNits;
} else if (skcms_TransferFunction_isHLGish(&destTF)) {
return maxHLGLux / GenericSdrWhiteNits;
- } else {
#ifdef __ANDROID__
+ } else if (RenderThread::isCurrent()) {
CanvasContext* context = CanvasContext::getActiveContext();
return context ? context->targetSdrHdrRatio() : 1.f;
-#else
- return 1.f;
#endif
}
+ return 1.f;
}
void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 00919dc3f22a..b87002371775 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -79,7 +79,7 @@ bool ShaderCache::validateCache(const void* identity, ssize_t size) {
void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) {
ATRACE_NAME("initShaderDiskCache");
- std::lock_guard<std::mutex> lock(mMutex);
+ std::lock_guard lock(mMutex);
// Emulators can switch between different renders either as part of config
// or snapshot migration. Also, program binaries may not work well on some
@@ -88,23 +88,21 @@ void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) {
mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
validateCache(identity, size);
mInitialized = true;
+ if (identity != nullptr && size > 0 && mIDHash.size()) {
+ set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+ }
}
}
void ShaderCache::setFilename(const char* filename) {
- std::lock_guard<std::mutex> lock(mMutex);
+ std::lock_guard lock(mMutex);
mFilename = filename;
}
-BlobCache* ShaderCache::getBlobCacheLocked() {
- LOG_ALWAYS_FATAL_IF(!mInitialized, "ShaderCache has not been initialized");
- return mBlobCache.get();
-}
-
sk_sp<SkData> ShaderCache::load(const SkData& key) {
ATRACE_NAME("ShaderCache::load");
size_t keySize = key.size();
- std::lock_guard<std::mutex> lock(mMutex);
+ std::lock_guard lock(mMutex);
if (!mInitialized) {
return nullptr;
}
@@ -115,8 +113,7 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) {
if (!valueBuffer) {
return nullptr;
}
- BlobCache* bc = getBlobCacheLocked();
- size_t valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+ size_t valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
int maxTries = 3;
while (valueSize > mObservedBlobValueSize && maxTries > 0) {
mObservedBlobValueSize = std::min(valueSize, maxValueSize);
@@ -126,7 +123,7 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) {
return nullptr;
}
valueBuffer = newValueBuffer;
- valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+ valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
maxTries--;
}
if (!valueSize) {
@@ -143,16 +140,17 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) {
return SkData::MakeFromMalloc(valueBuffer, valueSize);
}
-namespace {
-// Helper for BlobCache::set to trace the result.
-void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) {
- switch (cache->set(key, keySize, value, valueSize)) {
+void ShaderCache::set(const void* key, size_t keySize, const void* value, size_t valueSize) {
+ switch (mBlobCache->set(key, keySize, value, valueSize)) {
case BlobCache::InsertResult::kInserted:
// This is what we expect/hope. It means the cache is large enough.
return;
case BlobCache::InsertResult::kDidClean: {
ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize,
valueSize);
+ if (mIDHash.size()) {
+ set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+ }
return;
}
case BlobCache::InsertResult::kNotEnoughSpace: {
@@ -172,22 +170,22 @@ void set(BlobCache* cache, const void* key, size_t keySize, const void* value, s
}
}
}
-} // namespace
void ShaderCache::saveToDiskLocked() {
ATRACE_NAME("ShaderCache::saveToDiskLocked");
if (mInitialized && mBlobCache) {
- if (mIDHash.size()) {
- auto key = sIDKey;
- set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
- }
+ // The most straightforward way to make ownership shared
+ mMutex.unlock();
+ mMutex.lock_shared();
mBlobCache->writeToFile();
+ mMutex.unlock_shared();
+ mMutex.lock();
}
}
void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) {
ATRACE_NAME("ShaderCache::store");
- std::lock_guard<std::mutex> lock(mMutex);
+ std::lock_guard lock(mMutex);
mNumShadersCachedInRam++;
ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
@@ -204,11 +202,10 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /
const void* value = data.data();
- BlobCache* bc = getBlobCacheLocked();
if (mInStoreVkPipelineInProgress) {
if (mOldPipelineCacheSize == -1) {
// Record the initial pipeline cache size stored in the file.
- mOldPipelineCacheSize = bc->get(key.data(), keySize, nullptr, 0);
+ mOldPipelineCacheSize = mBlobCache->get(key.data(), keySize, nullptr, 0);
}
if (mNewPipelineCacheSize != -1 && mNewPipelineCacheSize == valueSize) {
// There has not been change in pipeline cache size. Stop trying to save.
@@ -223,13 +220,13 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /
mNewPipelineCacheSize = -1;
mTryToStorePipelineCache = true;
}
- set(bc, key.data(), keySize, value, valueSize);
+ set(key.data(), keySize, value, valueSize);
if (!mSavePending && mDeferredSaveDelayMs > 0) {
mSavePending = true;
std::thread deferredSaveThread([this]() {
usleep(mDeferredSaveDelayMs * 1000); // milliseconds to microseconds
- std::lock_guard<std::mutex> lock(mMutex);
+ std::lock_guard lock(mMutex);
// Store file on disk if there a new shader or Vulkan pipeline cache size changed.
if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) {
saveToDiskLocked();
@@ -245,11 +242,12 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /
void ShaderCache::onVkFrameFlushed(GrDirectContext* context) {
{
- std::lock_guard<std::mutex> lock(mMutex);
-
+ mMutex.lock_shared();
if (!mInitialized || !mTryToStorePipelineCache) {
+ mMutex.unlock_shared();
return;
}
+ mMutex.unlock_shared();
}
mInStoreVkPipelineInProgress = true;
context->storeVkPipelineCacheData();
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index f5506d60f811..74955503dbb1 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -19,8 +19,10 @@
#include <GrContextOptions.h>
#include <SkRefCnt.h>
#include <cutils/compiler.h>
+#include <ftl/shared_mutex.h>
+#include <utils/Mutex.h>
+
#include <memory>
-#include <mutex>
#include <string>
#include <vector>
@@ -94,25 +96,23 @@ private:
void operator=(const ShaderCache&) = delete;
/**
- * "getBlobCacheLocked" returns the BlobCache object being used to store the
- * key/value blob pairs. If the BlobCache object has not yet been created,
- * this will do so, loading the serialized cache contents from disk if
- * possible.
+ * "validateCache" updates the cache to match the given identity. If the
+ * cache currently has the wrong identity, all entries in the cache are cleared.
*/
- BlobCache* getBlobCacheLocked();
+ bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex);
/**
- * "validateCache" updates the cache to match the given identity. If the
- * cache currently has the wrong identity, all entries in the cache are cleared.
+ * Helper for BlobCache::set to trace the result and ensure the identity hash
+ * does not get evicted.
*/
- bool validateCache(const void* identity, ssize_t size);
+ void set(const void* key, size_t keySize, const void* value, size_t valueSize) REQUIRES(mMutex);
/**
- * "saveToDiskLocked" attemps to save the current contents of the cache to
+ * "saveToDiskLocked" attempts to save the current contents of the cache to
* disk. If the identity hash exists, we will insert the identity hash into
* the cache for next validation.
*/
- void saveToDiskLocked();
+ void saveToDiskLocked() REQUIRES(mMutex);
/**
* "mInitialized" indicates whether the ShaderCache is in the initialized
@@ -122,16 +122,14 @@ private:
* the load and store methods will return without performing any cache
* operations.
*/
- bool mInitialized = false;
+ bool mInitialized GUARDED_BY(mMutex) = false;
/**
- * "mBlobCache" is the cache in which the key/value blob pairs are stored. It
- * is initially NULL, and will be initialized by getBlobCacheLocked the
- * first time it's needed.
- * The blob cache contains the Android build number. We treat version mismatches as an empty
- * cache (logic implemented in BlobCache::unflatten).
+ * "mBlobCache" is the cache in which the key/value blob pairs are stored.
+ * The blob cache contains the Android build number. We treat version mismatches
+ * as an empty cache (logic implemented in BlobCache::unflatten).
*/
- std::unique_ptr<FileBlobCache> mBlobCache;
+ std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex);
/**
* "mFilename" is the name of the file for storing cache contents in between
@@ -140,7 +138,7 @@ private:
* empty string indicates that the cache should not be saved to or restored
* from disk.
*/
- std::string mFilename;
+ std::string mFilename GUARDED_BY(mMutex);
/**
* "mIDHash" is the current identity hash for the cache validation. It is
@@ -149,7 +147,7 @@ private:
* indicates that cache validation is not performed, and the hash should
* not be stored on disk.
*/
- std::vector<uint8_t> mIDHash;
+ std::vector<uint8_t> mIDHash GUARDED_BY(mMutex);
/**
* "mSavePending" indicates whether or not a deferred save operation is
@@ -159,7 +157,7 @@ private:
* contents to disk, unless mDeferredSaveDelayMs is 0 in which case saving
* is disabled.
*/
- bool mSavePending = false;
+ bool mSavePending GUARDED_BY(mMutex) = false;
/**
* "mObservedBlobValueSize" is the maximum value size observed by the cache reading function.
@@ -174,16 +172,16 @@ private:
unsigned int mDeferredSaveDelayMs = 4 * 1000;
/**
- * "mMutex" is the mutex used to prevent concurrent access to the member
+ * "mMutex" is the shared mutex used to prevent concurrent access to the member
* variables. It must be locked whenever the member variables are accessed.
*/
- mutable std::mutex mMutex;
+ mutable ftl::SharedMutex mMutex;
/**
* If set to "true", the next call to onVkFrameFlushed, will invoke
* GrCanvas::storeVkPipelineCacheData. This does not guarantee that data will be stored on disk.
*/
- bool mTryToStorePipelineCache = true;
+ bool mTryToStorePipelineCache GUARDED_BY(mMutex) = true;
/**
* This flag is used by "ShaderCache::store" to distinguish between shader data and
@@ -195,16 +193,16 @@ private:
* "mNewPipelineCacheSize" has the size of the new Vulkan pipeline cache data. It is used
* to prevent unnecessary disk writes, if the pipeline cache size has not changed.
*/
- size_t mNewPipelineCacheSize = -1;
+ size_t mNewPipelineCacheSize GUARDED_BY(mMutex) = -1;
/**
* "mOldPipelineCacheSize" has the size of the Vulkan pipeline cache data stored on disk.
*/
- size_t mOldPipelineCacheSize = -1;
+ size_t mOldPipelineCacheSize GUARDED_BY(mMutex) = -1;
/**
* "mCacheDirty" is true when there is new shader cache data, which is not saved to disk.
*/
- bool mCacheDirty = false;
+ bool mCacheDirty GUARDED_BY(mMutex) = false;
/**
* "sCache" is the singleton ShaderCache object.
@@ -221,7 +219,7 @@ private:
* interesting to keep track of how many shaders are stored in RAM. This
* class provides a convenient entry point for that.
*/
- int mNumShadersCachedInRam = 0;
+ int mNumShadersCachedInRam GUARDED_BY(mMutex) = 0;
friend class ShaderCacheTestUtils; // used for unit testing
};
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 16b35ffcabac..310e39e1d0a2 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -123,7 +123,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode*
, mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
, mContentDrawBounds(0, 0, 0, 0)
, mRenderPipeline(std::move(renderPipeline))
- , mHintSessionWrapper(uiThreadId, renderThreadId) {
+ , mHintSessionWrapper(std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId)) {
mRenderThread.cacheManager().registerCanvasContext(this);
rootRenderNode->makeRoot();
mRenderNodes.emplace_back(rootRenderNode);
@@ -160,6 +160,7 @@ void CanvasContext::destroy() {
destroyHardwareResources();
mAnimationContext->destroy();
mRenderThread.cacheManager().onContextStopped(this);
+ mHintSessionWrapper->delayedDestroy(mRenderThread, 2_s, mHintSessionWrapper);
}
static void setBufferCount(ANativeWindow* window) {
@@ -193,6 +194,7 @@ void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) {
void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
ATRACE_CALL();
+ startHintSession();
if (window) {
mNativeSurface = std::make_unique<ReliableSurface>(window);
mNativeSurface->init();
@@ -405,8 +407,17 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
// If the previous frame was dropped we don't need to hold onto it, so
// just keep using the previous frame's structure instead
- if (!wasSkipped(mCurrentFrameInfo)) {
+ if (wasSkipped(mCurrentFrameInfo)) {
+ // Use the oldest skipped frame in case we skip more than a single frame
+ if (!mSkippedFrameInfo) {
+ mSkippedFrameInfo.emplace();
+ mSkippedFrameInfo->vsyncId =
+ mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+ mSkippedFrameInfo->startTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+ }
+ } else {
mCurrentFrameInfo = mJankTracker.startFrame();
+ mSkippedFrameInfo.reset();
}
mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
@@ -602,10 +613,18 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
const auto inputEventId =
static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId));
- native_window_set_frame_timeline_info(
- mNativeSurface->getNativeWindow(), frameCompleteNr, vsyncId, inputEventId,
- mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime),
- solelyTextureViewUpdates);
+ const ANativeWindowFrameTimelineInfo ftl = {
+ .frameNumber = frameCompleteNr,
+ .frameTimelineVsyncId = vsyncId,
+ .inputEventId = inputEventId,
+ .startTimeNanos = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime),
+ .useForRefreshRateSelection = solelyTextureViewUpdates,
+ .skippedFrameVsyncId = mSkippedFrameInfo ? mSkippedFrameInfo->vsyncId
+ : UiFrameInfoBuilder::INVALID_VSYNC_ID,
+ .skippedFrameStartTimeNanos =
+ mSkippedFrameInfo ? mSkippedFrameInfo->startTime : 0,
+ };
+ native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), ftl);
}
}
@@ -721,7 +740,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
- mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync);
+ mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);
if (didDraw) {
int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
@@ -729,7 +748,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
int64_t actualDuration = frameDuration -
(std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
dequeueBufferDuration - idleDuration;
- mHintSessionWrapper.reportActualWorkDuration(actualDuration);
+ mHintSessionWrapper->reportActualWorkDuration(actualDuration);
}
mLastDequeueBufferDuration = dequeueBufferDuration;
@@ -1063,11 +1082,11 @@ void CanvasContext::prepareSurfaceControlForWebview() {
}
void CanvasContext::sendLoadResetHint() {
- mHintSessionWrapper.sendLoadResetHint();
+ mHintSessionWrapper->sendLoadResetHint();
}
void CanvasContext::sendLoadIncreaseHint() {
- mHintSessionWrapper.sendLoadIncreaseHint();
+ mHintSessionWrapper->sendLoadIncreaseHint();
}
void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
@@ -1075,7 +1094,7 @@ void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
}
void CanvasContext::startHintSession() {
- mHintSessionWrapper.init();
+ mHintSessionWrapper->init();
}
bool CanvasContext::shouldDither() {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 5219b5757008..10a4afb23d35 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -359,13 +359,19 @@ private:
std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
std::function<void()> mPrepareSurfaceControlForWebviewCallback;
- HintSessionWrapper mHintSessionWrapper;
+ std::shared_ptr<HintSessionWrapper> mHintSessionWrapper;
nsecs_t mLastDequeueBufferDuration = 0;
nsecs_t mSyncDelayDuration = 0;
nsecs_t mIdleDuration = 0;
ColorMode mColorMode = ColorMode::Default;
float mTargetSdrHdrRatio = 1.f;
+
+ struct SkippedFrameInfo {
+ int64_t vsyncId;
+ int64_t startTime;
+ };
+ std::optional<SkippedFrameInfo> mSkippedFrameInfo;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 30e472a79926..565da52de940 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -24,6 +24,7 @@
#include <vector>
#include "../Properties.h"
+#include "RenderThread.h"
#include "thread/CommonPool.h"
using namespace std::chrono_literals;
@@ -32,85 +33,56 @@ namespace android {
namespace uirenderer {
namespace renderthread {
-namespace {
+#define BIND_APH_METHOD(name) \
+ name = (decltype(name))dlsym(handle_, "APerformanceHint_" #name); \
+ LOG_ALWAYS_FATAL_IF(name == nullptr, "Failed to find required symbol APerformanceHint_" #name)
-typedef APerformanceHintManager* (*APH_getManager)();
-typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
- size_t, int64_t);
-typedef void (*APH_closeSession)(APerformanceHintSession* session);
-typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_sendHint)(APerformanceHintSession* session, int32_t);
-
-bool gAPerformanceHintBindingInitialized = false;
-APH_getManager gAPH_getManagerFn = nullptr;
-APH_createSession gAPH_createSessionFn = nullptr;
-APH_closeSession gAPH_closeSessionFn = nullptr;
-APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
-APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
-APH_sendHint gAPH_sendHintFn = nullptr;
-
-void ensureAPerformanceHintBindingInitialized() {
- if (gAPerformanceHintBindingInitialized) return;
+void HintSessionWrapper::HintSessionBinding::init() {
+ if (mInitialized) return;
void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
- gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
- LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
- "Failed to find required symbol APerformanceHint_getManager!");
-
- gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
- LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
- "Failed to find required symbol APerformanceHint_createSession!");
-
- gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
- LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
- "Failed to find required symbol APerformanceHint_closeSession!");
-
- gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym(
- handle_, "APerformanceHint_updateTargetWorkDuration");
- LOG_ALWAYS_FATAL_IF(
- gAPH_updateTargetWorkDurationFn == nullptr,
- "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!");
-
- gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
- handle_, "APerformanceHint_reportActualWorkDuration");
- LOG_ALWAYS_FATAL_IF(
- gAPH_reportActualWorkDurationFn == nullptr,
- "Failed to find required symbol APerformanceHint_reportActualWorkDuration!");
+ BIND_APH_METHOD(getManager);
+ BIND_APH_METHOD(createSession);
+ BIND_APH_METHOD(closeSession);
+ BIND_APH_METHOD(updateTargetWorkDuration);
+ BIND_APH_METHOD(reportActualWorkDuration);
+ BIND_APH_METHOD(sendHint);
- gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
- LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
- "Failed to find required symbol APerformanceHint_sendHint!");
-
- gAPerformanceHintBindingInitialized = true;
+ mInitialized = true;
}
-} // namespace
-
HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId)
- : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {}
+ : mUiThreadId(uiThreadId)
+ , mRenderThreadId(renderThreadId)
+ , mBinding(std::make_shared<HintSessionBinding>()) {}
HintSessionWrapper::~HintSessionWrapper() {
- if (mHintSessionFuture.valid()) {
- mHintSession = mHintSessionFuture.get();
+ destroy();
+}
+
+void HintSessionWrapper::destroy() {
+ if (mHintSessionFuture.has_value()) {
+ mHintSession = mHintSessionFuture->get();
+ mHintSessionFuture = std::nullopt;
}
if (mHintSession) {
- gAPH_closeSessionFn(mHintSession);
+ mBinding->closeSession(mHintSession);
mSessionValid = true;
mHintSession = nullptr;
}
+ mResetsSinceLastReport = 0;
}
bool HintSessionWrapper::init() {
if (mHintSession != nullptr) return true;
-
// If we're waiting for the session
- if (mHintSessionFuture.valid()) {
+ if (mHintSessionFuture.has_value()) {
// If the session is here
- if (mHintSessionFuture.wait_for(0s) == std::future_status::ready) {
- mHintSession = mHintSessionFuture.get();
+ if (mHintSessionFuture->wait_for(0s) == std::future_status::ready) {
+ mHintSession = mHintSessionFuture->get();
+ mHintSessionFuture = std::nullopt;
if (mHintSession != nullptr) {
mSessionValid = true;
return true;
@@ -129,9 +101,9 @@ bool HintSessionWrapper::init() {
// Assume that if we return before the end, it broke
mSessionValid = false;
- ensureAPerformanceHintBindingInitialized();
+ mBinding->init();
- APerformanceHintManager* manager = gAPH_getManagerFn();
+ APerformanceHintManager* manager = mBinding->getManager();
if (!manager) return false;
std::vector<pid_t> tids = CommonPool::getThreadIds();
@@ -140,9 +112,10 @@ bool HintSessionWrapper::init() {
// Use a placeholder target value to initialize,
// this will always be replaced elsewhere before it gets used
- int64_t defaultTargetDurationNanos = 16666667;
- mHintSessionFuture = CommonPool::async([=, tids = std::move(tids)] {
- return gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos);
+ int64_t targetDurationNanos =
+ mLastTargetWorkDuration == 0 ? kDefaultTargetDuration : mLastTargetWorkDuration;
+ mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] {
+ return mBinding->createSession(manager, tids.data(), tids.size(), targetDurationNanos);
});
return false;
}
@@ -154,7 +127,7 @@ void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos)
targetWorkDurationNanos > kSanityCheckLowerBound &&
targetWorkDurationNanos < kSanityCheckUpperBound) {
mLastTargetWorkDuration = targetWorkDurationNanos;
- gAPH_updateTargetWorkDurationFn(mHintSession, targetWorkDurationNanos);
+ mBinding->updateTargetWorkDuration(mHintSession, targetWorkDurationNanos);
}
mLastFrameNotification = systemTime();
}
@@ -164,8 +137,9 @@ void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
mResetsSinceLastReport = 0;
if (actualDurationNanos > kSanityCheckLowerBound &&
actualDurationNanos < kSanityCheckUpperBound) {
- gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
+ mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos);
}
+ mLastFrameNotification = systemTime();
}
void HintSessionWrapper::sendLoadResetHint() {
@@ -175,14 +149,36 @@ void HintSessionWrapper::sendLoadResetHint() {
if (now - mLastFrameNotification > kResetHintTimeout &&
mResetsSinceLastReport <= kMaxResetsSinceLastReport) {
++mResetsSinceLastReport;
- gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET));
+ mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET));
}
mLastFrameNotification = now;
}
void HintSessionWrapper::sendLoadIncreaseHint() {
if (!init()) return;
- gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_UP));
+ mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
+ mLastFrameNotification = systemTime();
+}
+
+bool HintSessionWrapper::alive() {
+ return mHintSession != nullptr;
+}
+
+nsecs_t HintSessionWrapper::getLastUpdate() {
+ return mLastFrameNotification;
+}
+
+// Requires passing in its shared_ptr since it shouldn't own a shared_ptr to itself
+void HintSessionWrapper::delayedDestroy(RenderThread& rt, nsecs_t delay,
+ std::shared_ptr<HintSessionWrapper> wrapperPtr) {
+ nsecs_t lastUpdate = wrapperPtr->getLastUpdate();
+ rt.queue().postDelayed(delay, [lastUpdate = lastUpdate, wrapper = wrapperPtr]() mutable {
+ if (wrapper->getLastUpdate() == lastUpdate) {
+ wrapper->destroy();
+ }
+ // Ensure the shared_ptr is killed at the end of the method
+ wrapper = nullptr;
+ });
}
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index 24b8150dd489..41891cd80a42 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -19,6 +19,7 @@
#include <android/performance_hint.h>
#include <future>
+#include <optional>
#include "utils/TimeUtils.h"
@@ -27,8 +28,12 @@ namespace uirenderer {
namespace renderthread {
+class RenderThread;
+
class HintSessionWrapper {
public:
+ friend class HintSessionWrapperTests;
+
HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId);
~HintSessionWrapper();
@@ -37,10 +42,16 @@ public:
void sendLoadResetHint();
void sendLoadIncreaseHint();
bool init();
+ void destroy();
+ bool alive();
+ nsecs_t getLastUpdate();
+ void delayedDestroy(renderthread::RenderThread& rt, nsecs_t delay,
+ std::shared_ptr<HintSessionWrapper> wrapperPtr);
private:
APerformanceHintSession* mHintSession = nullptr;
- std::future<APerformanceHintSession*> mHintSessionFuture;
+ // This needs to work concurrently for testing
+ std::optional<std::shared_future<APerformanceHintSession*>> mHintSessionFuture;
int mResetsSinceLastReport = 0;
nsecs_t mLastFrameNotification = 0;
@@ -54,6 +65,29 @@ private:
static constexpr nsecs_t kResetHintTimeout = 100_ms;
static constexpr int64_t kSanityCheckLowerBound = 100_us;
static constexpr int64_t kSanityCheckUpperBound = 10_s;
+ static constexpr int64_t kDefaultTargetDuration = 16666667;
+
+ // Allows easier stub when testing
+ class HintSessionBinding {
+ public:
+ virtual ~HintSessionBinding() = default;
+ virtual void init();
+ APerformanceHintManager* (*getManager)();
+ APerformanceHintSession* (*createSession)(APerformanceHintManager* manager,
+ const int32_t* tids, size_t tidCount,
+ int64_t defaultTarget) = nullptr;
+ void (*closeSession)(APerformanceHintSession* session) = nullptr;
+ void (*updateTargetWorkDuration)(APerformanceHintSession* session,
+ int64_t targetDuration) = nullptr;
+ void (*reportActualWorkDuration)(APerformanceHintSession* session,
+ int64_t actualDuration) = nullptr;
+ void (*sendHint)(APerformanceHintSession* session, int32_t hintId) = nullptr;
+
+ private:
+ bool mInitialized = false;
+ };
+
+ std::shared_ptr<HintSessionBinding> mBinding;
};
} /* namespace renderthread */
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
new file mode 100644
index 000000000000..5a10f4f959aa
--- /dev/null
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -0,0 +1,287 @@
+/*
+ * 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.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <private/performance_hint_private.h>
+#include <renderthread/HintSessionWrapper.h>
+#include <utils/Log.h>
+
+#include <chrono>
+
+#include "Properties.h"
+#include "tests/common/TestUtils.h"
+
+using namespace testing;
+using namespace std::chrono_literals;
+using namespace android::uirenderer::renderthread;
+
+APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123);
+APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456);
+int uiThreadId = 1;
+int renderThreadId = 2;
+
+namespace android::uirenderer::renderthread {
+
+class HintSessionWrapperTests : public testing::Test {
+public:
+ void SetUp() override;
+ void TearDown() override;
+
+protected:
+ std::shared_ptr<HintSessionWrapper> mWrapper;
+
+ std::promise<int> blockDestroyCallUntil;
+ std::promise<int> waitForDestroyFinished;
+
+ class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding {
+ public:
+ void init() override;
+
+ MOCK_METHOD(APerformanceHintManager*, fakeGetManager, ());
+ MOCK_METHOD(APerformanceHintSession*, fakeCreateSession,
+ (APerformanceHintManager*, const int32_t*, size_t, int64_t));
+ MOCK_METHOD(void, fakeCloseSession, (APerformanceHintSession*));
+ MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
+ MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
+ MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t));
+ // Needs to be on the binding so it can be accessed from static methods
+ std::promise<int> allowCreationToFinish;
+ };
+
+ // Must be static so it can have function pointers we can point to with static methods
+ static std::shared_ptr<MockHintSessionBinding> sMockBinding;
+
+ static void allowCreationToFinish() { sMockBinding->allowCreationToFinish.set_value(1); }
+ void allowDelayedDestructionToStart() { blockDestroyCallUntil.set_value(1); }
+ void waitForDelayedDestructionToFinish() { waitForDestroyFinished.get_future().wait(); }
+
+ // Must be static so we can point to them as normal fn pointers with HintSessionBinding
+ static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); };
+ static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager,
+ const int32_t* ids, size_t idsSize,
+ int64_t initialTarget) {
+ return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
+ }
+ static APerformanceHintSession* stubManagedCreateSession(APerformanceHintManager* manager,
+ const int32_t* ids, size_t idsSize,
+ int64_t initialTarget) {
+ sMockBinding->allowCreationToFinish.get_future().wait();
+ return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
+ }
+ static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager,
+ const int32_t* ids, size_t idsSize,
+ int64_t initialTarget) {
+ std::this_thread::sleep_for(50ms);
+ return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
+ }
+ static void stubCloseSession(APerformanceHintSession* session) {
+ sMockBinding->fakeCloseSession(session);
+ };
+ static void stubUpdateTargetWorkDuration(APerformanceHintSession* session,
+ int64_t workDuration) {
+ sMockBinding->fakeUpdateTargetWorkDuration(session, workDuration);
+ }
+ static void stubReportActualWorkDuration(APerformanceHintSession* session,
+ int64_t workDuration) {
+ sMockBinding->fakeReportActualWorkDuration(session, workDuration);
+ }
+ static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
+ sMockBinding->fakeSendHint(session, hintId);
+ };
+ void waitForWrapperReady() {
+ if (mWrapper->mHintSessionFuture.has_value()) {
+ mWrapper->mHintSessionFuture->wait();
+ }
+ }
+ void scheduleDelayedDestroyManaged() {
+ TestUtils::runOnRenderThread([&](renderthread::RenderThread& rt) {
+ // Guaranteed to be scheduled first, allows destruction to start
+ rt.queue().postDelayed(0_ms, [&] { blockDestroyCallUntil.get_future().wait(); });
+ // Guaranteed to be scheduled second, destroys the session
+ mWrapper->delayedDestroy(rt, 1_ms, mWrapper);
+ // This is guaranteed to be queued after the destroy, signals that destruction is done
+ rt.queue().postDelayed(1_ms, [&] { waitForDestroyFinished.set_value(1); });
+ });
+ }
+};
+
+std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding>
+ HintSessionWrapperTests::sMockBinding;
+
+void HintSessionWrapperTests::SetUp() {
+ // Pretend it's supported even if we're in an emulator
+ Properties::useHintManager = true;
+ sMockBinding = std::make_shared<NiceMock<MockHintSessionBinding>>();
+ mWrapper = std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId);
+ mWrapper->mBinding = sMockBinding;
+ EXPECT_CALL(*sMockBinding, fakeGetManager).WillOnce(Return(managerPtr));
+ ON_CALL(*sMockBinding, fakeCreateSession).WillByDefault(Return(sessionPtr));
+}
+
+void HintSessionWrapperTests::MockHintSessionBinding::init() {
+ sMockBinding->getManager = &stubGetManager;
+ if (sMockBinding->createSession == nullptr) {
+ sMockBinding->createSession = &stubCreateSession;
+ }
+ sMockBinding->closeSession = &stubCloseSession;
+ sMockBinding->updateTargetWorkDuration = &stubUpdateTargetWorkDuration;
+ sMockBinding->reportActualWorkDuration = &stubReportActualWorkDuration;
+ sMockBinding->sendHint = &stubSendHint;
+}
+
+void HintSessionWrapperTests::TearDown() {
+ // Ensure that anything running on RT is completely finished
+ mWrapper = nullptr;
+ sMockBinding = nullptr;
+}
+
+TEST_F(HintSessionWrapperTests, destructorClosesBackgroundSession) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ sMockBinding->createSession = stubSlowCreateSession;
+ mWrapper->init();
+ mWrapper = nullptr;
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+}
+
+TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) {
+ EXPECT_CALL(*sMockBinding, fakeCreateSession(managerPtr, _, Gt(1), _)).Times(1);
+ mWrapper->init();
+ waitForWrapperReady();
+}
+
+TEST_F(HintSessionWrapperTests, loadUpHintsSendCorrectly) {
+ EXPECT_CALL(*sMockBinding,
+ fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
+ .Times(1);
+ mWrapper->init();
+ waitForWrapperReady();
+ mWrapper->sendLoadIncreaseHint();
+}
+
+TEST_F(HintSessionWrapperTests, loadResetHintsSendCorrectly) {
+ EXPECT_CALL(*sMockBinding,
+ fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET)))
+ .Times(1);
+ mWrapper->init();
+ waitForWrapperReady();
+ mWrapper->sendLoadResetHint();
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionWorksCorrectlyAndOnlyClosesOnce) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to ensure the wrapper grabs the promise value
+ mWrapper->init();
+
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // Schedule delayed destruction, allow it to run, and check when it's done
+ scheduleDelayedDestroyManaged();
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+ // If we then delete the wrapper, it shouldn't close the session again
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(_)).Times(0);
+ mWrapper = nullptr;
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionResolvesBeforeAsyncCreationFinishes) {
+ // Here we test whether queueing delayedDestroy works while creation is still happening, if
+ // creation happens after
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ sMockBinding->createSession = &stubManagedCreateSession;
+
+ // Start creating the session and destroying it at the same time
+ mWrapper->init();
+ scheduleDelayedDestroyManaged();
+
+ // Allow destruction to happen first
+ allowDelayedDestructionToStart();
+
+ // Make sure destruction has had time to happen
+ std::this_thread::sleep_for(50ms);
+
+ // Then, allow creation to finish after delayed destroy runs
+ allowCreationToFinish();
+
+ // Wait for destruction to finish
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishes) {
+ // Here we test whether queueing delayedDestroy works while creation is still happening, if
+ // creation happens before
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ sMockBinding->createSession = &stubManagedCreateSession;
+
+ // Start creating the session and destroying it at the same time
+ mWrapper->init();
+ scheduleDelayedDestroyManaged();
+
+ // Allow creation to happen first
+ allowCreationToFinish();
+
+ // Make sure creation has had time to happen
+ waitForWrapperReady();
+
+ // Then allow destruction to happen after creation is done
+ allowDelayedDestructionToStart();
+
+ // Wait for it to finish
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+ EXPECT_CALL(*sMockBinding,
+ fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
+ .Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, send a hint to update the timestamp
+ mWrapper->sendLoadIncreaseHint();
+
+ // Then, run the delayed deletion after sending the update
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it didn't close within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), true);
+}
+
+} // namespace android::uirenderer::renderthread
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 7bcd45c6b643..9aa2e1db4461 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -49,7 +49,7 @@ public:
*/
static void reinitializeAllFields(ShaderCache& cache) {
ShaderCache newCache = ShaderCache();
- std::lock_guard<std::mutex> lock(cache.mMutex);
+ std::lock_guard lock(cache.mMutex), newLock(newCache.mMutex);
// By order of declaration
cache.mInitialized = newCache.mInitialized;
cache.mBlobCache.reset(nullptr);
@@ -72,7 +72,7 @@ public:
* manually, as seen in the "terminate" testing helper function.
*/
static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) {
- std::lock_guard<std::mutex> lock(cache.mMutex);
+ std::lock_guard lock(cache.mMutex);
cache.mDeferredSaveDelayMs = saveDelayMs;
}
@@ -81,7 +81,7 @@ public:
* Next call to "initShaderDiskCache" will load again the in-memory cache from disk.
*/
static void terminate(ShaderCache& cache, bool saveContent) {
- std::lock_guard<std::mutex> lock(cache.mMutex);
+ std::lock_guard lock(cache.mMutex);
if (saveContent) {
cache.saveToDiskLocked();
}
@@ -93,6 +93,7 @@ public:
*/
template <typename T>
static bool validateCache(ShaderCache& cache, std::vector<T> hash) {
+ std::lock_guard lock(cache.mMutex);
return cache.validateCache(hash.data(), hash.size() * sizeof(T));
}
@@ -108,7 +109,7 @@ public:
*/
static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) {
{
- std::lock_guard<std::mutex> lock(cache.mMutex);
+ std::lock_guard lock(cache.mMutex);
ASSERT_TRUE(cache.mSavePending);
}
bool saving = true;
@@ -123,7 +124,7 @@ public:
usleep(delayMicroseconds);
elapsedMilliseconds += (float)delayMicroseconds / 1000;
- std::lock_guard<std::mutex> lock(cache.mMutex);
+ std::lock_guard lock(cache.mMutex);
saving = cache.mSavePending;
}
}