summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java119
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java22
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java9
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java52
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java186
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java31
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java69
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java57
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java42
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java32
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java23
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml5
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java108
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java7
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/MinimumDimensionActivity.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerState.java)14
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java173
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java90
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java6
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java4
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java24
-rw-r--r--libs/WindowManager/Shell/res/color/decor_button_dark_color.xml21
-rw-r--r--libs/WindowManager/Shell/res/color/decor_button_light_color.xml21
-rw-r--r--libs/WindowManager/Shell/res/color/decor_caption_title_color.xml23
-rw-r--r--libs/WindowManager/Shell/res/color/taskbar_background.xml3
-rw-r--r--libs/WindowManager/Shell/res/color/unfold_transition_background.xml19
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_caption_title.xml22
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml32
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml36
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml3
-rw-r--r--libs/WindowManager/Shell/res/layout/caption_window_decoration.xml45
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values/attrs.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java357
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java213
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsPool.java93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java303
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java351
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java104
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarInputEventReceiver.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java175
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java431
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OverScroll.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java73
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java173
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java418
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java1314
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerWindowManager.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivity.java110
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java150
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java326
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreen.java85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java762
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java376
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java348
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/MinimizedDockShadow.java99
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java386
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java209
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java8
-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.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java264
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl103
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java104
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java181
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java82
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java144
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java99
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java595
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java292
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java324
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java1333
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java298
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java224
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java253
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java79
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java219
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java95
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java)191
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java)279
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl)26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java)20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java174
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java199
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java288
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskFocusStateConsumer.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java99
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java270
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt128
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt115
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt104
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt128
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt121
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt178
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt109
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt117
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt76
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt39
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt158
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt116
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt104
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt126
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt110
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt115
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt124
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt129
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt196
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt153
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt169
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt155
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt160
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt149
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt122
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt228
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt114
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt113
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt136
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt133
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt111
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt148
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt123
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt139
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt59
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml26
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java61
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java123
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java104
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java77
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java36
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java142
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java181
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java166
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java96
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java)16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java52
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java341
-rw-r--r--libs/hwui/JankTracker.cpp5
-rw-r--r--libs/hwui/tests/unit/JankTrackerTests.cpp65
292 files changed, 7942 insertions, 14844 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
index 921552b6cfbb..68ff806c6765 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
@@ -199,7 +199,7 @@ public final class CommonFoldingFeature {
throw new IllegalArgumentException(
"Display feature rectangle cannot have zero width and height simultaneously.");
}
- this.mRect = rect;
+ this.mRect = new Rect(rect);
}
/** Returns the type of the feature. */
@@ -217,7 +217,7 @@ public final class CommonFoldingFeature {
/** Returns the bounds of the feature. */
@NonNull
public Rect getRect() {
- return mRect;
+ return new Rect(mRect);
}
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index fdcb7be597d5..cc2bb63ca8e1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -22,7 +22,6 @@ import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
import static androidx.window.common.CommonFoldingFeature.parseListFromString;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
@@ -30,22 +29,25 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
+import androidx.window.util.AcceptOnceConsumer;
import androidx.window.util.BaseDataProducer;
-import androidx.window.util.DataProducer;
import com.android.internal.R;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
/**
- * An implementation of {@link androidx.window.util.DataProducer} that returns the device's posture
- * by mapping the state returned from {@link DeviceStateManager} to values provided in the resources
- * config at {@link R.array#config_device_state_postures}.
+ * An implementation of {@link androidx.window.util.BaseDataProducer} that returns
+ * the device's posture by mapping the state returned from {@link DeviceStateManager} to
+ * values provided in the resources' config at {@link R.array#config_device_state_postures}.
*/
-public final class DeviceStateManagerFoldingFeatureProducer extends
- BaseDataProducer<List<CommonFoldingFeature>> {
+public final class DeviceStateManagerFoldingFeatureProducer
+ extends BaseDataProducer<List<CommonFoldingFeature>> {
private static final String TAG =
DeviceStateManagerFoldingFeatureProducer.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -54,15 +56,11 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
private int mCurrentDeviceState = INVALID_DEVICE_STATE;
- private final DeviceStateCallback mDeviceStateCallback = (state) -> {
- mCurrentDeviceState = state;
- notifyDataChanged();
- };
@NonNull
- private final DataProducer<String> mRawFoldSupplier;
+ private final BaseDataProducer<String> mRawFoldSupplier;
public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
- @NonNull DataProducer<String> rawFoldSupplier) {
+ @NonNull BaseDataProducer<String> rawFoldSupplier) {
mRawFoldSupplier = rawFoldSupplier;
String[] deviceStatePosturePairs = context.getResources()
.getStringArray(R.array.config_device_state_postures);
@@ -70,7 +68,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
if (deviceStatePostureMapping.length != 2) {
if (DEBUG) {
- Log.e(TAG, "Malformed device state posture pair: " + deviceStatePosturePair);
+ Log.e(TAG, "Malformed device state posture pair: "
+ + deviceStatePosturePair);
}
continue;
}
@@ -82,7 +81,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
posture = Integer.parseInt(deviceStatePostureMapping[1]);
} catch (NumberFormatException e) {
if (DEBUG) {
- Log.e(TAG, "Failed to parse device state or posture: " + deviceStatePosturePair,
+ Log.e(TAG, "Failed to parse device state or posture: "
+ + deviceStatePosturePair,
e);
}
continue;
@@ -92,32 +92,95 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
}
if (mDeviceStateToPostureMap.size() > 0) {
- context.getSystemService(DeviceStateManager.class)
- .registerCallback(context.getMainExecutor(), mDeviceStateCallback);
+ DeviceStateCallback deviceStateCallback = (state) -> {
+ mCurrentDeviceState = state;
+ mRawFoldSupplier.getData(this::notifyFoldingFeatureChange);
+ };
+ Objects.requireNonNull(context.getSystemService(DeviceStateManager.class))
+ .registerCallback(context.getMainExecutor(), deviceStateCallback);
}
}
- @Override
- @Nullable
- public Optional<List<CommonFoldingFeature>> getData() {
- final int globalHingeState = globalHingeState();
- Optional<String> displayFeaturesString = mRawFoldSupplier.getData();
- if (displayFeaturesString.isEmpty() || TextUtils.isEmpty(displayFeaturesString.get())) {
- return Optional.empty();
+ /**
+ * Add a callback to mCallbacks if there is no device state. This callback will be run
+ * once a device state is set. Otherwise,run the callback immediately.
+ */
+ private void runCallbackWhenValidState(@NonNull Consumer<List<CommonFoldingFeature>> callback,
+ String displayFeaturesString) {
+ if (isCurrentStateValid()) {
+ callback.accept(calculateFoldingFeature(displayFeaturesString));
+ } else {
+ // This callback will be added to mCallbacks and removed once it runs once.
+ AcceptOnceConsumer<List<CommonFoldingFeature>> singleRunCallback =
+ new AcceptOnceConsumer<>(this, callback);
+ addDataChangedCallback(singleRunCallback);
}
- return Optional.of(parseListFromString(displayFeaturesString.get(), globalHingeState));
+ }
+
+ /**
+ * Checks to find {@link DeviceStateManagerFoldingFeatureProducer#mCurrentDeviceState} in the
+ * {@link DeviceStateManagerFoldingFeatureProducer#mDeviceStateToPostureMap} which was
+ * initialized in the constructor of {@link DeviceStateManagerFoldingFeatureProducer}.
+ * Returns a boolean value of whether the device state is valid.
+ */
+ private boolean isCurrentStateValid() {
+ // If the device state is not found in the map, indexOfKey returns a negative number.
+ return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState) >= 0;
}
@Override
- protected void onListenersChanged(Set<Runnable> callbacks) {
+ protected void onListenersChanged(
+ @NonNull Set<Consumer<List<CommonFoldingFeature>>> callbacks) {
super.onListenersChanged(callbacks);
if (callbacks.isEmpty()) {
- mRawFoldSupplier.removeDataChangedCallback(this::notifyDataChanged);
+ mCurrentDeviceState = INVALID_DEVICE_STATE;
+ mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange);
+ } else {
+ mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange);
+ }
+ }
+
+ @NonNull
+ @Override
+ public Optional<List<CommonFoldingFeature>> getCurrentData() {
+ Optional<String> displayFeaturesString = mRawFoldSupplier.getCurrentData();
+ if (!isCurrentStateValid()) {
+ return Optional.empty();
+ } else {
+ return displayFeaturesString.map(this::calculateFoldingFeature);
+ }
+ }
+
+ /**
+ * Adds the data to the storeFeaturesConsumer when the data is ready.
+ * @param storeFeaturesConsumer a consumer to collect the data when it is first available.
+ */
+ public void getData(Consumer<List<CommonFoldingFeature>> storeFeaturesConsumer) {
+ mRawFoldSupplier.getData((String displayFeaturesString) -> {
+ if (TextUtils.isEmpty(displayFeaturesString)) {
+ storeFeaturesConsumer.accept(new ArrayList<>());
+ } else {
+ runCallbackWhenValidState(storeFeaturesConsumer, displayFeaturesString);
+ }
+ });
+ }
+
+ private void notifyFoldingFeatureChange(String displayFeaturesString) {
+ if (!isCurrentStateValid()) {
+ return;
+ }
+ if (TextUtils.isEmpty(displayFeaturesString)) {
+ notifyDataChanged(new ArrayList<>());
} else {
- mRawFoldSupplier.addDataChangedCallback(this::notifyDataChanged);
+ notifyDataChanged(calculateFoldingFeature(displayFeaturesString));
}
}
+ private List<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) {
+ final int globalHingeState = globalHingeState();
+ return parseListFromString(displayFeaturesString, globalHingeState);
+ }
+
private int globalHingeState() {
return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
index 69ad1badce60..7906342d445d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
@@ -32,6 +32,7 @@ import com.android.internal.R;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Implementation of {@link androidx.window.util.DataProducer} that produces a
@@ -40,7 +41,7 @@ import java.util.Set;
* settings where the {@link String} property is saved with the key
* {@link RawFoldingFeatureProducer#DISPLAY_FEATURES}. If this value is null or empty then the
* value in {@link android.content.res.Resources} is used. If both are empty then
- * {@link RawFoldingFeatureProducer#getData()} returns an empty object.
+ * {@link RawFoldingFeatureProducer#getData} returns an empty object.
* {@link RawFoldingFeatureProducer} listens to changes in the setting so that it can override
* the system {@link CommonFoldingFeature} data.
*/
@@ -63,12 +64,13 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
@Override
@NonNull
- public Optional<String> getData() {
+ public void getData(Consumer<String> dataConsumer) {
String displayFeaturesString = getFeatureString();
if (displayFeaturesString == null) {
- return Optional.empty();
+ dataConsumer.accept("");
+ } else {
+ dataConsumer.accept(displayFeaturesString);
}
- return Optional.of(displayFeaturesString);
}
/**
@@ -84,7 +86,7 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
}
@Override
- protected void onListenersChanged(Set<Runnable> callbacks) {
+ protected void onListenersChanged(Set<Consumer<String>> callbacks) {
if (callbacks.isEmpty()) {
unregisterObserversIfNeeded();
} else {
@@ -92,6 +94,12 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
}
}
+ @NonNull
+ @Override
+ public Optional<String> getCurrentData() {
+ return Optional.of(getFeatureString());
+ }
+
/**
* Registers settings observers, if needed. When settings observers are registered for this
* producer callbacks for changes in data will be triggered.
@@ -125,8 +133,8 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (mDisplayFeaturesUri.equals(uri)) {
- notifyDataChanged();
+ notifyDataChanged(getFeatureString());
}
}
}
-}
+} \ No newline at end of file
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 44af1a9fd780..f09a91018bf0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -18,6 +18,8 @@ package androidx.window.extensions.embedding;
import android.annotation.NonNull;
import android.app.Activity;
+import android.util.Pair;
+import android.util.Size;
/**
* Client-side descriptor of a split that holds two containers.
@@ -66,6 +68,13 @@ class SplitContainer {
return mSplitRule;
}
+ /** Returns the minimum dimension pair of primary container and secondary container. */
+ @NonNull
+ Pair<Size, Size> getMinDimensionsPair() {
+ return new Pair<>(mPrimaryContainer.getMinDimensions(),
+ mSecondaryContainer.getMinDimensions());
+ }
+
boolean isPlaceholderContainer() {
return (mSplitRule instanceof SplitPlaceholderRule);
}
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 575c3f002791..242e9ab6beee 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -24,9 +24,11 @@ import static androidx.window.extensions.embedding.SplitContainer.getFinishSecon
import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
+import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
+import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
+import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
+import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityClient;
import android.app.ActivityOptions;
@@ -43,11 +45,15 @@ import android.os.IBinder;
import android.os.Looper;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
import android.util.SparseArray;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import com.android.internal.annotations.VisibleForTesting;
@@ -63,7 +69,7 @@ import java.util.function.Consumer;
*/
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
ActivityEmbeddingComponent {
- private static final String TAG = "SplitController";
+ static final String TAG = "SplitController";
@VisibleForTesting
@GuardedBy("mLock")
@@ -350,7 +356,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (!(rule instanceof SplitRule)) {
continue;
}
- if (mPresenter.shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) {
+ if (shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) {
return true;
}
}
@@ -610,11 +616,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
primaryActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
- && canReuseContainer(splitRule, splitContainer.getSplitRule())) {
+ && canReuseContainer(splitRule, splitContainer.getSplitRule())
+ && !boundsSmallerThanMinDimensions(primaryContainer.getLastRequestedBounds(),
+ getMinDimensions(primaryActivity))) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
- if (secondaryContainer == getContainerWithActivity(secondaryActivity)) {
+ if (secondaryContainer == getContainerWithActivity(secondaryActivity)
+ && !boundsSmallerThanMinDimensions(secondaryContainer.getLastRequestedBounds(),
+ getMinDimensions(secondaryActivity))) {
// The activity is already in the target TaskFragment.
return true;
}
@@ -791,9 +801,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
&& (canReuseContainer(splitRule, splitContainer.getSplitRule())
// TODO(b/231845476) we should always respect clearTop.
|| !respectClearTop)) {
- // Can launch in the existing secondary container if the rules share the same
- // presentation.
- return splitContainer.getSecondaryContainer();
+ final Rect secondaryBounds = splitContainer.getSecondaryContainer()
+ .getLastRequestedBounds();
+ if (secondaryBounds.isEmpty()
+ || !boundsSmallerThanMinDimensions(secondaryBounds,
+ getMinDimensions(intent))) {
+ // Can launch in the existing secondary container if the rules share the same
+ // presentation.
+ return splitContainer.getSecondaryContainer();
+ }
}
// Create a new TaskFragment to split with the primary activity for the new activity.
return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent,
@@ -1117,8 +1133,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Check if there is enough space for launch
final SplitPlaceholderRule placeholderRule = getPlaceholderRule(activity);
- if (placeholderRule == null || !mPresenter.shouldShowSideBySide(
- mPresenter.getParentContainerBounds(activity), placeholderRule)) {
+
+ if (placeholderRule == null) {
+ return false;
+ }
+
+ final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
+ placeholderRule.getPlaceholderIntent());
+ if (!shouldShowSideBySide(
+ mPresenter.getParentContainerBounds(activity), placeholderRule,
+ minDimensionsPair)) {
return false;
}
@@ -1161,7 +1185,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return false;
}
- if (mPresenter.shouldShowSideBySide(splitContainer)) {
+ if (shouldShowSideBySide(splitContainer)) {
return false;
}
@@ -1233,7 +1257,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Splits that are not showing side-by-side are reported as having 0 split
// ratio, since by definition in the API the primary container occupies no
// width of the split when covered by the secondary.
- mPresenter.shouldShowSideBySide(container)
+ shouldShowSideBySide(container)
? container.getSplitRule().getSplitRatio()
: 0.0f);
splitStates.add(splitState);
@@ -1402,7 +1426,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
// Decide whether the associated container should be retained based on the current
// presentation mode.
- if (mPresenter.shouldShowSideBySide(splitContainer)) {
+ if (shouldShowSideBySide(splitContainer)) {
return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior);
} else {
return !shouldFinishAssociatedContainerWhenStacked(finishBehavior);
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 ac3b05a0e825..1b79ad999435 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -16,15 +16,23 @@
package androidx.window.extensions.embedding;
+import static android.content.pm.PackageManager.MATCH_ALL;
+
import android.app.Activity;
+import android.app.ActivityThread;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.LayoutDirection;
+import android.util.Pair;
+import android.util.Size;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowMetrics;
@@ -34,6 +42,8 @@ import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.concurrent.Executor;
/**
@@ -41,9 +51,12 @@ import java.util.concurrent.Executor;
* {@link SplitController}.
*/
class SplitPresenter extends JetpackTaskFragmentOrganizer {
- private static final int POSITION_START = 0;
- private static final int POSITION_END = 1;
- private static final int POSITION_FILL = 2;
+ @VisibleForTesting
+ static final int POSITION_START = 0;
+ @VisibleForTesting
+ static final int POSITION_END = 1;
+ @VisibleForTesting
+ static final int POSITION_FILL = 2;
@IntDef(value = {
POSITION_START,
@@ -103,8 +116,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) {
final Rect parentBounds = getParentContainerBounds(primaryActivity);
+ final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
+ primaryActivity, secondaryIntent);
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
- isLtr(primaryActivity, rule));
+ primaryActivity, minDimensionsPair);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
primaryActivity, primaryRectBounds, null);
@@ -113,7 +128,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final TaskFragmentContainer secondaryContainer = mController.newContainer(
secondaryIntent, primaryActivity, taskId);
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
- rule, isLtr(primaryActivity, rule));
+ rule, primaryActivity, minDimensionsPair);
final int windowingMode = mController.getTaskContainer(taskId)
.getWindowingModeForSplitTaskFragment(secondaryRectBounds);
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
@@ -121,7 +136,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
windowingMode);
// Set adjacent to each other so that the containers below will be invisible.
- setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
+ setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
+ minDimensionsPair);
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
@@ -144,13 +160,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final WindowContainerTransaction wct = new WindowContainerTransaction();
final Rect parentBounds = getParentContainerBounds(primaryActivity);
+ final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
+ secondaryActivity);
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
- isLtr(primaryActivity, rule));
+ primaryActivity, minDimensionsPair);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
primaryActivity, primaryRectBounds, null);
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
- isLtr(primaryActivity, rule));
+ primaryActivity, minDimensionsPair);
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
secondaryActivity);
TaskFragmentContainer containerToAvoid = primaryContainer;
@@ -162,7 +180,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
secondaryActivity, secondaryRectBounds, containerToAvoid);
// Set adjacent to each other so that the containers below will be invisible.
- setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
+ setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
+ minDimensionsPair);
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
@@ -211,10 +230,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) {
final Rect parentBounds = getParentContainerBounds(launchingActivity);
+ final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
+ launchingActivity, activityIntent);
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
- isLtr(launchingActivity, rule));
+ launchingActivity, minDimensionsPair);
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
- isLtr(launchingActivity, rule));
+ launchingActivity, minDimensionsPair);
TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
launchingActivity);
@@ -258,11 +279,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
if (activity == null) {
return;
}
- final boolean isLtr = isLtr(activity, rule);
+ final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
- isLtr);
+ activity, minDimensionsPair);
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
- isLtr);
+ activity, minDimensionsPair);
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
// Whether the placeholder is becoming side-by-side with the primary from fullscreen.
final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer()
@@ -273,7 +294,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// are created again.
resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
- setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
+ setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
+ minDimensionsPair);
if (isPlaceholderBecomingSplit) {
// When placeholder is shown in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
@@ -287,11 +309,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer primaryContainer,
- @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule) {
+ @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule,
+ @NonNull Pair<Size, Size> minDimensionsPair) {
final Rect parentBounds = getParentContainerBounds(primaryContainer);
// Clear adjacent TaskFragments if the container is shown in fullscreen, or the
// secondaryContainer could not be finished.
- if (!shouldShowSideBySide(parentBounds, splitRule)) {
+ if (!shouldShowSideBySide(parentBounds, splitRule, minDimensionsPair)) {
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
null /* secondary */, null /* splitRule */);
} else {
@@ -373,41 +396,132 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
super.updateWindowingMode(wct, fragmentToken, windowingMode);
}
- boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) {
+ static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) {
+ return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */);
+ }
+
+ static boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) {
final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer());
- return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule());
+
+ return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule(),
+ splitContainer.getMinDimensionsPair());
}
- boolean shouldShowSideBySide(@Nullable Rect parentBounds, @NonNull SplitRule rule) {
+ static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule,
+ @Nullable Pair<Size, Size> minDimensionsPair) {
// TODO(b/190433398): Supply correct insets.
final WindowMetrics parentMetrics = new WindowMetrics(parentBounds,
new WindowInsets(new Rect()));
- return rule.checkParentMetrics(parentMetrics);
+ // Don't show side by side if bounds is not qualified.
+ if (!rule.checkParentMetrics(parentMetrics)) {
+ return false;
+ }
+ final float splitRatio = rule.getSplitRatio();
+ // We only care the size of the bounds regardless of its position.
+ final Rect primaryBounds = getPrimaryBounds(parentBounds, splitRatio, true /* isLtr */);
+ final Rect secondaryBounds = getSecondaryBounds(parentBounds, splitRatio, true /* isLtr */);
+
+ if (minDimensionsPair == null) {
+ return true;
+ }
+ return !boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first)
+ && !boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second);
}
@NonNull
- private Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds,
- @NonNull SplitRule rule, boolean isLtr) {
- if (!shouldShowSideBySide(parentBounds, rule)) {
- return new Rect();
+ static Pair<Size, Size> getActivitiesMinDimensionsPair(Activity primaryActivity,
+ Activity secondaryActivity) {
+ return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity));
+ }
+
+ @NonNull
+ static Pair<Size, Size> getActivityIntentMinDimensionsPair(Activity primaryActivity,
+ Intent secondaryIntent) {
+ return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryIntent));
+ }
+
+ @Nullable
+ static Size getMinDimensions(@Nullable Activity activity) {
+ if (activity == null) {
+ return null;
+ }
+ final ActivityInfo.WindowLayout windowLayout = activity.getActivityInfo().windowLayout;
+ if (windowLayout == null) {
+ return null;
}
+ return new Size(windowLayout.minWidth, windowLayout.minHeight);
+ }
+ // TODO(b/232871351): find a light-weight approach for this check.
+ @Nullable
+ static Size getMinDimensions(@Nullable Intent intent) {
+ if (intent == null) {
+ return null;
+ }
+ final PackageManager packageManager = ActivityThread.currentActivityThread()
+ .getApplication().getPackageManager();
+ final ResolveInfo resolveInfo = packageManager.resolveActivity(intent,
+ PackageManager.ResolveInfoFlags.of(MATCH_ALL));
+ if (resolveInfo == null) {
+ return null;
+ }
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ return null;
+ }
+ final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout;
+ if (windowLayout == null) {
+ return null;
+ }
+ return new Size(windowLayout.minWidth, windowLayout.minHeight);
+ }
+
+ static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
+ @Nullable Size minDimensions) {
+ if (minDimensions == null) {
+ return false;
+ }
+ return bounds.width() < minDimensions.getWidth()
+ || bounds.height() < minDimensions.getHeight();
+ }
+
+ @VisibleForTesting
+ @NonNull
+ static Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds,
+ @NonNull SplitRule rule, @NonNull Activity primaryActivity,
+ @Nullable Pair<Size, Size> minDimensionsPair) {
+ if (!shouldShowSideBySide(parentBounds, rule, minDimensionsPair)) {
+ return new Rect();
+ }
+ final boolean isLtr = isLtr(primaryActivity, rule);
final float splitRatio = rule.getSplitRatio();
- final float rtlSplitRatio = 1 - splitRatio;
+
switch (position) {
case POSITION_START:
- return isLtr ? getLeftContainerBounds(parentBounds, splitRatio)
- : getRightContainerBounds(parentBounds, rtlSplitRatio);
+ return getPrimaryBounds(parentBounds, splitRatio, isLtr);
case POSITION_END:
- return isLtr ? getRightContainerBounds(parentBounds, splitRatio)
- : getLeftContainerBounds(parentBounds, rtlSplitRatio);
+ return getSecondaryBounds(parentBounds, splitRatio, isLtr);
case POSITION_FILL:
- return parentBounds;
+ default:
+ return new Rect();
}
- return parentBounds;
}
- private Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
+ @NonNull
+ private static Rect getPrimaryBounds(@NonNull Rect parentBounds, float splitRatio,
+ boolean isLtr) {
+ return isLtr ? getLeftContainerBounds(parentBounds, splitRatio)
+ : getRightContainerBounds(parentBounds, 1 - splitRatio);
+ }
+
+ @NonNull
+ private static Rect getSecondaryBounds(@NonNull Rect parentBounds, float splitRatio,
+ boolean isLtr) {
+ return isLtr ? getRightContainerBounds(parentBounds, splitRatio)
+ : getLeftContainerBounds(parentBounds, 1 - splitRatio);
+ }
+
+ private static Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
return new Rect(
parentBounds.left,
parentBounds.top,
@@ -415,7 +529,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
parentBounds.bottom);
}
- private Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
+ private static Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
return new Rect(
(int) (parentBounds.left + parentBounds.width() * splitRatio),
parentBounds.top,
@@ -427,7 +541,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* Checks if a split with the provided rule should be displays in left-to-right layout
* direction, either always or with the current configuration.
*/
- private boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) {
+ private static boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) {
switch (rule.getLayoutDirection()) {
case LayoutDirection.LOCALE:
return context.getResources().getConfiguration().getLayoutDirection()
@@ -441,7 +555,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@NonNull
- Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) {
+ static Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) {
return container.getTaskContainer().getTaskBounds();
}
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 624cde50ff72..abf32a26efa2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -26,6 +26,7 @@ import android.content.Intent;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
+import android.util.Size;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
@@ -414,6 +415,11 @@ class TaskFragmentContainer {
}
}
+ @NonNull
+ Rect getLastRequestedBounds() {
+ return mLastRequestedBounds;
+ }
+
/**
* Checks if last requested windowing mode is equal to the provided value.
*/
@@ -439,6 +445,31 @@ class TaskFragmentContainer {
return mTaskContainer;
}
+ @Nullable
+ Size getMinDimensions() {
+ if (mInfo == null) {
+ return null;
+ }
+ int maxMinWidth = mInfo.getMinimumWidth();
+ int maxMinHeight = mInfo.getMinimumHeight();
+ for (Activity activity : mPendingAppearedActivities) {
+ final Size minDimensions = SplitPresenter.getMinDimensions(activity);
+ if (minDimensions == null) {
+ continue;
+ }
+ maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth());
+ maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight());
+ }
+ if (mPendingAppearedIntent != null) {
+ final Size minDimensions = SplitPresenter.getMinDimensions(mPendingAppearedIntent);
+ if (minDimensions != null) {
+ maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth());
+ maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight());
+ }
+ }
+ return new Size(maxMinWidth, maxMinHeight);
+ }
+
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
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 a6f638822d10..cfb32050e32f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -43,7 +43,6 @@ import androidx.window.util.DataProducer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
@@ -63,7 +62,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
- public WindowLayoutComponentImpl(Context context) {
+ public WindowLayoutComponentImpl(@NonNull Context context) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
RawFoldingFeatureProducer foldingFeatureProducer = new RawFoldingFeatureProducer(context);
@@ -80,8 +79,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*/
public void addWindowLayoutInfoListener(@NonNull Activity activity,
@NonNull Consumer<WindowLayoutInfo> consumer) {
+ mFoldingFeatureProducer.getData((features) -> {
+ // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
+ WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, features);
+ consumer.accept(newWindowLayout);
+ });
mWindowLayoutChangeListeners.put(activity, consumer);
- onDisplayFeaturesChanged();
}
/**
@@ -89,18 +92,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*
* @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
*/
- public void removeWindowLayoutInfoListener(
- @NonNull Consumer<WindowLayoutInfo> consumer) {
+ public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) {
mWindowLayoutChangeListeners.values().remove(consumer);
- onDisplayFeaturesChanged();
- }
-
- void updateWindowLayout(@NonNull Activity activity,
- @NonNull WindowLayoutInfo newLayout) {
- Consumer<WindowLayoutInfo> consumer = mWindowLayoutChangeListeners.get(activity);
- if (consumer != null) {
- consumer.accept(newLayout);
- }
}
@NonNull
@@ -108,7 +101,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return mWindowLayoutChangeListeners.keySet();
}
- @NonNull
private boolean isListeningForLayoutChanges(IBinder token) {
for (Activity activity: getActivitiesListeningForLayoutChanges()) {
if (token.equals(activity.getWindow().getAttributes().token)) {
@@ -125,12 +117,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
/**
* A convenience method to translate from the common feature state to the extensions feature
* state. More specifically, translates from {@link CommonFoldingFeature.State} to
- * {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED}. If it is not
+ * {@link FoldingFeature#STATE_FLAT} or {@link FoldingFeature#STATE_HALF_OPENED}. If it is not
* possible to translate, then we will return a {@code null} value.
*
* @param state if it matches a value in {@link CommonFoldingFeature.State}, {@code null}
- * otherwise. @return a {@link FoldingFeature.STATE_FLAT} or
- * {@link FoldingFeature.STATE_HALF_OPENED} if the given state matches a value in
+ * otherwise. @return a {@link FoldingFeature#STATE_FLAT} or
+ * {@link FoldingFeature#STATE_HALF_OPENED} if the given state matches a value in
* {@link CommonFoldingFeature.State} and {@code null} otherwise.
*/
@Nullable
@@ -144,17 +136,24 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
}
- private void onDisplayFeaturesChanged() {
+ private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
for (Activity activity : getActivitiesListeningForLayoutChanges()) {
- WindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
- updateWindowLayout(activity, newLayout);
+ // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
+ Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(activity);
+ WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, storedFeatures);
+ layoutConsumer.accept(newWindowLayout);
}
}
- @NonNull
- private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
- List<DisplayFeature> displayFeatures = getDisplayFeatures(activity);
- return new WindowLayoutInfo(displayFeatures);
+ /**
+ * Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a
+ * valid state is found.
+ * @param activity a proxy for the {@link android.view.Window} that contains the
+ */
+ private WindowLayoutInfo getWindowLayoutInfo(
+ @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
+ List<DisplayFeature> displayFeatureList = getDisplayFeatures(activity, storedFeatures);
+ return new WindowLayoutInfo(displayFeatureList);
}
/**
@@ -172,26 +171,21 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*
* @param activity a proxy for the {@link android.view.Window} that contains the
* {@link DisplayFeature}.
- * @return a {@link List} of valid {@link DisplayFeature} that
* are within the {@link android.view.Window} of the {@link Activity}
*/
- private List<DisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
+ private List<DisplayFeature> getDisplayFeatures(
+ @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
List<DisplayFeature> features = new ArrayList<>();
int displayId = activity.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) {
Log.w(TAG, "This sample doesn't support display features on secondary displays");
return features;
- }
-
- if (activity.isInMultiWindowMode()) {
+ } else if (activity.isInMultiWindowMode()) {
// 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 features;
- }
-
- Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData();
- if (storedFeatures.isPresent()) {
- for (CommonFoldingFeature baseFeature : storedFeatures.get()) {
+ } else {
+ for (CommonFoldingFeature baseFeature : storedFeatures) {
Integer state = convertToExtensionState(baseFeature.getState());
if (state == null) {
continue;
@@ -205,8 +199,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
}
}
+ return features;
}
- return features;
}
/**
@@ -233,7 +227,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private void onDisplayFeaturesChangedIfListening(Activity activity) {
IBinder token = activity.getWindow().getAttributes().token;
if (token == null || isListeningForLayoutChanges(token)) {
- onDisplayFeaturesChanged();
+ mFoldingFeatureProducer.getData(
+ WindowLayoutComponentImpl.this::onDisplayFeaturesChanged);
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 970f0a2af632..5bfb0ebdcaa8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -28,41 +28,42 @@ import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.common.RawFoldingFeatureProducer;
-import androidx.window.util.DataProducer;
+import androidx.window.util.BaseDataProducer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Optional;
/**
* Reference implementation of androidx.window.sidecar OEM interface for use with
* WindowManager Jetpack.
*/
class SampleSidecarImpl extends StubSidecar {
- private static final String TAG = "SampleSidecar";
-
- private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
-
+ private List<CommonFoldingFeature> mStoredFeatures = new ArrayList<>();
SampleSidecarImpl(Context context) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
- DataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context);
- mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context,
- settingsFeatureProducer);
+ BaseDataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context);
+ BaseDataProducer<List<CommonFoldingFeature>> foldingFeatureProducer =
+ new DeviceStateManagerFoldingFeatureProducer(context,
+ settingsFeatureProducer);
- mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
+ foldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
- private void onDisplayFeaturesChanged() {
+ private void setStoredFeatures(List<CommonFoldingFeature> storedFeatures) {
+ mStoredFeatures = storedFeatures;
+ }
+
+ private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
+ setStoredFeatures(storedFeatures);
updateDeviceState(getDeviceState());
for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
@@ -79,16 +80,16 @@ class SampleSidecarImpl extends StubSidecar {
}
private int deviceStateFromFeature() {
- List<CommonFoldingFeature> storedFeatures = mFoldingFeatureProducer.getData()
- .orElse(Collections.emptyList());
- for (int i = 0; i < storedFeatures.size(); i++) {
- CommonFoldingFeature feature = storedFeatures.get(i);
+ for (int i = 0; i < mStoredFeatures.size(); i++) {
+ CommonFoldingFeature feature = mStoredFeatures.get(i);
final int state = feature.getState();
switch (state) {
case CommonFoldingFeature.COMMON_STATE_FLAT:
return SidecarDeviceState.POSTURE_OPENED;
case CommonFoldingFeature.COMMON_STATE_HALF_OPENED:
return SidecarDeviceState.POSTURE_HALF_OPENED;
+ case CommonFoldingFeature.COMMON_STATE_UNKNOWN:
+ return SidecarDeviceState.POSTURE_UNKNOWN;
}
}
return SidecarDeviceState.POSTURE_UNKNOWN;
@@ -109,7 +110,6 @@ class SampleSidecarImpl extends StubSidecar {
private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
int displayId = activity.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) {
- Log.w(TAG, "This sample doesn't support display features on secondary displays");
return Collections.emptyList();
}
@@ -119,18 +119,15 @@ class SampleSidecarImpl extends StubSidecar {
return Collections.emptyList();
}
- Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData();
List<SidecarDisplayFeature> features = new ArrayList<>();
- if (storedFeatures.isPresent()) {
- for (CommonFoldingFeature baseFeature : storedFeatures.get()) {
- SidecarDisplayFeature feature = new SidecarDisplayFeature();
- Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
- transformToWindowSpaceRect(activity, featureRect);
- feature.setRect(featureRect);
- feature.setType(baseFeature.getType());
- features.add(feature);
- }
+ for (CommonFoldingFeature baseFeature : mStoredFeatures) {
+ SidecarDisplayFeature feature = new SidecarDisplayFeature();
+ Rect featureRect = baseFeature.getRect();
+ rotateRectToDisplayRotation(displayId, featureRect);
+ transformToWindowSpaceRect(activity, featureRect);
+ feature.setRect(featureRect);
+ feature.setType(baseFeature.getType());
+ features.add(feature);
}
return Collections.unmodifiableList(features);
}
@@ -138,7 +135,7 @@ class SampleSidecarImpl extends StubSidecar {
@Override
protected void onListenersChanged() {
if (hasListeners()) {
- onDisplayFeaturesChanged();
+ onDisplayFeaturesChanged(mStoredFeatures);
}
}
@@ -158,7 +155,7 @@ class SampleSidecarImpl extends StubSidecar {
private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) {
IBinder token = activity.getWindow().getAttributes().token;
if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) {
- onDisplayFeaturesChanged();
+ onDisplayFeaturesChanged(mStoredFeatures);
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
new file mode 100644
index 000000000000..7624b693ac43
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
@@ -0,0 +1,42 @@
+/*
+ * 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 androidx.window.util;
+
+import android.annotation.NonNull;
+
+import java.util.function.Consumer;
+
+/**
+ * A base class that works with {@link BaseDataProducer} to add/remove a consumer that should
+ * only be used once when {@link BaseDataProducer#notifyDataChanged} is called.
+ * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
+ */
+public class AcceptOnceConsumer<T> implements Consumer<T> {
+ private final Consumer<T> mCallback;
+ private final DataProducer<T> mProducer;
+
+ public AcceptOnceConsumer(@NonNull DataProducer<T> producer, @NonNull Consumer<T> callback) {
+ mProducer = producer;
+ mCallback = callback;
+ }
+
+ @Override
+ public void accept(@NonNull T t) {
+ mCallback.accept(t);
+ mProducer.removeDataChangedCallback(this);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index 930db3b701b7..0da44ac36a6e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -19,38 +19,48 @@ package androidx.window.util;
import androidx.annotation.NonNull;
import java.util.LinkedHashSet;
+import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Base class that provides the implementation for the callback mechanism of the
* {@link DataProducer} API.
*
- * @param <T> The type of data this producer returns through {@link #getData()}.
+ * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
*/
public abstract class BaseDataProducer<T> implements DataProducer<T> {
- private final Set<Runnable> mCallbacks = new LinkedHashSet<>();
+ private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>();
@Override
- public final void addDataChangedCallback(@NonNull Runnable callback) {
+ public final void addDataChangedCallback(@NonNull Consumer<T> callback) {
mCallbacks.add(callback);
+ Optional<T> currentData = getCurrentData();
+ currentData.ifPresent(callback);
onListenersChanged(mCallbacks);
}
@Override
- public final void removeDataChangedCallback(@NonNull Runnable callback) {
+ public final void removeDataChangedCallback(@NonNull Consumer<T> callback) {
mCallbacks.remove(callback);
onListenersChanged(mCallbacks);
}
- protected void onListenersChanged(Set<Runnable> callbacks) {}
+ protected void onListenersChanged(Set<Consumer<T>> callbacks) {}
/**
- * Called to notify all registered callbacks that the data provided by {@link #getData()} has
- * changed.
+ * @return the current data if available and {@code Optional.empty()} otherwise.
*/
- protected void notifyDataChanged() {
- for (Runnable callback : mCallbacks) {
- callback.run();
+ @NonNull
+ public abstract Optional<T> getCurrentData();
+
+ /**
+ * Called to notify all registered consumers that the data provided
+ * by {@link DataProducer#getData} has changed.
+ */
+ protected void notifyDataChanged(T value) {
+ for (Consumer<T> callback : mCallbacks) {
+ callback.accept(value);
}
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
index d4d1a23b756b..ec301dc34aaa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
@@ -18,26 +18,27 @@ package androidx.window.util;
import android.annotation.NonNull;
-import java.util.Optional;
+import java.util.function.Consumer;
/**
- * Produces data through {@link #getData()} and provides a mechanism for receiving a callback when
- * the data managed by the produces has changed.
+ * Produces data through {@link DataProducer#getData} and provides a mechanism for receiving
+ * a callback when the data managed by the produces has changed.
*
- * @param <T> The type of data this producer returns through {@link #getData()}.
+ * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
*/
public interface DataProducer<T> {
/**
- * Returns the data currently stored in the provider, or {@link Optional#empty()} if the
- * provider has no data.
+ * Emits the first available data at that point in time.
+ * @param dataConsumer a {@link Consumer} that will receive one value.
*/
- Optional<T> getData();
+ void getData(@NonNull Consumer<T> dataConsumer);
/**
- * Adds a callback to be notified when the data returned from {@link #getData()} has changed.
+ * Adds a callback to be notified when the data returned
+ * from {@link DataProducer#getData} has changed.
*/
- void addDataChangedCallback(@NonNull Runnable callback);
+ void addDataChangedCallback(@NonNull Consumer<T> callback);
- /** Removes a callback previously added with {@link #addDataChangedCallback(Runnable)}. */
- void removeDataChangedCallback(@NonNull Runnable callback);
+ /** Removes a callback previously added with {@link #addDataChangedCallback(Consumer)}. */
+ void removeDataChangedCallback(@NonNull Consumer<T> callback);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml b/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml
index b12b6f6f0ef1..c736e9ed971e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml
+++ b/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml
@@ -22,6 +22,11 @@
<application android:debuggable="true" android:largeHeap="true">
<uses-library android:name="android.test.mock" />
<uses-library android:name="android.test.runner" />
+
+ <activity android:name="androidx.window.extensions.embedding.MinimumDimensionActivity">
+ <layout android:minWidth="600px"
+ android:minHeight="1200px"/>
+ </activity>
</application>
<instrumentation
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
new file mode 100644
index 000000000000..835c40365cda
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -0,0 +1,108 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
+import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
+
+import static org.mockito.Mockito.mock;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.Pair;
+import android.window.TaskFragmentInfo;
+import android.window.WindowContainerToken;
+
+import java.util.Collections;
+
+public class EmbeddingTestUtils {
+ static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
+ static final int TASK_ID = 10;
+ static final float SPLIT_RATIO = 0.5f;
+ /** Default finish behavior in Jetpack. */
+ static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER;
+ static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS;
+
+ private EmbeddingTestUtils() {}
+
+ /** Gets the bounds of a TaskFragment that is in split. */
+ static Rect getSplitBounds(boolean isPrimary) {
+ final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO);
+ return isPrimary
+ ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width,
+ TASK_BOUNDS.bottom)
+ : new Rect(
+ TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right,
+ TASK_BOUNDS.bottom);
+ }
+
+ /** Creates a rule to always split the given activity and the given intent. */
+ static SplitRule createSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Intent secondaryIntent) {
+ final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent);
+ return new SplitPairRule.Builder(
+ activityPair -> false,
+ targetPair::equals,
+ w -> true)
+ .setSplitRatio(SPLIT_RATIO)
+ .setShouldClearTop(true)
+ .build();
+ }
+
+ /** Creates a rule to always split the given activities. */
+ static SplitRule createSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity) {
+ return createSplitRule(primaryActivity, secondaryActivity,
+ DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY,
+ true /* clearTop */);
+ }
+
+ /** Creates a rule to always split the given activities with the given finish behaviors. */
+ static SplitRule createSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity, int finishPrimaryWithSecondary,
+ int finishSecondaryWithPrimary, boolean clearTop) {
+ final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity);
+ return new SplitPairRule.Builder(
+ targetPair::equals,
+ activityIntentPair -> false,
+ w -> true)
+ .setSplitRatio(SPLIT_RATIO)
+ .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
+ .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
+ .setShouldClearTop(clearTop)
+ .build();
+ }
+
+ /** Creates a mock TaskFragmentInfo for the given TaskFragment. */
+ static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+ @NonNull Activity activity) {
+ return new TaskFragmentInfo(container.getTaskFragmentToken(),
+ mock(WindowContainerToken.class),
+ new Configuration(),
+ 1,
+ true /* isVisible */,
+ Collections.singletonList(activity.getActivityToken()),
+ new Point(),
+ false /* isTaskClearedForReuse */,
+ false /* isTaskFragmentClearedForPip */,
+ new Point());
+ }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index a191e685f651..4d2595275f20 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -18,6 +18,8 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -58,8 +60,6 @@ import java.util.ArrayList;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class JetpackTaskFragmentOrganizerTest {
- private static final int TASK_ID = 10;
-
@Mock
private WindowContainerTransaction mTransaction;
@Mock
@@ -131,6 +131,7 @@ public class JetpackTaskFragmentOrganizerTest {
return new TaskFragmentInfo(container.getTaskFragmentToken(),
mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
false /* isVisible */, new ArrayList<>(), new Point(),
- false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */);
+ false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */,
+ new Point());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerState.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/MinimumDimensionActivity.java
index af2ab158ab46..ffcaf3e6f546 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerState.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/MinimumDimensionActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.wm.shell.legacysplitscreen;
+package androidx.window.extensions.embedding;
+
+import android.app.Activity;
/**
- * Class to hold state of divider that needs to persist across configuration changes.
+ * Activity that declares minWidth and minHeight in
+ * {@link android.content.pm.ActivityInfo.WindowLayout}
*/
-final class DividerState {
- public boolean animateAfterRecentsDrawn;
- public float mRatioPositionBeforeMinimized;
-}
+public class MinimumDimensionActivity extends Activity {}
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 60390eb2b3d2..ef7728cec387 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
@@ -19,8 +19,13 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
-import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -49,20 +54,19 @@ import android.app.Activity;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
-import android.util.Pair;
import android.window.TaskFragmentInfo;
-import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -86,16 +90,9 @@ import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SplitControllerTest {
- private static final int TASK_ID = 10;
- private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
- private static final float SPLIT_RATIO = 0.5f;
private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
new ComponentName("test", "placeholder"));
- /** Default finish behavior in Jetpack. */
- private static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER;
- private static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS;
-
private Activity mActivity;
@Mock
private Resources mActivityResources;
@@ -420,6 +417,25 @@ public class SplitControllerTest {
}
@Test
+ public void testResolveStartActivityIntent_shouldLaunchInFullscreen() {
+ final Intent intent = new Intent().setComponent(
+ new ComponentName(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class));
+ setupSplitRule(mActivity, intent);
+ final Activity primaryActivity = createMockActivity();
+ addSplitTaskFragments(primaryActivity, mActivity);
+
+ final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
+ mTransaction, TASK_ID, intent, null /* launchingActivity */);
+ final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
+ mActivity);
+
+ assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container));
+ assertTrue(primaryContainer.areLastRequestedBoundsEqual(null));
+ assertTrue(container.areLastRequestedBoundsEqual(null));
+ }
+
+ @Test
public void testPlaceActivityInTopContainer() {
mSplitController.placeActivityInTopContainer(mActivity);
@@ -767,6 +783,52 @@ public class SplitControllerTest {
}
@Test
+ public void testResolveActivityToContainer_primaryActivityMinDimensionsNotSatisfied() {
+ final Activity activityBelow = createMockActivity();
+ setupSplitRule(mActivity, activityBelow);
+
+ ActivityInfo aInfo = new ActivityInfo();
+ final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
+ aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
+ primaryBounds.width() + 1, primaryBounds.height() + 1);
+ doReturn(aInfo).when(mActivity).getActivityInfo();
+
+ final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
+ TASK_ID);
+ container.addPendingAppearedActivity(mActivity);
+
+ // Allow to split as primary.
+ boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ true /* isOnReparent */);
+
+ assertTrue(result);
+ assertSplitPair(mActivity, activityBelow, true /* matchParentBounds */);
+ }
+
+ @Test
+ public void testResolveActivityToContainer_secondaryActivityMinDimensionsNotSatisfied() {
+ final Activity activityBelow = createMockActivity();
+ setupSplitRule(activityBelow, mActivity);
+
+ ActivityInfo aInfo = new ActivityInfo();
+ final Rect secondaryBounds = getSplitBounds(false /* isPrimary */);
+ aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
+ secondaryBounds.width() + 1, secondaryBounds.height() + 1);
+ doReturn(aInfo).when(mActivity).getActivityInfo();
+
+ final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
+ TASK_ID);
+ container.addPendingAppearedActivity(mActivity);
+
+ // Allow to split as primary.
+ boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ false /* isOnReparent */);
+
+ assertTrue(result);
+ assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */);
+ }
+
+ @Test
public void testResolveActivityToContainer_inUnknownTaskFragment() {
doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity);
@@ -835,23 +897,10 @@ public class SplitControllerTest {
doReturn(activityToken).when(activity).getActivityToken();
doReturn(activity).when(mSplitController).getActivity(activityToken);
doReturn(TASK_ID).when(activity).getTaskId();
+ doReturn(new ActivityInfo()).when(activity).getActivityInfo();
return activity;
}
- /** Creates a mock TaskFragmentInfo for the given TaskFragment. */
- private TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
- @NonNull Activity activity) {
- return new TaskFragmentInfo(container.getTaskFragmentToken(),
- mock(WindowContainerToken.class),
- new Configuration(),
- 1,
- true /* isVisible */,
- Collections.singletonList(activity.getActivityToken()),
- new Point(),
- false /* isTaskClearedForReuse */,
- false /* isTaskFragmentClearedForPip */);
- }
-
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
final TaskFragmentContainer container = mSplitController.newContainer(activity, TASK_ID);
@@ -902,49 +951,10 @@ public class SplitControllerTest {
/** Setups a rule to always split the given activities. */
private void setupSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) {
- final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity,
- DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY,
- true /* clearTop */);
+ final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity);
mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
}
- /** Creates a rule to always split the given activity and the given intent. */
- private SplitRule createSplitRule(@NonNull Activity primaryActivity,
- @NonNull Intent secondaryIntent) {
- final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent);
- return new SplitPairRule.Builder(
- activityPair -> false,
- targetPair::equals,
- w -> true)
- .setSplitRatio(SPLIT_RATIO)
- .setShouldClearTop(true)
- .build();
- }
-
- /** Creates a rule to always split the given activities. */
- private SplitRule createSplitRule(@NonNull Activity primaryActivity,
- @NonNull Activity secondaryActivity) {
- return createSplitRule(primaryActivity, secondaryActivity,
- DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY,
- true /* clearTop */);
- }
-
- /** Creates a rule to always split the given activities with the given finish behaviors. */
- private SplitRule createSplitRule(@NonNull Activity primaryActivity,
- @NonNull Activity secondaryActivity, int finishPrimaryWithSecondary,
- int finishSecondaryWithPrimary, boolean clearTop) {
- final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity);
- return new SplitPairRule.Builder(
- targetPair::equals,
- activityIntentPair -> false,
- w -> true)
- .setSplitRatio(SPLIT_RATIO)
- .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
- .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
- .setShouldClearTop(clearTop)
- .build();
- }
-
/** Adds a pair of TaskFragments as split for the given activities. */
private void addSplitTaskFragments(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) {
@@ -973,39 +983,42 @@ public class SplitControllerTest {
secondaryContainer.setLastRequestedBounds(getSplitBounds(false /* isPrimary */));
}
- /** Gets the bounds of a TaskFragment that is in split. */
- private Rect getSplitBounds(boolean isPrimary) {
- final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO);
- return isPrimary
- ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width,
- TASK_BOUNDS.bottom)
- : new Rect(TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right,
- TASK_BOUNDS.bottom);
+ /** Asserts that the two given activities are in split. */
+ private void assertSplitPair(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity) {
+ assertSplitPair(primaryActivity, secondaryActivity, false /* matchParentBounds */);
}
/** Asserts that the two given activities are in split. */
private void assertSplitPair(@NonNull Activity primaryActivity,
- @NonNull Activity secondaryActivity) {
+ @NonNull Activity secondaryActivity, boolean matchParentBounds) {
assertSplitPair(mSplitController.getContainerWithActivity(primaryActivity),
- mSplitController.getContainerWithActivity(secondaryActivity));
+ mSplitController.getContainerWithActivity(secondaryActivity), matchParentBounds);
}
- /** Asserts that the two given TaskFragments are in split. */
private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer,
@NonNull TaskFragmentContainer secondaryContainer) {
+ assertSplitPair(primaryContainer, secondaryContainer, false /* matchParentBounds*/);
+ }
+
+ /** Asserts that the two given TaskFragments are in split. */
+ private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer,
+ @NonNull TaskFragmentContainer secondaryContainer, boolean matchParentBounds) {
assertNotNull(primaryContainer);
assertNotNull(secondaryContainer);
assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer,
secondaryContainer));
if (primaryContainer.mInfo != null) {
- assertTrue(primaryContainer.areLastRequestedBoundsEqual(
- getSplitBounds(true /* isPrimary */)));
+ final Rect primaryBounds = matchParentBounds ? new Rect()
+ : getSplitBounds(true /* isPrimary */);
+ assertTrue(primaryContainer.areLastRequestedBoundsEqual(primaryBounds));
assertTrue(primaryContainer.isLastRequestedWindowingModeEqual(
WINDOWING_MODE_MULTI_WINDOW));
}
if (secondaryContainer.mInfo != null) {
- assertTrue(secondaryContainer.areLastRequestedBoundsEqual(
- getSplitBounds(false /* isPrimary */)));
+ final Rect secondaryBounds = matchParentBounds ? new Rect()
+ : getSplitBounds(false /* isPrimary */);
+ assertTrue(secondaryContainer.areLastRequestedBoundsEqual(secondaryBounds));
assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual(
WINDOWING_MODE_MULTI_WINDOW));
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 906e9904566f..acc398a27baf 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -18,25 +18,44 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
+import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END;
+import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL;
+import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START;
+import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition;
+import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
+import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
+import android.util.Size;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -56,8 +75,6 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SplitPresenterTest {
- private static final int TASK_ID = 10;
- private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
@Mock
private Activity mActivity;
@@ -77,11 +94,7 @@ public class SplitPresenterTest {
mPresenter = mController.mPresenter;
spyOn(mController);
spyOn(mPresenter);
- final Configuration activityConfig = new Configuration();
- activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
- activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
- doReturn(mActivityResources).when(mActivity).getResources();
- doReturn(activityConfig).when(mActivityResources).getConfiguration();
+ mActivity = createMockActivity();
}
@Test
@@ -129,4 +142,67 @@ public class SplitPresenterTest {
verify(mTransaction, never()).setWindowingMode(any(), anyInt());
}
+
+ @Test
+ public void testGetMinDimensionsForIntent() {
+ final Intent intent = new Intent(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class);
+ assertEquals(new Size(600, 1200), getMinDimensions(intent));
+ }
+
+ @Test
+ public void testShouldShowSideBySide() {
+ Activity secondaryActivity = createMockActivity();
+ final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
+
+ assertTrue(shouldShowSideBySide(TASK_BOUNDS, splitRule));
+
+ // Set minDimensions of primary container to larger than primary bounds.
+ final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
+ Pair<Size, Size> minDimensionsPair = new Pair<>(
+ new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null);
+
+ assertFalse(shouldShowSideBySide(TASK_BOUNDS, splitRule, minDimensionsPair));
+ }
+
+ @Test
+ public void testGetBoundsForPosition() {
+ Activity secondaryActivity = createMockActivity();
+ final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
+ final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
+ final Rect secondaryBounds = getSplitBounds(false /* isPrimary */);
+
+ assertEquals("Primary bounds must be reported.",
+ primaryBounds,
+ getBoundsForPosition(POSITION_START, TASK_BOUNDS, splitRule,
+ mActivity, null /* miniDimensionsPair */));
+
+ assertEquals("Secondary bounds must be reported.",
+ secondaryBounds,
+ getBoundsForPosition(POSITION_END, TASK_BOUNDS, splitRule,
+ mActivity, null /* miniDimensionsPair */));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ getBoundsForPosition(POSITION_FILL, TASK_BOUNDS, splitRule,
+ mActivity, null /* miniDimensionsPair */));
+
+ Pair<Size, Size> minDimensionsPair = new Pair<>(
+ new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null);
+
+ assertEquals("Fullscreen bounds must be reported because of min dimensions.",
+ new Rect(),
+ getBoundsForPosition(POSITION_START, TASK_BOUNDS,
+ splitRule, mActivity, minDimensionsPair));
+ }
+
+ private Activity createMockActivity() {
+ final Activity activity = mock(Activity.class);
+ final Configuration activityConfig = new Configuration();
+ activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
+ activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
+ doReturn(mActivityResources).when(activity).getResources();
+ doReturn(activityConfig).when(mActivityResources).getConfiguration();
+ doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+ return activity;
+ }
}
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 ebe202db4e54..dd67e48ef353 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
@@ -22,6 +22,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -53,9 +56,6 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TaskContainerTest {
- private static final int TASK_ID = 10;
- private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
-
@Mock
private SplitController mController;
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
index af3ad70c04db..d31342bfb309 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
@@ -16,6 +16,8 @@
package androidx.window.extensions.embedding;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -43,8 +45,6 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TaskFragmentAnimationControllerTest {
- private static final int TASK_ID = 10;
-
@Mock
private TaskFragmentOrganizer mOrganizer;
private TaskFragmentAnimationController mAnimationController;
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index fcbd8a3ac020..28c2773e25cb 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -16,6 +16,9 @@
package androidx.window.extensions.embedding;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
@@ -30,17 +33,13 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import android.annotation.NonNull;
import android.app.Activity;
import android.content.Intent;
-import android.content.res.Configuration;
-import android.graphics.Point;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
-import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -55,7 +54,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
@@ -68,8 +66,6 @@ import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TaskFragmentContainerTest {
- private static final int TASK_ID = 10;
-
@Mock
private SplitPresenter mPresenter;
@Mock
@@ -311,18 +307,4 @@ public class TaskFragmentContainerTest {
doReturn(activity).when(mController).getActivity(activityToken);
return activity;
}
-
- /** Creates a mock TaskFragmentInfo for the given TaskFragment. */
- private TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
- @NonNull Activity activity) {
- return new TaskFragmentInfo(container.getTaskFragmentToken(),
- mock(WindowContainerToken.class),
- new Configuration(),
- 1,
- true /* isVisible */,
- Collections.singletonList(activity.getActivityToken()),
- new Point(),
- false /* isTaskClearedForReuse */,
- false /* isTaskFragmentClearedForPip */);
- }
}
diff --git a/libs/WindowManager/Shell/res/color/decor_button_dark_color.xml b/libs/WindowManager/Shell/res/color/decor_button_dark_color.xml
new file mode 100644
index 000000000000..bf325bd84c00
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/decor_button_dark_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item app:state_task_focused="true" android:color="#FF000000" />
+ <item android:color="#33000000" />
+</selector>
diff --git a/libs/WindowManager/Shell/res/color/decor_button_light_color.xml b/libs/WindowManager/Shell/res/color/decor_button_light_color.xml
new file mode 100644
index 000000000000..2e48bca7786a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/decor_button_light_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item app:state_task_focused="true" android:color="#FFFFFFFF" />
+ <item android:color="#33FFFFFF" />
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml b/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml
new file mode 100644
index 000000000000..1ecc13e4da38
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <!-- Fading the to 85% blackness -->
+ <item app:state_task_focused="true" android:color="#D8D8D8" />
+ <!-- Fading the to 95% blackness -->
+ <item android:color="#F2F2F2" />
+</selector>
diff --git a/libs/WindowManager/Shell/res/color/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml
index 329e5b9b31a0..b3d260299106 100644
--- a/libs/WindowManager/Shell/res/color/taskbar_background.xml
+++ b/libs/WindowManager/Shell/res/color/taskbar_background.xml
@@ -14,6 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+<!-- Should be the same as in packages/apps/Launcher3/res/color-v31/taskbar_background.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="35" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="15" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/unfold_transition_background.xml b/libs/WindowManager/Shell/res/color/unfold_transition_background.xml
deleted file mode 100644
index 63289a3f75d9..000000000000
--- a/libs/WindowManager/Shell/res/color/unfold_transition_background.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- Matches taskbar color -->
- <item android:color="@android:color/system_neutral2_500" android:lStar="35" />
-</selector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
new file mode 100644
index 000000000000..8207365a737d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape android:shape="rectangle"
+ android:tintMode="multiply"
+ android:tint="@color/decor_caption_title_color"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="?android:attr/colorPrimary" />
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
new file mode 100644
index 000000000000..f2f1a1d55dee
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+ android:tint="@color/decor_button_dark_color"
+ >
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml
new file mode 100644
index 000000000000..ab4e29ac97e5
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+ android:tint="@color/decor_button_dark_color">
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
+ </group>
+</vector>
+
+
diff --git a/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
index cb516cdbe49b..df5985c605d1 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
@@ -30,7 +30,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
- android:gravity="center"/>
+ android:gravity="center"
+ android:clipChildren="false"/>
<LinearLayout
android:id="@+id/bubble_overflow_empty_state"
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
new file mode 100644
index 000000000000..a112f1933dd1
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/caption"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:background="@drawable/decor_caption_title">
+ <Button
+ android:id="@+id/maximize_window"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_margin="5dp"
+ android:padding="4dp"
+ android:layout_gravity="center_vertical|end"
+ android:contentDescription="@string/maximize_button_text"
+ android:background="@drawable/decor_maximize_button_dark"
+ android:duplicateParentState="true"/>
+ <Button
+ android:id="@+id/close_window"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_margin="5dp"
+ android:padding="4dp"
+ android:layout_gravity="center_vertical|end"
+ android:contentDescription="@string/close_button_text"
+ android:background="@drawable/decor_close_button_dark"
+ android:duplicateParentState="true"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
+
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 2476f65c7e5b..e7bc74a1744f 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Draai jou toestel om dit volskerm te maak"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dubbeltik langs ’n program om dit te herposisioneer"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index f0c391cd6b99..8b2a89238584 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ወደ የሙሉ ገጽ ዕይታ ለመሄድ መሣሪያዎን ያሽከርክሩት"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ቦታውን ለመቀየር ከመተግበሪያው ቀጥሎ ላይ ሁለቴ መታ ያድርጉ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ገባኝ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ለተጨማሪ መረጃ ይዘርጉ።"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index aa4b3b704110..98b25c8ab087 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"قم بتدوير الشاشة للانتقال إلى وضع ملء الشاشة."</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"انقر مرتين بجانب التطبيق لتغيير موضعه."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"حسنًا"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"التوسيع للحصول على مزيد من المعلومات"</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 985d3b9b96fd..2520267f46d2 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"পূৰ্ণ স্ক্ৰীনলৈ যাবলৈ আপোনাৰ ডিভাইচটো ঘূৰাওক"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"এপ্‌টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ কাষত দুবাৰ টিপক"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"বুজি পালোঁ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"অধিক তথ্যৰ বাবে বিস্তাৰ কৰক।"</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 8cd9b7a635ab..51b60774b2fd 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Tam ekrana keçmək üçün cihazınızı fırladın"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tətbiqin yerini dəyişmək üçün yanına iki dəfə toxunun"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ətraflı məlumat üçün genişləndirin."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 49524c608543..b34920e0568f 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotirajte uređaj za prikaz preko celog ekrana"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da biste promenili njenu poziciju"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za još informacija."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 1767e0d66241..525e46ff371e 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Каб перайсці ў поўнаэкранны рэжым, павярніце прыладу"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Двойчы націсніце побач з праграмай, каб перамясціць яе"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Зразумела"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгарнуць для дадатковай інфармацыі"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index c22fb86a4d4d..c44afcd74c01 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Завъртете екрана си, за да преминете в режим на цял екран"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Докоснете два пъти дадено приложение, за да промените позицията му"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Разбрах"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгъване за още информация."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index c0944e0584e6..0a8b0b746231 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"\'ফুল স্ক্রিন\' মোডে যেতে ডিভাইস ঘোরান"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"কোনও অ্যাপের পাশে ডবল ট্যাপ করে সেটির জায়গা পরিবর্তন করুন"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"বুঝেছি"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"আরও তথ্যের জন্য বড় করুন।"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index ae01c641cc43..fbf39255be08 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zarotirajte uređaj da aktivirate prikaz preko cijelog ekrana"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da promijenite njen položaj"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Razumijem"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za više informacija."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 8a522b3e6397..3ea31cebe5ab 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gira el dispositiu per passar a pantalla completa"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Fes doble toc al costat d\'una aplicació per canviar-ne la posició"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Desplega per obtenir més informació."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index d0cf80aef38c..574ed2616490 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Otočením zařízení přejděte do režimu celé obrazovky"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvojitým klepnutím vedle aplikace změňte její umístění"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozbalením zobrazíte další informace."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index bb81c10c6e1b..2fcd37a54bc6 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Drej din enhed for at gå til fuld skærm"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tryk to gange ud for en app for at ændre dens placering"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Udvid for at få flere oplysninger."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index c5d945a982ef..07075b591f73 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gerät drehen, um zum Vollbildmodus zu wechseln"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Neben einer App doppeltippen, um die Position zu ändern"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 70f55058925c..0d9d3eab591b 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Περιστρέψτε τη συσκευή σας για μετάβαση σε πλήρη οθόνη."</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Πατήστε δύο φορές δίπλα σε μια εφαρμογή για να αλλάξετε τη θέση της."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Το κατάλαβα"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ανάπτυξη για περισσότερες πληροφορίες."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 0b5aefa5c72e..48525cd10749 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 0b5aefa5c72e..48525cd10749 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 0b5aefa5c72e..48525cd10749 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 0b5aefa5c72e..48525cd10749 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 5c3d0f65374a..fb6db5f91a25 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‏‎‎‏‏‎Rotate your device to go full screen‎‏‎‎‏‎"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‎‏‏‏‎Double-tap next to an app to reposition it‎‏‎‎‏‎"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎Got it‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎Expand for more information.‎‏‎‎‏‎"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎Maximize‎‏‎‎‏‎"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎Close‎‏‎‎‏‎"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index e523ae53b0cc..3db353a0b4ea 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rota el dispositivo para ver la pantalla completa"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Presiona dos veces junto a una app para cambiar su posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expande para obtener más información."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 39990dc8cb0c..8f7c741b45e2 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gira el dispositivo para ir al modo de pantalla completa"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toca dos veces junto a una aplicación para cambiar su posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mostrar más información"</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index a5f82a6452c4..0d75a5552fe4 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pöörake seadet, et aktiveerida täisekraanirežiim"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Topeltpuudutage rakenduse kõrval, et selle asendit muuta"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 67b9a433dc03..71ccad59e71a 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pantaila osoko modua erabiltzeko, biratu gailua"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren ondoko edozein toki"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ados"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Informazio gehiago lortzeko, zabaldu hau."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Itxi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 761fb9ddeb2f..3e95b484d567 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"برای رفتن به حالت تمام صفحه، دستگاهتان را بچرخانید"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"در کنار برنامه دوضربه بزنید تا جابه‌جا شود"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجه‌ام"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index c809b4879e71..a1e5efbad7b6 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Käännä laitetta, niin se siirtyy koko näytön tilaan"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Kaksoisnapauta sovellusta, jos haluat siirtää sitä"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Katso lisätietoja laajentamalla."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 62b2bb65a603..c3f36e60bddc 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Faites pivoter votre appareil pour passer en plein écran"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Touchez deux fois à côté d\'une application pour la repositionner"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 07475055f03e..1365c22017cf 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Faites pivoter l\'appareil pour passer en plein écran"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Appuyez deux fois à côté d\'une appli pour la repositionner"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index b8e039602243..19430577f181 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Xira o dispositivo para ver o contido en pantalla completa"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toca dúas veces a carón dunha aplicación para cambiala de posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Despregar para obter máis información."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index deda2d755e20..8e9f086daa97 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"પૂર્ણ સ્ક્રીન મોડ લાગુ કરવા માટે, તમારા ડિવાઇસને ફેરવો"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બાજુમાં બે વાર ટૅપ કરો"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"સમજાઈ ગયું"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"વધુ માહિતી માટે મોટું કરો."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index a5fcb97d1418..478bfbaed62e 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"फ़ुल स्क्रीन मोड में जाने के लिए, डिवाइस को घुमाएं"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बगल में दो बार टैप करें"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ठीक है"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ज़्यादा जानकारी के लिए बड़ा करें."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 5ecc5585a6e9..1c4116d6cc8a 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zakrenite uređaj radi prikaza na cijelom zaslonu"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da biste joj promijenili položaj"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Shvaćam"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite da biste saznali više."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 2295250e2853..d749775a2160 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"A teljes képernyős mód elindításához forgassa el az eszközt"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Koppintson duplán az alkalmazás mellett az áthelyezéséhez"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Értem"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kibontással további információkhoz juthat."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 208936539094..4cc183de140a 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Պտտեք սարքը՝ լիաէկրան ռեժիմին անցնելու համար"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Եղավ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ծավալեք՝ ավելին իմանալու համար։"</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 1b46b2fe2570..af8a8ece437d 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Putar perangkat untuk tampilan layar penuh"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ketuk dua kali di samping aplikasi untuk mengubah posisinya"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index a201c95137f3..c22b88138714 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Snúðu tækinu til að nota allan skjáinn"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ýttu tvisvar við hlið forritsins til að færa það"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Stækka til að sjá frekari upplýsingar."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 7157ed088d30..40a0e176ca29 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ruota il dispositivo per passare alla modalità a schermo intero"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tocca due volte accanto a un\'app per riposizionarla"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 52a6b0676222..3a7381164e08 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"מסובבים את המכשיר כדי לעבור לתצוגה במסך מלא"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"מקישים הקשה כפולה ליד אפליקציה כדי למקם אותה מחדש"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"הבנתי"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"מרחיבים כדי לקבל מידע נוסף."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 5a25c24ba034..4d09b202ca06 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"全画面表示にするにはデバイスを回転させてください"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"位置を変えるにはアプリの横をダブルタップしてください"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"開くと詳細が表示されます。"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"閉じる"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index bff86fa6ffe2..8cbc3d893bd8 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"მოატრიალეთ თქვენი მოწყობილობა სრული ეკრანის გასაშლელად"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ორმაგად შეეხეთ აპის გვერდითა სივრცეს, რათა ის სხვაგან გადაიტანოთ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"გასაგებია"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"დამატებითი ინფორმაციისთვის გააფართოეთ."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index f57f3f581c85..a1585a454322 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Толық экранға ауысу үшін құрылғыңызды бұрыңыз."</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Қолданбаның орнын ауыстыру үшін жанынан екі рет түртіңіз."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Түсінікті"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толығырақ ақпарат алу үшін терезені жайыңыз."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 5c04f881fe0e..1e72d4fe145f 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"បង្វិលឧបករណ៍របស់អ្នក ដើម្បីចូលប្រើអេក្រង់ពេញ"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ចុចពីរដងនៅជាប់កម្មវិធីណាមួយ ដើម្បីប្ដូរទីតាំងកម្មវិធីនោះ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"យល់ហើយ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ពង្រីកដើម្បីទទួលបានព័ត៌មានបន្ថែម។"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index e91383caa009..24d0c8257b7b 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ಗೆ ಹೋಗಲು ನಿಮ್ಮ ಸಾಧನವನ್ನು ತಿರುಗಿಸಿ"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಪಕ್ಕದಲ್ಲಿ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ಸರಿ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ವಿಸ್ತೃತಗೊಳಿಸಿ."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 104ba3f22c96..6f516e64ca02 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"전체 화면 모드로 전환하려면 기기를 회전하세요."</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"앱 위치를 조정하려면 앱 옆을 두 번 탭하세요."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"확인"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"추가 정보는 펼쳐서 확인하세요."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 8203622a33fc..94ea9fb9f950 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Толук экран режимине өтүү үчүн түзмөктү буруңуз"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Колдонмонун ракурсун өзгөртүү үчүн анын тушуна эки жолу басыңыз"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Түшүндүм"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толук маалымат алуу үчүн жайып көрүңүз."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 24396786f9d8..a7ad006b0347 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ໝຸນອຸປະກອນຂອງທ່ານເພື່ອໃຊ້ແບບເຕັມຈໍ"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ແຕະສອງເທື່ອໃສ່ຖັດຈາກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ເຂົ້າໃຈແລ້ວ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ຂະຫຍາຍເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index e2ae643ad308..3002492989aa 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pasukite įrenginį, kad įjungtumėte viso ekrano režimą"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dukart palieskite šalia programos, kad pakeistumėte jos poziciją"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Supratau"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Išskleiskite, jei reikia daugiau informacijos."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index a77160bc262a..cce1f5feafb7 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pagrieziet ierīci, lai aktivizētu pilnekrāna režīmu"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Veiciet dubultskārienu blakus lietotnei, lai manītu tās pozīciju"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Labi"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Izvērsiet, lai iegūtu plašāku informāciju."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index bac0c9eee4c2..56e9b406fe8d 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ротирајте го уредот за да отворите на цел екран"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Допрете двапати до некоја апликација за да ја преместите"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Сфатив"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширете за повеќе информации."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Затвори"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index de0f837fcd3f..88e49e78d925 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"പൂർണ്ണ സ്ക്രീനിലേക്ക് മാറാൻ ഈ ഉപകരണം റൊട്ടേറ്റ് ചെയ്യുക"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ഒരു ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ, അതിന് തൊട്ടടുത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"മനസ്സിലായി"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"കൂടുതൽ വിവരങ്ങൾക്ക് വികസിപ്പിക്കുക."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 1205306e0833..7a6f62577e63 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Төхөөрөмжөө бүтэн дэлгэцээр үзэхийн тулд эргүүлнэ"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Аппыг дахин байрлуулахын тулд хажууд нь хоёр товшино"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ойлголоо"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Нэмэлт мэдээлэл авах бол дэлгэнэ үү."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Хаах"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index c91d06fdf3d5..c2a947542f0a 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"फुल स्क्रीन करण्यासाठी, तुमचे डिव्हाइस फिरवा"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या शेजारी दोनदा टॅप करा"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"समजले"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"अधिक माहितीसाठी विस्तार करा."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 652a9919d163..35512c1c75bb 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Putar peranti anda untuk beralih ke skrin penuh"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ketik dua kali bersebelahan apl untuk menempatkan semula apl"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kembangkan untuk mendapatkan maklumat lanjut."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 15d182c6451e..5aea49f59e2f 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ဖန်သားပြင်အပြည့်လုပ်ရန် သင့်စက်ကို လှည့်နိုင်သည်"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"အက်ပ်နေရာပြန်ချရန် ၎င်းဘေးတွင် နှစ်ချက်တို့နိုင်သည်"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ရပြီ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 2f2fea6eb833..ff987ea2b7b7 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Roter enheten for å starte fullskjerm"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dobbelttrykk ved siden av en app for å flytte den"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vis for å få mer informasjon."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 8dfec88cc29d..831f6e2ef829 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"तपाईं फुल स्क्रिन मोड हेर्न चाहनुहुन्छ भने आफ्नो डिभाइस रोटेट गर्नुहोस्"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको छेउमा डबल ट्याप गर्नुहोस्"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"बुझेँ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"थप जानकारी प्राप्त गर्न चाहनुहुन्छ भने एक्स्पान्ड गर्नुहोस्।"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 8468b04c66da..affdd0e2a223 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Draai je apparaat om naar volledig scherm te schakelen"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dubbeltik naast een app om deze opnieuw te positioneren"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Uitvouwen voor meer informatie."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index a8d8448edf99..f150cb9a9160 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ପୂର୍ଣ୍ଣ-ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ରୋଟେଟ କରନ୍ତୁ"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହା ପାଖରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ବୁଝିଗଲି"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ଅଧିକ ସୂଚନା ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ।"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index f99176cb682d..af91b36364f5 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ਪੂਰੀ-ਸਕ੍ਰੀਨ ਮੋਡ \'ਤੇ ਜਾਣ ਲਈ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁਮਾਓ"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਅੱਗੇ ਡਬਲ ਟੈਪ ਕਰੋ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ਸਮਝ ਲਿਆ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਵਿਸਤਾਰ ਕਰੋ।"</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index f2147c04d335..8c6a4d55d269 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Obróć urządzenie, aby przejść do pełnego ekranu"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Kliknij dwukrotnie obok aplikacji, aby ją przenieść"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozwiń, aby wyświetlić więcej informacji."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 2efc5543dd87..f328b6c3a781 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gire o dispositivo para entrar no modo de tela cheia"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes ao lado de um app para reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index c68a6934dead..2fe448bcaf28 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rode o dispositivo para ficar em ecrã inteiro"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes junto a uma app para a reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expandir para obter mais informações"</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 2efc5543dd87..f328b6c3a781 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gire o dispositivo para entrar no modo de tela cheia"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes ao lado de um app para reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 804d34f980ff..8da1d67f9841 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotiți dispozitivul pentru a trece în modul ecran complet"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Atingeți de două ori lângă o aplicație pentru a o repoziționa"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Extindeți pentru mai multe informații"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizați"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Închideți"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 95bf1cf11435..49efac67ceb0 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Чтобы перейти в полноэкранный режим, поверните устройство."</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Чтобы переместить приложение, нажмите на него дважды."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ОК"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Развернуть, чтобы узнать больше."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 23dd65ad7b31..b8c9b54904a2 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"සම්පූර්ණ තිරයට යාමට ඔබගේ උපාංගය කරකවන්න"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"එය නැවත ස්ථානගත කිරීමට යෙදුමකට යාබදව දෙවරක් තට්ටු කරන්න"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"තේරුණා"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"වැඩිදුර තොරතුරු සඳහා දිග හරින්න"</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index a231cacefb20..ca14dd78aaa5 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Otočením zariadenia prejdete do režimu celej obrazovky"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvojitým klepnutím vedľa aplikácie zmeníte jej pozíciu"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Dobre"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Po rozbalení sa dozviete viac."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index adeaae978eaa..626be8beb04d 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Če želite preklopiti v celozaslonski način, zasukajte napravo."</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvakrat se dotaknite ob aplikaciji, če jo želite prestaviti."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Razširitev za več informacij"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 2839b4bae7e4..a9b9a58fcae3 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rrotullo ekranin për të kaluar në ekran të plotë"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Trokit dy herë pranë një aplikacioni për ta ripozicionuar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje për më shumë informacion."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 9db6b7c63610..fdd49b5542c7 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ротирајте уређај за приказ преко целог екрана"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Двапут додирните поред апликације да бисте променили њену позицију"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Важи"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширите за још информација."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Затворите"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index f6bd55423cdc..d58b1e3cafef 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotera skärmen för att gå över till helskärmsläge"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tryck snabbt två gånger bredvid en app för att flytta den"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Utöka för mer information."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index f6e558527ee5..331004aaf109 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zungusha kifaa chako ili uende kwenye hali ya skrini nzima"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Gusa mara mbili karibu na programu ili uihamishe"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Panua ili upate maelezo zaidi."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index d8334adfe5ef..b6269946ab32 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"முழுத்திரைக்குச் செல்ல உங்கள் சாதனத்தைச் சுழற்றவும்"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ஆப்ஸை இடம் மாற்ற, ஆப்ஸுக்கு அடுத்து இருமுறை தட்டவும்"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"சரி"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"கூடுதல் தகவல்களுக்கு விரிவாக்கலாம்."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 733075550007..9f1a97347d78 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ఫుల్ స్క్రీన్‌కు వెళ్లడానికి మీ పరికరాన్ని తిప్పండి"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"యాప్ స్థానాన్ని మార్చడానికి దాని పక్కన డబుల్-ట్యాప్ చేయండి"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"అర్థమైంది"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"మరింత సమాచారం కోసం విస్తరించండి."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index cfee8ea3242e..0d1f23b1d350 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"หมุนอุปกรณ์ให้แสดงเต็มหน้าจอ"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"แตะสองครั้งข้างแอปเพื่อเปลี่ยนตำแหน่ง"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"รับทราบ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ขยายเพื่อดูข้อมูลเพิ่มเติม"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ปิด"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index eed624dd5069..0a7fc9617e8f 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"I-rotate ang iyong device para mag-full screen"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Mag-double tap sa tabi ng isang app para iposisyon ito ulit"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"I-expand para sa higit pang impormasyon."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 2b4a2d0550f0..b437392ca796 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Tam ekrana geçmek için cihazınızı döndürün"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Yeniden konumlandırmak için uygulamanın yanına iki kez dokunun"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Daha fazla bilgi için genişletin."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index c3411a837c78..5a7395c237bf 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Щоб перейти в повноекранний режим, поверніть пристрій"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Щоб перемістити додаток, двічі торкніться області поруч із ним"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Розгорніть, щоб дізнатися більше."</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index a31c2be25643..e6dc030e5fb2 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"پوری اسکرین پر جانے کیلئے اپنا آلہ گھمائیں"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس کے آگے دو بار تھپتھپائیں"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"سمجھ آ گئی"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"مزید معلومات کے لیے پھیلائیں۔"</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 2e3222560dde..0c0771f870dd 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Butun ekranda ochish uchun qurilmani buring"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Qayta joylash uchun keyingi ilova ustiga ikki marta bosing"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Batafsil axborot olish uchun kengaytiring."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Yopish"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 8f3cffecc952..8b692a71dd27 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Xoay thiết bị để chuyển sang chế độ toàn màn hình"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Nhấn đúp vào bên cạnh ứng dụng để đặt lại vị trí"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mở rộng để xem thêm thông tin."</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 19a9d371e435..15bb2939406b 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -81,4 +81,9 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋转设备即可进入全屏模式"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在某个应用旁边连续点按两次,即可调整它的位置"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展开即可了解详情。"</string>
+ <!-- no translation found for maximize_button_text (1650859196290301963) -->
+ <skip />
+ <!-- no translation found for close_button_text (2913281996024033299) -->
+ <skip />
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 0c40e963f2e4..add329188515 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋轉裝置方向即可進入全螢幕模式"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在應用程式旁輕按兩下即可調整位置"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳情。"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 8691352cf94a..f2c2a2970d8b 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋轉裝置方向即可進入全螢幕模式"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在應用程式旁輕觸兩下即可調整位置"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"我知道了"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳細資訊。"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 44ffbc6afa45..94e5b9bf4529 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -81,4 +81,7 @@
<string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zungezisa idivayisi yakho ukuze uye esikrinini esigcwele"</string>
<string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Thepha kabili eduze kwe-app ukuze uyimise kabusha"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ngiyezwa"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Nweba ukuze uthole ulwazi olwengeziwe"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Vala"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/attrs.xml b/libs/WindowManager/Shell/res/values/attrs.xml
index 4aaeef8afcb0..2aad4c1c1805 100644
--- a/libs/WindowManager/Shell/res/values/attrs.xml
+++ b/libs/WindowManager/Shell/res/values/attrs.xml
@@ -19,4 +19,8 @@
<attr name="icon" format="reference" />
<attr name="text" format="string" />
</declare-styleable>
+
+ <declare-styleable name="MessageState">
+ <attr name="state_task_focused" format="boolean"/>
+ </declare-styleable>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index a24311fb1f21..b2f09895d7d8 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -186,4 +186,12 @@
<!-- Button text for dismissing the letterbox education dialog. [CHAR LIMIT=20] -->
<string name="letterbox_education_got_it">Got it</string>
+ <!-- Accessibility description of the letterbox education toast expand to dialog button. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_expand_button_description">Expand for more information.</string>
+
+ <!-- Freeform window caption strings -->
+ <!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
+ <string name="maximize_button_text">Maximize</string>
+ <!-- Accessibility text for the close window button [CHAR LIMIT=NONE] -->
+ <string name="close_button_text">Close</string>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 06f4367752fb..c5f7c19518a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -18,11 +18,9 @@ package com.android.wm.shell;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasksController;
@@ -39,12 +37,10 @@ import java.util.Optional;
public final class ShellCommandHandlerImpl {
private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
- private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
private final Optional<Pip> mPipOptional;
private final Optional<OneHandedController> mOneHandedOptional;
private final Optional<HideDisplayCutoutController> mHideDisplayCutout;
- private final Optional<AppPairsController> mAppPairsOptional;
private final Optional<RecentTasksController> mRecentTasks;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
@@ -54,23 +50,19 @@ public final class ShellCommandHandlerImpl {
public ShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
Optional<OneHandedController> oneHandedOptional,
Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<AppPairsController> appPairsOptional,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
mShellTaskOrganizer = shellTaskOrganizer;
mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
mRecentTasks = recentTasks;
- mLegacySplitScreenOptional = legacySplitScreenOptional;
mSplitScreenOptional = splitScreenOptional;
mPipOptional = pipOptional;
mOneHandedOptional = oneHandedOptional;
mHideDisplayCutout = hideDisplayCutout;
- mAppPairsOptional = appPairsOptional;
mMainExecutor = mainExecutor;
}
@@ -84,14 +76,10 @@ public final class ShellCommandHandlerImpl {
pw.println();
pw.println();
mPipOptional.ifPresent(pip -> pip.dump(pw));
- mLegacySplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw));
mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
pw.println();
pw.println();
- mAppPairsOptional.ifPresent(appPairs -> appPairs.dump(pw, ""));
- pw.println();
- pw.println();
mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw, ""));
pw.println();
pw.println();
@@ -109,10 +97,6 @@ public final class ShellCommandHandlerImpl {
return false;
}
switch (args[1]) {
- case "pair":
- return runPair(args, pw);
- case "unpair":
- return runUnpair(args, pw);
case "moveToSideStage":
return runMoveToSideStage(args, pw);
case "removeFromSideStage":
@@ -126,29 +110,6 @@ public final class ShellCommandHandlerImpl {
}
}
- private boolean runPair(String[] args, PrintWriter pw) {
- if (args.length < 4) {
- // First two arguments are "WMShell" and command name.
- pw.println("Error: two task ids should be provided as arguments");
- return false;
- }
- final int taskId1 = new Integer(args[2]);
- final int taskId2 = new Integer(args[3]);
- mAppPairsOptional.ifPresent(appPairs -> appPairs.pair(taskId1, taskId2));
- return true;
- }
-
- private boolean runUnpair(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First two arguments are "WMShell" and command name.
- pw.println("Error: task id should be provided as an argument");
- return false;
- }
- final int taskId = new Integer(args[2]);
- mAppPairsOptional.ifPresent(appPairs -> appPairs.unpair(taskId));
- return true;
- }
-
private boolean runMoveToSideStage(String[] args, PrintWriter pw) {
if (args.length < 3) {
// First arguments are "WMShell" and command name.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 62fb840d29d1..992f31562145 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -18,7 +18,6 @@ package com.android.wm.shell;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
-import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
@@ -28,13 +27,14 @@ import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import java.util.Optional;
@@ -53,12 +53,11 @@ public class ShellInitImpl {
private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
private final Optional<BubbleController> mBubblesOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
- private final Optional<AppPairsController> mAppPairsOptional;
private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
private final FullscreenTaskListener mFullscreenTaskListener;
- private final Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
+ private final Optional<UnfoldAnimationController> mUnfoldController;
private final Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler;
- private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional;
+ private final Optional<FreeformTaskListener<?>> mFreeformTaskListenerOptional;
private final ShellExecutor mMainExecutor;
private final Transitions mTransitions;
private final StartingWindowController mStartingWindow;
@@ -75,12 +74,11 @@ public class ShellInitImpl {
KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairsController> appPairsOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
- Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
- Optional<FreeformTaskListener> freeformTaskListenerOptional,
+ Optional<FreeformTaskListener<?>> freeformTaskListenerOptional,
Optional<RecentTasksController> recentTasks,
Transitions transitions,
StartingWindowController startingWindow,
@@ -93,10 +91,9 @@ public class ShellInitImpl {
mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
mBubblesOptional = bubblesOptional;
mSplitScreenOptional = splitScreenOptional;
- mAppPairsOptional = appPairsOptional;
mFullscreenTaskListener = fullscreenTaskListener;
mPipTouchHandlerOptional = pipTouchHandlerOptional;
- mFullscreenUnfoldController = fullscreenUnfoldTransitionController;
+ mUnfoldController = unfoldAnimationController;
mUnfoldTransitionHandler = unfoldTransitionHandler;
mFreeformTaskListenerOptional = freeformTaskListenerOptional;
mRecentTasks = recentTasks;
@@ -121,7 +118,6 @@ public class ShellInitImpl {
mShellTaskOrganizer.initStartingWindow(mStartingWindow);
mShellTaskOrganizer.registerOrganizer();
- mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered);
mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
mBubblesOptional.ifPresent(BubbleController::initialize);
@@ -131,6 +127,13 @@ public class ShellInitImpl {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitions.register(mShellTaskOrganizer);
mUnfoldTransitionHandler.ifPresent(UnfoldTransitionHandler::init);
+ if (mSplitScreenOptional.isPresent() && mPipTouchHandlerOptional.isPresent()) {
+ final DefaultMixedHandler mixedHandler = new DefaultMixedHandler(mTransitions,
+ mPipTouchHandlerOptional.get().getTransitionHandler(),
+ mSplitScreenOptional.get().getTransitionHandler());
+ // Added at end so that it has highest priority.
+ mTransitions.addHandler(mixedHandler);
+ }
}
// TODO(b/181599115): This should really be the pip controller, but until we can provide the
@@ -143,7 +146,7 @@ public class ShellInitImpl {
mShellTaskOrganizer.addListenerForType(
f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM));
- mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init);
+ mUnfoldController.ifPresent(UnfoldAnimationController::init);
mRecentTasks.ifPresent(RecentTasksController::init);
// Initialize kids mode task organizer
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 31f0ef0192ae..e9d24fbf4d0a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -55,6 +55,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -179,33 +180,41 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
private final Optional<RecentTasksController> mRecentTasks;
@Nullable
+ private final UnfoldAnimationController mUnfoldAnimationController;
+
+ @Nullable
private RunningTaskInfo mLastFocusedTaskInfo;
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
this(null /* taskOrganizerController */, mainExecutor, context, null /* compatUI */,
+ Optional.empty() /* unfoldAnimationController */,
Optional.empty() /* recentTasksController */);
}
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
CompatUIController compatUI) {
this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
+ Optional.empty() /* unfoldAnimationController */,
Optional.empty() /* recentTasksController */);
}
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
CompatUIController compatUI,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks) {
this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
- recentTasks);
+ unfoldAnimationController, recentTasks);
}
@VisibleForTesting
protected ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks) {
super(taskOrganizerController, mainExecutor);
mCompatUI = compatUI;
mRecentTasks = recentTasks;
+ mUnfoldAnimationController = unfoldAnimationController.orElse(null);
if (compatUI != null) {
compatUI.setCompatUICallback(this);
}
@@ -437,6 +446,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
if (listener != null) {
listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
}
+ if (mUnfoldAnimationController != null) {
+ mUnfoldAnimationController.onTaskAppeared(info.getTaskInfo(), info.getLeash());
+ }
notifyLocusVisibilityIfNeeded(info.getTaskInfo());
notifyCompatUI(info.getTaskInfo(), listener);
}
@@ -458,6 +470,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
synchronized (mLock) {
ProtoLog.v(WM_SHELL_TASK_ORG, "Task info changed taskId=%d", taskInfo.taskId);
+
+ if (mUnfoldAnimationController != null) {
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+ }
+
final TaskAppearedInfo data = mTasks.get(taskInfo.taskId);
final TaskListener oldListener = getTaskListener(data.getTaskInfo());
final TaskListener newListener = getTaskListener(taskInfo);
@@ -507,6 +524,10 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
public void onTaskVanished(RunningTaskInfo taskInfo) {
synchronized (mLock) {
ProtoLog.v(WM_SHELL_TASK_ORG, "Task vanished taskId=%d", taskInfo.taskId);
+ if (mUnfoldAnimationController != null) {
+ mUnfoldAnimationController.onTaskVanished(taskInfo);
+ }
+
final int taskId = taskInfo.taskId;
final TaskListener listener = getTaskListener(mTasks.get(taskId).getTaskInfo());
mTasks.remove(taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index 2aead9392e59..a0dde6ad168d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -53,6 +53,19 @@ public class Interpolators {
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
/**
+ * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
+ * is disappearing e.g. when moving off screen.
+ */
+ public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
+ 0.3f, 0f, 0.8f, 0.15f);
+
+ /**
+ * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
+ * is appearing e.g. when coming from off screen
+ */
+ public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+ 0.05f, 0.7f, 0.1f, 1f);
+ /**
* Interpolator to be used when animating a move based on a click. Pair with enough duration.
*/
public static final Interpolator TOUCH_RESPONSE = new PathInterpolator(0.3f, 0f, 0.1f, 1f);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
deleted file mode 100644
index 3f0b01bef0ce..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ /dev/null
@@ -1,357 +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.apppairs;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import static 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.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
-
-import android.app.ActivityManager;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.SurfaceUtils;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.split.SplitLayout;
-import com.android.wm.shell.common.split.SplitWindowManager;
-
-import java.io.PrintWriter;
-
-/**
- * An app-pairs consisting of {@link #mRootTaskInfo} that acts as the hierarchy parent of
- * {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair.
- * Also includes all UI for managing the pair like the divider.
- */
-class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayoutHandler {
- private static final String TAG = AppPair.class.getSimpleName();
-
- private ActivityManager.RunningTaskInfo mRootTaskInfo;
- private SurfaceControl mRootTaskLeash;
- private ActivityManager.RunningTaskInfo mTaskInfo1;
- private SurfaceControl mTaskLeash1;
- private ActivityManager.RunningTaskInfo mTaskInfo2;
- private SurfaceControl mTaskLeash2;
- private SurfaceControl mDimLayer1;
- private SurfaceControl mDimLayer2;
- private final SurfaceSession mSurfaceSession = new SurfaceSession();
-
- private final AppPairsController mController;
- private final SyncTransactionQueue mSyncQueue;
- private final DisplayController mDisplayController;
- private final DisplayImeController mDisplayImeController;
- private final DisplayInsetsController mDisplayInsetsController;
- private SplitLayout mSplitLayout;
-
- private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
- new SplitWindowManager.ParentContainerCallbacks() {
- @Override
- public void attachToParentSurface(SurfaceControl.Builder b) {
- b.setParent(mRootTaskLeash);
- }
-
- @Override
- public void onLeashReady(SurfaceControl leash) {
- mSyncQueue.runInSync(t -> t
- .show(leash)
- .setLayer(leash, Integer.MAX_VALUE)
- .setPosition(leash,
- mSplitLayout.getDividerBounds().left,
- mSplitLayout.getDividerBounds().top));
- }
- };
-
- AppPair(AppPairsController controller) {
- mController = controller;
- mSyncQueue = controller.getSyncTransactionQueue();
- mDisplayController = controller.getDisplayController();
- mDisplayImeController = controller.getDisplayImeController();
- mDisplayInsetsController = controller.getDisplayInsetsController();
- }
-
- int getRootTaskId() {
- return mRootTaskInfo != null ? mRootTaskInfo.taskId : INVALID_TASK_ID;
- }
-
- private int getTaskId1() {
- return mTaskInfo1 != null ? mTaskInfo1.taskId : INVALID_TASK_ID;
- }
-
- private int getTaskId2() {
- return mTaskInfo2 != null ? mTaskInfo2.taskId : INVALID_TASK_ID;
- }
-
- boolean contains(int taskId) {
- return taskId == getRootTaskId() || taskId == getTaskId1() || taskId == getTaskId2();
- }
-
- boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
- ProtoLog.v(WM_SHELL_TASK_ORG, "pair task1=%d task2=%d in AppPair=%s",
- task1.taskId, task2.taskId, this);
-
- if (!task1.supportsMultiWindow || !task2.supportsMultiWindow) {
- ProtoLog.e(WM_SHELL_TASK_ORG,
- "Can't pair tasks that doesn't support multi window, "
- + "task1.supportsMultiWindow=%b, task2.supportsMultiWindow=%b",
- task1.supportsMultiWindow, task2.supportsMultiWindow);
- return false;
- }
-
- mTaskInfo1 = task1;
- mTaskInfo2 = task2;
- mSplitLayout = new SplitLayout(TAG + "SplitDivider",
- mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
- mRootTaskInfo.configuration, this /* layoutChangeListener */,
- mParentContainerCallbacks, mDisplayImeController, mController.getTaskOrganizer(),
- SplitLayout.PARALLAX_DISMISSING);
- mDisplayInsetsController.addInsetsChangedListener(mRootTaskInfo.displayId, mSplitLayout);
-
- final WindowContainerToken token1 = task1.token;
- final WindowContainerToken token2 = task2.token;
- final WindowContainerTransaction wct = new WindowContainerTransaction();
-
- wct.setHidden(mRootTaskInfo.token, false)
- .reparent(token1, mRootTaskInfo.token, true /* onTop */)
- .reparent(token2, mRootTaskInfo.token, true /* onTop */)
- .setWindowingMode(token1, WINDOWING_MODE_MULTI_WINDOW)
- .setWindowingMode(token2, WINDOWING_MODE_MULTI_WINDOW)
- .setBounds(token1, mSplitLayout.getBounds1())
- .setBounds(token2, mSplitLayout.getBounds2())
- // Moving the root task to top after the child tasks were repareted , or the root
- // task cannot be visible and focused.
- .reorder(mRootTaskInfo.token, true);
- mController.getTaskOrganizer().applyTransaction(wct);
- return true;
- }
-
- void unpair() {
- unpair(null /* toTopToken */);
- }
-
- private void unpair(@Nullable WindowContainerToken toTopToken) {
- final WindowContainerToken token1 = mTaskInfo1.token;
- final WindowContainerToken token2 = mTaskInfo2.token;
- final WindowContainerTransaction wct = new WindowContainerTransaction();
-
- // Reparent out of this container and reset windowing mode.
- wct.setHidden(mRootTaskInfo.token, true)
- .reorder(mRootTaskInfo.token, false)
- .reparent(token1, null, token1 == toTopToken /* onTop */)
- .reparent(token2, null, token2 == toTopToken /* onTop */)
- .setWindowingMode(token1, WINDOWING_MODE_UNDEFINED)
- .setWindowingMode(token2, WINDOWING_MODE_UNDEFINED);
- mController.getTaskOrganizer().applyTransaction(wct);
-
- mTaskInfo1 = null;
- mTaskInfo2 = null;
- mSplitLayout.release();
- mSplitLayout = null;
- }
-
- @Override
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- if (mRootTaskInfo == null || taskInfo.taskId == mRootTaskInfo.taskId) {
- mRootTaskInfo = taskInfo;
- mRootTaskLeash = leash;
- } else if (taskInfo.taskId == getTaskId1()) {
- mTaskInfo1 = taskInfo;
- mTaskLeash1 = leash;
- mSyncQueue.runInSync(t -> mDimLayer1 =
- SurfaceUtils.makeDimLayer(t, mTaskLeash1, "Dim layer", mSurfaceSession));
- } else if (taskInfo.taskId == getTaskId2()) {
- mTaskInfo2 = taskInfo;
- mTaskLeash2 = leash;
- mSyncQueue.runInSync(t -> mDimLayer2 =
- SurfaceUtils.makeDimLayer(t, mTaskLeash2, "Dim layer", mSurfaceSession));
- } else {
- throw new IllegalStateException("Unknown task=" + taskInfo.taskId);
- }
-
- if (mTaskLeash1 == null || mTaskLeash2 == null) return;
-
- mSplitLayout.init();
-
- mSyncQueue.runInSync(t -> t
- .show(mRootTaskLeash)
- .show(mTaskLeash1)
- .show(mTaskLeash2)
- .setPosition(mTaskLeash1,
- mTaskInfo1.positionInParent.x,
- mTaskInfo1.positionInParent.y)
- .setPosition(mTaskLeash2,
- mTaskInfo2.positionInParent.x,
- mTaskInfo2.positionInParent.y));
- }
-
- @Override
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- if (!taskInfo.supportsMultiWindow) {
- // Dismiss AppPair if the task no longer supports multi window.
- mController.unpair(mRootTaskInfo.taskId);
- return;
- }
- if (taskInfo.taskId == getRootTaskId()) {
- if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
- mSyncQueue.runInSync(t -> {
- if (taskInfo.isVisible) {
- t.show(mRootTaskLeash);
- } else {
- t.hide(mRootTaskLeash);
- }
- });
- }
- mRootTaskInfo = taskInfo;
-
- if (mSplitLayout != null
- && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
- onLayoutSizeChanged(mSplitLayout);
- }
- } else if (taskInfo.taskId == getTaskId1()) {
- mTaskInfo1 = taskInfo;
- } else if (taskInfo.taskId == getTaskId2()) {
- mTaskInfo2 = taskInfo;
- } else {
- throw new IllegalStateException("Unknown task=" + taskInfo.taskId);
- }
- }
-
- @Override
- public int getSplitItemPosition(WindowContainerToken token) {
- if (token == null) {
- return SPLIT_POSITION_UNDEFINED;
- }
-
- if (token.equals(mTaskInfo1.getToken())) {
- return SPLIT_POSITION_TOP_OR_LEFT;
- } else if (token.equals(mTaskInfo2.getToken())) {
- return SPLIT_POSITION_BOTTOM_OR_RIGHT;
- }
-
- return SPLIT_POSITION_UNDEFINED;
- }
-
- @Override
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- if (taskInfo.taskId == getRootTaskId()) {
- // We don't want to release this object back to the pool since the root task went away.
- mController.unpair(mRootTaskInfo.taskId, false /* releaseToPool */);
- } else if (taskInfo.taskId == getTaskId1()) {
- mController.unpair(mRootTaskInfo.taskId);
- mSyncQueue.runInSync(t -> t.remove(mDimLayer1));
- } else if (taskInfo.taskId == getTaskId2()) {
- mController.unpair(mRootTaskInfo.taskId);
- mSyncQueue.runInSync(t -> t.remove(mDimLayer2));
- }
- }
-
- @Override
- public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
- b.setParent(findTaskSurface(taskId));
- }
-
- @Override
- public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
- SurfaceControl.Transaction t) {
- t.reparent(sc, findTaskSurface(taskId));
- }
-
- private SurfaceControl findTaskSurface(int taskId) {
- if (getRootTaskId() == taskId) {
- return mRootTaskLeash;
- } else if (getTaskId1() == taskId) {
- return mTaskLeash1;
- } else if (getTaskId2() == taskId) {
- return mTaskLeash2;
- } else {
- throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
- }
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
- if (mRootTaskInfo != null) {
- pw.println(innerPrefix + "Root taskId=" + mRootTaskInfo.taskId
- + " winMode=" + mRootTaskInfo.getWindowingMode());
- }
- if (mTaskInfo1 != null) {
- pw.println(innerPrefix + "1 taskId=" + mTaskInfo1.taskId
- + " winMode=" + mTaskInfo1.getWindowingMode());
- }
- if (mTaskInfo2 != null) {
- pw.println(innerPrefix + "2 taskId=" + mTaskInfo2.taskId
- + " winMode=" + mTaskInfo2.getWindowingMode());
- }
- }
-
- @Override
- public String toString() {
- return TAG + "#" + getRootTaskId();
- }
-
- @Override
- public void onSnappedToDismiss(boolean snappedToEnd) {
- unpair(snappedToEnd ? mTaskInfo1.token : mTaskInfo2.token /* toTopToken */);
- }
-
- @Override
- public void onLayoutPositionChanging(SplitLayout layout) {
- mSyncQueue.runInSync(t ->
- layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2,
- true /* applyResizingOffset */));
- }
-
- @Override
- public void onLayoutSizeChanging(SplitLayout layout) {
- mSyncQueue.runInSync(t ->
- layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2,
- true /* applyResizingOffset */));
- }
-
- @Override
- public void onLayoutSizeChanged(SplitLayout layout) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- layout.applyTaskChanges(wct, mTaskInfo1, mTaskInfo2);
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t ->
- layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2,
- false /* applyResizingOffset */));
- }
-
- @Override
- public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, mTaskInfo1, mTaskInfo2);
- mController.getTaskOrganizer().applyTransaction(wct);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
deleted file mode 100644
index a9b1dbc3c23b..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
+++ /dev/null
@@ -1,38 +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.apppairs;
-
-import android.app.ActivityManager;
-
-import androidx.annotation.NonNull;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-import java.io.PrintWriter;
-
-/**
- * Interface to engage app pairs feature.
- */
-@ExternalThread
-public interface AppPairs {
- /** Pairs indicated tasks. */
- boolean pair(int task1, int task2);
- /** Pairs indicated tasks. */
- boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2);
- /** Unpairs any app-pair containing this task id. */
- void unpair(int taskId);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
deleted file mode 100644
index 53234ab971d6..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
+++ /dev/null
@@ -1,213 +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.apppairs;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
-
-import android.app.ActivityManager;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import java.io.PrintWriter;
-
-/**
- * Class manages app-pairs multitasking mode and implements the main interface {@link AppPairs}.
- */
-public class AppPairsController {
- private static final String TAG = AppPairsController.class.getSimpleName();
-
- private final ShellTaskOrganizer mTaskOrganizer;
- private final SyncTransactionQueue mSyncQueue;
- private final ShellExecutor mMainExecutor;
- private final AppPairsImpl mImpl = new AppPairsImpl();
-
- private AppPairsPool mPairsPool;
- // Active app-pairs mapped by root task id key.
- private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
- private final DisplayController mDisplayController;
- private final DisplayImeController mDisplayImeController;
- private final DisplayInsetsController mDisplayInsetsController;
-
- public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
- DisplayController displayController, ShellExecutor mainExecutor,
- DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController) {
- mTaskOrganizer = organizer;
- mSyncQueue = syncQueue;
- mDisplayController = displayController;
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mMainExecutor = mainExecutor;
- }
-
- public AppPairs asAppPairs() {
- return mImpl;
- }
-
- public void onOrganizerRegistered() {
- if (mPairsPool == null) {
- setPairsPool(new AppPairsPool(this));
- }
- }
-
- @VisibleForTesting
- public void setPairsPool(AppPairsPool pool) {
- mPairsPool = pool;
- }
-
- public boolean pair(int taskId1, int taskId2) {
- final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
- final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
- if (task1 == null || task2 == null) {
- return false;
- }
- return pair(task1, task2);
- }
-
- public boolean pair(ActivityManager.RunningTaskInfo task1,
- ActivityManager.RunningTaskInfo task2) {
- return pairInner(task1, task2) != null;
- }
-
- @VisibleForTesting
- public AppPair pairInner(
- @NonNull ActivityManager.RunningTaskInfo task1,
- @NonNull ActivityManager.RunningTaskInfo task2) {
- final AppPair pair = mPairsPool.acquire();
- if (!pair.pair(task1, task2)) {
- mPairsPool.release(pair);
- return null;
- }
-
- mActiveAppPairs.put(pair.getRootTaskId(), pair);
- return pair;
- }
-
- public void unpair(int taskId) {
- unpair(taskId, true /* releaseToPool */);
- }
-
- public void unpair(int taskId, boolean releaseToPool) {
- AppPair pair = mActiveAppPairs.get(taskId);
- if (pair == null) {
- for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
- final AppPair candidate = mActiveAppPairs.valueAt(i);
- if (candidate.contains(taskId)) {
- pair = candidate;
- break;
- }
- }
- }
- if (pair == null) {
- ProtoLog.v(WM_SHELL_TASK_ORG, "taskId %d isn't isn't in an app-pair.", taskId);
- return;
- }
-
- ProtoLog.v(WM_SHELL_TASK_ORG, "unpair taskId=%d pair=%s", taskId, pair);
- mActiveAppPairs.remove(pair.getRootTaskId());
- pair.unpair();
- if (releaseToPool) {
- mPairsPool.release(pair);
- }
- }
-
- ShellTaskOrganizer getTaskOrganizer() {
- return mTaskOrganizer;
- }
-
- SyncTransactionQueue getSyncTransactionQueue() {
- return mSyncQueue;
- }
-
- DisplayController getDisplayController() {
- return mDisplayController;
- }
-
- DisplayImeController getDisplayImeController() {
- return mDisplayImeController;
- }
-
- DisplayInsetsController getDisplayInsetsController() {
- return mDisplayInsetsController;
- }
-
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
-
- for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
- mActiveAppPairs.valueAt(i).dump(pw, childPrefix);
- }
-
- if (mPairsPool != null) {
- mPairsPool.dump(pw, prefix);
- }
- }
-
- @Override
- public String toString() {
- return TAG + "#" + mActiveAppPairs.size();
- }
-
- private class AppPairsImpl implements AppPairs {
- @Override
- public boolean pair(int task1, int task2) {
- boolean[] result = new boolean[1];
- try {
- mMainExecutor.executeBlocking(() -> {
- result[0] = AppPairsController.this.pair(task1, task2);
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to pair tasks: " + task1 + ", " + task2);
- }
- return result[0];
- }
-
- @Override
- public boolean pair(ActivityManager.RunningTaskInfo task1,
- ActivityManager.RunningTaskInfo task2) {
- boolean[] result = new boolean[1];
- try {
- mMainExecutor.executeBlocking(() -> {
- result[0] = AppPairsController.this.pair(task1, task2);
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to pair tasks: " + task1 + ", " + task2);
- }
- return result[0];
- }
-
- @Override
- public void unpair(int taskId) {
- mMainExecutor.execute(() -> {
- AppPairsController.this.unpair(taskId);
- });
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsPool.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsPool.java
deleted file mode 100644
index 5c6037ea0702..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsPool.java
+++ /dev/null
@@ -1,93 +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.apppairs;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * Class that manager pool of {@link AppPair} objects. Helps reduce the need to call system_server
- * to create a root task for the app-pair when needed since we always have one ready to go.
- */
-class AppPairsPool {
- private static final String TAG = AppPairsPool.class.getSimpleName();
-
- @VisibleForTesting
- final AppPairsController mController;
- // The pool
- private final ArrayList<AppPair> mPool = new ArrayList();
-
- AppPairsPool(AppPairsController controller) {
- mController = controller;
- incrementPool();
- }
-
- AppPair acquire() {
- final AppPair entry = mPool.remove(mPool.size() - 1);
- ProtoLog.v(WM_SHELL_TASK_ORG, "acquire entry.taskId=%s listener=%s size=%d",
- entry.getRootTaskId(), entry, mPool.size());
- if (mPool.size() == 0) {
- incrementPool();
- }
- return entry;
- }
-
- void release(AppPair entry) {
- mPool.add(entry);
- ProtoLog.v(WM_SHELL_TASK_ORG, "release entry.taskId=%s listener=%s size=%d",
- entry.getRootTaskId(), entry, mPool.size());
- }
-
- @VisibleForTesting
- void incrementPool() {
- ProtoLog.v(WM_SHELL_TASK_ORG, "incrementPool size=%d", mPool.size());
- final AppPair entry = new AppPair(mController);
- // TODO: multi-display...
- mController.getTaskOrganizer().createRootTask(
- DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN, entry);
- mPool.add(entry);
- }
-
- @VisibleForTesting
- int poolSize() {
- return mPool.size();
- }
-
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
- for (int i = mPool.size() - 1; i >= 0; --i) {
- mPool.get(i).dump(pw, childPrefix);
- }
- }
-
- @Override
- public String toString() {
- return TAG + "#" + mPool.size();
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS
deleted file mode 100644
index 4d9b520e3f0e..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# WM shell sub-modules apppair owner
-chenghsiuchang@google.com
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 f427a2c4bc95..ea3712ba34b4 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,6 +25,7 @@ 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.BubblePositioner.TASKBAR_POSITION_BOTTOM;
@@ -79,7 +80,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
-import android.window.WindowContainerTransaction;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
@@ -90,7 +90,6 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
@@ -435,17 +434,13 @@ public class BubbleController {
});
mDisplayController.addDisplayChangingController(
- new DisplayChangeController.OnDisplayChangingListener() {
- @Override
- public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- WindowContainerTransaction t) {
- // This is triggered right before the rotation is applied
- if (fromRotation != toRotation) {
- if (mStackView != null) {
- // Layout listener set on stackView will update the positioner
- // once the rotation is applied
- mStackView.onOrientationChanged();
- }
+ (displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> {
+ // This is triggered right before the rotation is applied
+ if (fromRotation != toRotation) {
+ if (mStackView != null) {
+ // Layout listener set on stackView will update the positioner
+ // once the rotation is applied
+ mStackView.onOrientationChanged();
}
}
});
@@ -885,6 +880,19 @@ public class BubbleController {
}
}
+ private void onNotificationPanelExpandedChanged(boolean expanded) {
+ if (DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "onNotificationPanelExpandedChanged: expanded=" + expanded);
+ }
+ if (mStackView != null && mStackView.isExpanded()) {
+ if (expanded) {
+ mStackView.stopMonitoringSwipeUpGesture();
+ } else {
+ mStackView.startMonitoringSwipeUpGesture();
+ }
+ }
+ }
+
private void setSysuiProxy(Bubbles.SysuiProxy proxy) {
mSysuiProxy = proxy;
}
@@ -1468,6 +1476,18 @@ public class BubbleController {
}
/**
+ * 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
+ * WM Shell main thread.
+ *
+ * @param callback callback that has the result of notification panel expanded state
+ */
+ public void isNotificationPanelExpanded(Consumer<Boolean> callback) {
+ mSysuiProxy.isNotificationPanelExpand(expanded ->
+ mMainExecutor.execute(() -> callback.accept(expanded)));
+ }
+
+ /**
* Description of current bubble state.
*/
private void dump(PrintWriter pw, String[] args) {
@@ -1546,7 +1566,7 @@ public class BubbleController {
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
mBubblePositioner.setImeVisible(imeVisible, imeHeight);
if (mStackView != null) {
- mStackView.animateForIme(imeVisible);
+ mStackView.setImeVisible(imeVisible);
}
}
}
@@ -1843,6 +1863,12 @@ public class BubbleController {
}
@Override
+ public void onNotificationPanelExpandedChanged(boolean expanded) {
+ mMainExecutor.execute(
+ () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
+ }
+
+ @Override
public void dump(PrintWriter pw, String[] args) {
try {
mMainExecutor.executeBlocking(() -> {
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 dc2ace949f0c..dce6b56261ff 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
@@ -46,6 +46,9 @@ public class BubbleDebugConfig {
static final boolean DEBUG_OVERFLOW = false;
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 =
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 b8bf1a8e497e..4f225fff1451 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
@@ -43,10 +43,13 @@ import android.graphics.CornerPathEffect;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Picture;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.RemoteException;
import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.IntProperty;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -75,6 +78,62 @@ import java.io.PrintWriter;
public class BubbleExpandedView extends LinearLayout {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
+ /** {@link IntProperty} for updating bottom clip */
+ public static final IntProperty<BubbleExpandedView> BOTTOM_CLIP_PROPERTY =
+ new IntProperty<BubbleExpandedView>("bottomClip") {
+ @Override
+ public void setValue(BubbleExpandedView expandedView, int value) {
+ expandedView.setBottomClip(value);
+ }
+
+ @Override
+ public Integer get(BubbleExpandedView expandedView) {
+ return expandedView.mBottomClip;
+ }
+ };
+
+ /** {@link FloatProperty} for updating taskView or overflow alpha */
+ public static final FloatProperty<BubbleExpandedView> CONTENT_ALPHA =
+ new FloatProperty<BubbleExpandedView>("contentAlpha") {
+ @Override
+ public void setValue(BubbleExpandedView expandedView, float value) {
+ expandedView.setContentAlpha(value);
+ }
+
+ @Override
+ public Float get(BubbleExpandedView expandedView) {
+ return expandedView.getContentAlpha();
+ }
+ };
+
+ /** {@link FloatProperty} for updating background and pointer alpha */
+ public static final FloatProperty<BubbleExpandedView> BACKGROUND_ALPHA =
+ new FloatProperty<BubbleExpandedView>("backgroundAlpha") {
+ @Override
+ public void setValue(BubbleExpandedView expandedView, float value) {
+ expandedView.setBackgroundAlpha(value);
+ }
+
+ @Override
+ public Float get(BubbleExpandedView expandedView) {
+ return expandedView.getAlpha();
+ }
+ };
+
+ /** {@link FloatProperty} for updating manage button alpha */
+ public static final FloatProperty<BubbleExpandedView> MANAGE_BUTTON_ALPHA =
+ new FloatProperty<BubbleExpandedView>("manageButtonAlpha") {
+ @Override
+ public void setValue(BubbleExpandedView expandedView, float value) {
+ expandedView.mManageButton.setAlpha(value);
+ }
+
+ @Override
+ public Float get(BubbleExpandedView expandedView) {
+ return expandedView.mManageButton.getAlpha();
+ }
+ };
+
// The triangle pointing to the expanded view
private View mPointerView;
@Nullable private int[] mExpandedViewContainerLocation;
@@ -90,7 +149,7 @@ public class BubbleExpandedView extends LinearLayout {
/**
* Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If
- * {@link #mIsAlphaAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha
+ * {@link #mIsAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha
* value until the animation ends.
*/
private boolean mIsContentVisible = false;
@@ -99,12 +158,13 @@ public class BubbleExpandedView extends LinearLayout {
* Whether we're animating the {@code TaskView}'s alpha value. If so, we will hold off on
* applying alpha changes from {@link #setContentVisibility} until the animation ends.
*/
- private boolean mIsAlphaAnimating = false;
+ private boolean mIsAnimating = false;
private int mPointerWidth;
private int mPointerHeight;
private float mPointerRadius;
private float mPointerOverlap;
+ private final PointF mPointerPos = new PointF();
private CornerPathEffect mPointerEffect;
private ShapeDrawable mCurrentPointer;
private ShapeDrawable mTopPointer;
@@ -113,11 +173,13 @@ public class BubbleExpandedView extends LinearLayout {
private float mCornerRadius = 0f;
private int mBackgroundColorFloating;
private boolean mUsingMaxHeight;
-
+ private int mTopClip = 0;
+ private int mBottomClip = 0;
@Nullable private Bubble mBubble;
private PendingIntent mPendingIntent;
// TODO(b/170891664): Don't use a flag, set the BubbleOverflow object instead
private boolean mIsOverflow;
+ private boolean mIsClipping;
private BubbleController mController;
private BubbleStackView mStackView;
@@ -268,7 +330,8 @@ public class BubbleExpandedView extends LinearLayout {
mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
+ Rect clip = new Rect(0, mTopClip, view.getWidth(), view.getHeight() - mBottomClip);
+ outline.setRoundRect(clip, mCornerRadius);
}
});
mExpandedViewContainer.setClipToOutline(true);
@@ -300,9 +363,9 @@ public class BubbleExpandedView extends LinearLayout {
// they should not collapse the stack (which all other touches on areas around the AV
// would do).
if (motionEvent.getRawY() >= avBounds.top
- && motionEvent.getRawY() <= avBounds.bottom
- && (motionEvent.getRawX() < avBounds.left
- || motionEvent.getRawX() > avBounds.right)) {
+ && motionEvent.getRawY() <= avBounds.bottom
+ && (motionEvent.getRawX() < avBounds.left
+ || motionEvent.getRawX() > avBounds.right)) {
return true;
}
@@ -384,7 +447,7 @@ public class BubbleExpandedView extends LinearLayout {
}
void applyThemeAttrs() {
- final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+ final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
android.R.attr.dialogCornerRadius,
android.R.attr.colorBackgroundFloating});
boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
@@ -429,7 +492,7 @@ public class BubbleExpandedView extends LinearLayout {
* ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble
* being dragged out, the manage menu) this is set to false, otherwise it should be true.
*/
- void setSurfaceZOrderedOnTop(boolean onTop) {
+ public void setSurfaceZOrderedOnTop(boolean onTop) {
if (mTaskView == null) {
return;
}
@@ -510,12 +573,12 @@ public class BubbleExpandedView extends LinearLayout {
}
/**
- * Whether we are currently animating the {@code TaskView}'s alpha value. If this is set to
+ * Whether we are currently animating the {@code TaskView}. If this is set to
* true, calls to {@link #setContentVisibility} will not be applied until this is set to false
* again.
*/
- void setAlphaAnimating(boolean animating) {
- mIsAlphaAnimating = animating;
+ public void setAnimating(boolean animating) {
+ mIsAnimating = animating;
// If we're done animating, apply the correct
if (!animating) {
@@ -524,18 +587,139 @@ public class BubbleExpandedView extends LinearLayout {
}
/**
- * Sets the alpha of the underlying {@code TaskView}, since changing the expanded view's alpha
- * does not affect the {@code TaskView} since it uses a Surface.
+ * Get alpha from underlying {@code TaskView} if this view is for a bubble.
+ * Or get alpha for the overflow view if this view is for overflow.
+ *
+ * @return alpha for the content being shown
*/
- void setTaskViewAlpha(float alpha) {
+ public float getContentAlpha() {
+ if (mIsOverflow) {
+ return mOverflowView.getAlpha();
+ }
if (mTaskView != null) {
+ return mTaskView.getAlpha();
+ }
+ return 1f;
+ }
+
+ /**
+ * Set alpha of the underlying {@code TaskView} if this view is for a bubble.
+ * Or set alpha for the overflow view if this view is for overflow.
+ *
+ * Changing expanded view's alpha does not affect the {@code TaskView} since it uses a Surface.
+ */
+ public void setContentAlpha(float alpha) {
+ if (mIsOverflow) {
+ mOverflowView.setAlpha(alpha);
+ } else if (mTaskView != null) {
mTaskView.setAlpha(alpha);
}
+ }
+
+ /**
+ * Sets the alpha of the background and the pointer view.
+ */
+ public void setBackgroundAlpha(float alpha) {
mPointerView.setAlpha(alpha);
setAlpha(alpha);
}
/**
+ * Set translation Y for the expanded view content.
+ * Excludes manage button and pointer.
+ */
+ public void setContentTranslationY(float translationY) {
+ mExpandedViewContainer.setTranslationY(translationY);
+
+ // Left or right pointer can become detached when moving the view up
+ if (translationY <= 0 && (isShowingLeftPointer() || isShowingRightPointer())) {
+ // Y coordinate where the pointer would start to get detached from the expanded view.
+ // Takes into account bottom clipping and rounded corners
+ float detachPoint =
+ mExpandedViewContainer.getBottom() - mBottomClip - mCornerRadius + translationY;
+ float pointerBottom = mPointerPos.y + mPointerHeight;
+ // If pointer bottom is past detach point, move it in by that many pixels
+ float horizontalShift = 0;
+ if (pointerBottom > detachPoint) {
+ horizontalShift = pointerBottom - detachPoint;
+ }
+ if (isShowingLeftPointer()) {
+ // Move left pointer right
+ movePointerBy(horizontalShift, 0);
+ } else {
+ // Move right pointer left
+ movePointerBy(-horizontalShift, 0);
+ }
+ // Hide pointer if it is moved by entire width
+ mPointerView.setVisibility(
+ horizontalShift > mPointerWidth ? View.INVISIBLE : View.VISIBLE);
+ }
+ }
+
+ /**
+ * Update alpha value for the manage button
+ */
+ public void setManageButtonAlpha(float alpha) {
+ mManageButton.setAlpha(alpha);
+ }
+
+ /**
+ * Set {@link #setTranslationY(float) translationY} for the manage button
+ */
+ public void setManageButtonTranslationY(float translationY) {
+ mManageButton.setTranslationY(translationY);
+ }
+
+ /**
+ * Set top clipping for the view
+ */
+ public void setTopClip(int clip) {
+ mTopClip = clip;
+ onContainerClipUpdate();
+ }
+
+ /**
+ * Set bottom clipping for the view
+ */
+ public void setBottomClip(int clip) {
+ mBottomClip = clip;
+ onContainerClipUpdate();
+ }
+
+ private void onContainerClipUpdate() {
+ if (mTopClip == 0 && mBottomClip == 0) {
+ if (mIsClipping) {
+ mIsClipping = false;
+ if (mTaskView != null) {
+ mTaskView.setClipBounds(null);
+ mTaskView.setEnableSurfaceClipping(false);
+ }
+ mExpandedViewContainer.invalidateOutline();
+ }
+ } else {
+ if (!mIsClipping) {
+ mIsClipping = true;
+ if (mTaskView != null) {
+ mTaskView.setEnableSurfaceClipping(true);
+ }
+ }
+ mExpandedViewContainer.invalidateOutline();
+ if (mTaskView != null) {
+ mTaskView.setClipBounds(new Rect(0, mTopClip, mTaskView.getWidth(),
+ mTaskView.getHeight() - mBottomClip));
+ }
+ }
+ }
+
+ /**
+ * Move pointer from base position
+ */
+ public void movePointerBy(float x, float y) {
+ mPointerView.setTranslationX(mPointerPos.x + x);
+ mPointerView.setTranslationY(mPointerPos.y + y);
+ }
+
+ /**
* Set visibility of contents in the expanded state.
*
* @param visibility {@code true} if the contents should be visible on the screen.
@@ -543,13 +727,13 @@ public class BubbleExpandedView extends LinearLayout {
* Note that this contents visibility doesn't affect visibility at {@link android.view.View},
* and setting {@code false} actually means rendering the contents in transparent.
*/
- void setContentVisibility(boolean visibility) {
+ public void setContentVisibility(boolean visibility) {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "setContentVisibility: visibility=" + visibility
+ " bubble=" + getBubbleKey());
}
mIsContentVisible = visibility;
- if (mTaskView != null && !mIsAlphaAnimating) {
+ if (mTaskView != null && !mIsAnimating) {
mTaskView.setAlpha(visibility ? 1f : 0f);
mPointerView.setAlpha(visibility ? 1f : 0f);
}
@@ -560,6 +744,44 @@ public class BubbleExpandedView extends LinearLayout {
return mTaskView;
}
+ @VisibleForTesting
+ public BubbleOverflowContainerView getOverflow() {
+ return mOverflowView;
+ }
+
+
+ /**
+ * Return content height: taskView or overflow.
+ * Takes into account clippings set by {@link #setTopClip(int)} and {@link #setBottomClip(int)}
+ *
+ * @return if bubble is for overflow, return overflow height, otherwise return taskView height
+ */
+ public int getContentHeight() {
+ if (mIsOverflow) {
+ return mOverflowView.getHeight() - mTopClip - mBottomClip;
+ }
+ if (mTaskView != null) {
+ return mTaskView.getHeight() - mTopClip - mBottomClip;
+ }
+ return 0;
+ }
+
+ /**
+ * Return bottom position of the content on screen
+ *
+ * @return if bubble is for overflow, return value for overflow, otherwise taskView
+ */
+ public int getContentBottomOnScreen() {
+ Rect out = new Rect();
+ if (mIsOverflow) {
+ mOverflowView.getBoundsOnScreen(out);
+ }
+ if (mTaskView != null) {
+ mTaskView.getBoundsOnScreen(out);
+ }
+ return out.bottom;
+ }
+
int getTaskId() {
return mTaskId;
}
@@ -687,7 +909,9 @@ public class BubbleExpandedView extends LinearLayout {
mTaskView.onLocationChanged();
}
if (mIsOverflow) {
- mOverflowView.show();
+ post(() -> {
+ mOverflowView.show();
+ });
}
}
@@ -730,38 +954,59 @@ public class BubbleExpandedView extends LinearLayout {
post(() -> {
mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
updatePointerView();
- float pointerY;
- float pointerX;
if (showVertically) {
- pointerY = bubbleCenter - (mPointerWidth / 2f);
+ mPointerPos.y = bubbleCenter - (mPointerWidth / 2f);
if (!isRtl) {
- pointerX = onLeft
+ mPointerPos.x = onLeft
? -mPointerHeight + mPointerOverlap
: getWidth() - mPaddingRight - mPointerOverlap;
} else {
- pointerX = onLeft
+ mPointerPos.x = onLeft
? -(getWidth() - mPaddingLeft - mPointerOverlap)
: mPointerHeight - mPointerOverlap;
}
} else {
- pointerY = mPointerOverlap;
+ mPointerPos.y = mPointerOverlap;
if (!isRtl) {
- pointerX = bubbleCenter - (mPointerWidth / 2f);
+ mPointerPos.x = bubbleCenter - (mPointerWidth / 2f);
} else {
- pointerX = -(getWidth() - mPaddingLeft - bubbleCenter) + (mPointerWidth / 2f);
+ mPointerPos.x = -(getWidth() - mPaddingLeft - bubbleCenter)
+ + (mPointerWidth / 2f);
}
}
if (animate) {
- mPointerView.animate().translationX(pointerX).translationY(pointerY).start();
+ mPointerView.animate().translationX(mPointerPos.x).translationY(
+ mPointerPos.y).start();
} else {
- mPointerView.setTranslationY(pointerY);
- mPointerView.setTranslationX(pointerX);
+ mPointerView.setTranslationY(mPointerPos.y);
+ mPointerView.setTranslationX(mPointerPos.x);
mPointerView.setVisibility(VISIBLE);
}
});
}
/**
+ * Return true if pointer is shown on the left
+ */
+ public boolean isShowingLeftPointer() {
+ return mCurrentPointer == mLeftPointer;
+ }
+
+ /**
+ * Return true if pointer is shown on the right
+ */
+ public boolean isShowingRightPointer() {
+ return mCurrentPointer == mRightPointer;
+ }
+
+ /**
+ * Return width of the current pointer
+ */
+ public int getPointerWidth() {
+ return mPointerWidth;
+ }
+
+ /**
* Position of the manage button displayed in the expanded view. Used for placing user
* education about the manage button.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index fcd0ed7308ef..9aa285fff19c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -167,7 +167,10 @@ public class BubbleOverflowContainerView extends LinearLayout {
void updateOverflow() {
Resources res = getResources();
- final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
+ int columns = (int) Math.round(getWidth()
+ / (res.getDimension(R.dimen.bubble_name_width)));
+ columns = columns > 0 ? columns : res.getInteger(R.integer.bubbles_overflow_columns);
+
mRecyclerView.setLayoutManager(
new OverflowGridLayoutManager(getContext(), columns));
if (mRecyclerView.getItemDecorationCount() == 0) {
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 e9729e45731b..dbad5df9cf56 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
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles;
+import static android.view.View.LAYOUT_DIRECTION_RTL;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
@@ -28,7 +30,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import android.view.Surface;
-import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
@@ -366,6 +367,14 @@ public class BubblePositioner {
return mImeVisible ? mImeHeight : 0;
}
+ /** Return top position of the IME if it's visible */
+ public int getImeTop() {
+ if (mImeVisible) {
+ return getScreenRect().bottom - getImeHeight() - getInsets().bottom;
+ }
+ return 0;
+ }
+
/** Sets whether the IME is visible. **/
public void setImeVisible(boolean visible, int height) {
mImeVisible = visible;
@@ -557,16 +566,30 @@ public class BubblePositioner {
* @return the position of the bubble on-screen when the stack is expanded.
*/
public PointF getExpandedBubbleXY(int index, BubbleStackView.StackViewState state) {
- final float positionInRow = index * (mBubbleSize + mSpacingBetweenBubbles);
+ boolean showBubblesVertically = showBubblesVertically();
+ boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection()
+ == LAYOUT_DIRECTION_RTL;
+
+ int onScreenIndex;
+ if (showBubblesVertically || !isRtl) {
+ onScreenIndex = index;
+ } else {
+ // If bubbles are shown horizontally, check if RTL language is used.
+ // If RTL is active, position first bubble on the right and last on the left.
+ // Last bubble has screen index 0 and first bubble has max screen index value.
+ onScreenIndex = state.numberOfBubbles - 1 - index;
+ }
+
+ final float positionInRow = onScreenIndex * (mBubbleSize + mSpacingBetweenBubbles);
final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
- final float centerPosition = showBubblesVertically()
+ final float centerPosition = showBubblesVertically
? mPositionRect.centerY()
: mPositionRect.centerX();
// alignment - centered on the edge
final float rowStart = centerPosition - (expandedStackSize / 2f);
float x;
float y;
- if (showBubblesVertically()) {
+ if (showBubblesVertically) {
int inset = mExpandedViewLargeScreenInsetClosestEdge;
y = rowStart + positionInRow;
int left = mIsLargeScreen
@@ -583,8 +606,8 @@ public class BubblePositioner {
x = rowStart + positionInRow;
}
- if (showBubblesVertically() && mImeVisible) {
- return new PointF(x, getExpandedBubbleYForIme(index, state));
+ if (showBubblesVertically && mImeVisible) {
+ return new PointF(x, getExpandedBubbleYForIme(onScreenIndex, state));
}
return new PointF(x, y);
}
@@ -693,7 +716,7 @@ public class BubblePositioner {
// Start on the left if we're in LTR, right otherwise.
final boolean startOnLeft =
mContext.getResources().getConfiguration().getLayoutDirection()
- != View.LAYOUT_DIRECTION_RTL;
+ != 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
@@ -749,4 +772,21 @@ public class BubblePositioner {
public void setPinnedLocation(PointF point) {
mPinLocation = point;
}
+
+ /**
+ * Navigation bar has an area where system gestures can be started from.
+ *
+ * @return {@link Rect} for system navigation bar gesture zone
+ */
+ public Rect getNavBarGestureZone() {
+ // Gesture zone height from the bottom
+ int gestureZoneHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_gesture_height);
+ Rect screen = getScreenRect();
+ return new Rect(
+ screen.left,
+ screen.bottom - gestureZoneHeight,
+ screen.right,
+ screen.bottom);
+ }
}
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 0e8dc63943a6..66be2a0cebea 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,6 +21,7 @@ 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;
@@ -44,6 +45,7 @@ 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;
@@ -56,6 +58,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
+import android.view.WindowManagerPolicyConstants;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
@@ -75,8 +78,12 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
import com.android.wm.shell.bubbles.animation.ExpandedAnimationController;
+import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationController;
+import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerImpl;
+import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerStub;
import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
import com.android.wm.shell.bubbles.animation.StackAnimationController;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -89,6 +96,7 @@ import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -97,6 +105,12 @@ import java.util.stream.Collectors;
*/
public class BubbleStackView extends FrameLayout
implements ViewTreeObserver.OnComputeInternalInsetsListener {
+ /**
+ * Set to {@code true} to enable home gesture handling in bubbles
+ */
+ public static final boolean HOME_GESTURE_ENABLED =
+ SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", 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. */
@@ -123,6 +137,9 @@ public class BubbleStackView extends FrameLayout
private static final float SCRIM_ALPHA = 0.6f;
+ /** Minimum alpha value for scrim when alpha is being changed via drag */
+ private static final float MIN_SCRIM_ALPHA_FOR_DRAG = 0.2f;
+
/**
* How long to wait to animate the stack temporarily invisible after a drag/flyout hide
* animation ends, if we are in fact temporarily invisible.
@@ -148,7 +165,7 @@ public class BubbleStackView extends FrameLayout
* Handler to use for all delayed animations - this way, we can easily cancel them before
* starting a new animation.
*/
- private final ShellExecutor mDelayedAnimationExecutor;
+ private final ShellExecutor mMainExecutor;
private Runnable mDelayedAnimation;
/**
@@ -197,8 +214,10 @@ public class BubbleStackView extends FrameLayout
private PhysicsAnimationLayout mBubbleContainer;
private StackAnimationController mStackAnimationController;
private ExpandedAnimationController mExpandedAnimationController;
+ private ExpandedViewAnimationController mExpandedViewAnimationController;
private View mScrim;
+ private boolean mScrimAnimating;
private View mManageMenuScrim;
private FrameLayout mExpandedViewContainer;
@@ -276,6 +295,9 @@ public class BubbleStackView extends FrameLayout
*/
private int mPointerIndexDown = -1;
+ @Nullable
+ private BubblesNavBarGestureTracker mBubblesNavBarGestureTracker;
+
/** Description of current animation controller state. */
public void dump(PrintWriter pw, String[] args) {
pw.println("Stack view state:");
@@ -693,6 +715,90 @@ public class BubbleStackView extends FrameLayout
}
};
+ /** Touch listener set on the whole view that forwards event to the swipe up listener. */
+ private final RelativeTouchListener mContainerSwipeListener = new RelativeTouchListener() {
+ @Override
+ public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
+ // Pass move event on to swipe listener
+ mSwipeUpListener.onDown(ev.getX(), ev.getY());
+ return true;
+ }
+
+ @Override
+ public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+ float viewInitialY, float dx, float dy) {
+ // Pass move event on to swipe listener
+ mSwipeUpListener.onMove(dx, dy);
+ }
+
+ @Override
+ public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+ float viewInitialY, float dx, float dy, float velX, float velY) {
+ // Pass up even on to swipe listener
+ mSwipeUpListener.onUp(velX, velY);
+ }
+ };
+
+ /** MotionEventListener that listens from home gesture swipe event. */
+ private final MotionEventListener mSwipeUpListener = new MotionEventListener() {
+ @Override
+ public void onDown(float x, float y) {}
+
+ @Override
+ public void onMove(float dx, float dy) {
+ if ((mManageEduView != null && mManageEduView.getVisibility() == VISIBLE)
+ || isStackEduShowing()) {
+ return;
+ }
+
+ if (mShowingManage) {
+ showManageMenu(false /* show */);
+ }
+ // Only allow up, normalize for up direction
+ float collapsed = -Math.min(dy, 0);
+ mExpandedViewAnimationController.updateDrag((int) collapsed);
+
+ // Update scrim
+ if (!mScrimAnimating) {
+ mScrim.setAlpha(getScrimAlphaForDrag(collapsed));
+ }
+ }
+
+ @Override
+ public void onCancel() {
+ mExpandedViewAnimationController.animateBackToExpanded();
+ }
+
+ @Override
+ public void onUp(float velX, float velY) {
+ mExpandedViewAnimationController.setSwipeVelocity(velY);
+ if (mExpandedViewAnimationController.shouldCollapse()) {
+ // Update data first and start the animation when we are processing change
+ mBubbleData.setExpanded(false);
+ } else {
+ mExpandedViewAnimationController.animateBackToExpanded();
+
+ // Update scrim
+ if (!mScrimAnimating) {
+ showScrim(true);
+ }
+ }
+ }
+
+ private float getScrimAlphaForDrag(float dragAmount) {
+ // dragAmount should be negative as we allow scroll up only
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ float alphaRange = SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG;
+
+ int dragMax = mExpandedBubble.getExpandedView().getContentHeight();
+ float dragFraction = dragAmount / dragMax;
+
+ return Math.max(SCRIM_ALPHA - alphaRange * dragFraction, MIN_SCRIM_ALPHA_FOR_DRAG);
+ }
+ return SCRIM_ALPHA;
+ }
+ };
+
/** Click listener set on the flyout, which expands the stack when the flyout is tapped. */
private OnClickListener mFlyoutClickListener = new OnClickListener() {
@Override
@@ -766,7 +872,7 @@ public class BubbleStackView extends FrameLayout
ShellExecutor mainExecutor) {
super(context);
- mDelayedAnimationExecutor = mainExecutor;
+ mMainExecutor = mainExecutor;
mBubbleController = bubbleController;
mBubbleData = data;
@@ -796,6 +902,14 @@ public class BubbleStackView extends FrameLayout
mExpandedAnimationController = new ExpandedAnimationController(mPositioner,
onBubbleAnimatedOut, this);
+
+ if (HOME_GESTURE_ENABLED) {
+ mExpandedViewAnimationController =
+ new ExpandedViewAnimationControllerImpl(context, mPositioner);
+ } else {
+ mExpandedViewAnimationController = new ExpandedViewAnimationControllerStub();
+ }
+
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
// Force LTR by default since most of the Bubbles UI is positioned manually by the user, or
@@ -971,7 +1085,7 @@ public class BubbleStackView extends FrameLayout
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
// We need to be Z ordered on top in order for alpha animations to work.
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
- mExpandedBubble.getExpandedView().setAlphaAnimating(true);
+ mExpandedBubble.getExpandedView().setAnimating(true);
}
}
@@ -985,14 +1099,15 @@ public class BubbleStackView extends FrameLayout
// = 0f remains in effect.
&& !mExpandedViewTemporarilyHidden) {
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
- mExpandedBubble.getExpandedView().setAlphaAnimating(false);
+ mExpandedBubble.getExpandedView().setAnimating(false);
}
}
});
mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setTaskViewAlpha(
- (float) valueAnimator.getAnimatedValue());
+ float alpha = (float) valueAnimator.getAnimatedValue();
+ mExpandedBubble.getExpandedView().setContentAlpha(alpha);
+ mExpandedBubble.getExpandedView().setBackgroundAlpha(alpha);
}
});
@@ -1795,6 +1910,7 @@ public class BubbleStackView extends FrameLayout
private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) {
final BubbleViewProvider previouslySelected = mExpandedBubble;
mExpandedBubble = bubbleToSelect;
+ mExpandedViewAnimationController.setExpandedView(mExpandedBubble.getExpandedView());
if (mIsExpanded) {
hideCurrentInputMethod();
@@ -1843,12 +1959,19 @@ public class BubbleStackView extends FrameLayout
return;
}
+ boolean wasExpanded = mIsExpanded;
+
hideCurrentInputMethod();
mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand);
- if (mIsExpanded) {
- animateCollapse();
+ if (wasExpanded) {
+ stopMonitoringSwipeUpGesture();
+ if (HOME_GESTURE_ENABLED) {
+ animateCollapse();
+ } else {
+ animateCollapseWithScale();
+ }
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
} else {
animateExpansion();
@@ -1856,11 +1979,58 @@ public class BubbleStackView extends FrameLayout
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
logBubbleEvent(mExpandedBubble,
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
+ if (HOME_GESTURE_ENABLED) {
+ mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
+ if (!notifPanelExpanded && mIsExpanded) {
+ startMonitoringSwipeUpGesture();
+ }
+ });
+ }
}
notifyExpansionChanged(mExpandedBubble, mIsExpanded);
}
/**
+ * Monitor for swipe up gesture that is used to collapse expanded view
+ */
+ void startMonitoringSwipeUpGesture() {
+ if (DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "startMonitoringSwipeUpGesture");
+ }
+ stopMonitoringSwipeUpGestureInternal();
+
+ if (isGestureNavEnabled()) {
+ mBubblesNavBarGestureTracker = new BubblesNavBarGestureTracker(mContext, mPositioner);
+ mBubblesNavBarGestureTracker.start(mSwipeUpListener);
+ setOnTouchListener(mContainerSwipeListener);
+ }
+ }
+
+ private boolean isGestureNavEnabled() {
+ return mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode)
+ == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+ }
+
+ /**
+ * Stop monitoring for swipe up gesture
+ */
+ void stopMonitoringSwipeUpGesture() {
+ if (DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "stopMonitoringSwipeUpGesture");
+ }
+ stopMonitoringSwipeUpGestureInternal();
+ }
+
+ private void stopMonitoringSwipeUpGestureInternal() {
+ if (mBubblesNavBarGestureTracker != null) {
+ mBubblesNavBarGestureTracker.stop();
+ mBubblesNavBarGestureTracker = null;
+ setOnTouchListener(null);
+ }
+ }
+
+ /**
* Called when back press occurs while bubbles are expanded.
*/
public void onBackPressed() {
@@ -1982,15 +2152,28 @@ public class BubbleStackView extends FrameLayout
}
private void showScrim(boolean show) {
+ AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mScrimAnimating = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mScrimAnimating = false;
+ }
+ };
if (show) {
mScrim.animate()
.setInterpolator(ALPHA_IN)
.alpha(SCRIM_ALPHA)
+ .setListener(listener)
.start();
} else {
mScrim.animate()
.alpha(0f)
.setInterpolator(ALPHA_OUT)
+ .setListener(listener)
.start();
}
}
@@ -2072,11 +2255,12 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
if (mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setTaskViewAlpha(0f);
+ mExpandedBubble.getExpandedView().setContentAlpha(0f);
+ mExpandedBubble.getExpandedView().setBackgroundAlpha(0f);
// We'll be starting the alpha animation after a slight delay, so set this flag early
// here.
- mExpandedBubble.getExpandedView().setAlphaAnimating(true);
+ mExpandedBubble.getExpandedView().setAnimating(true);
}
mDelayedAnimation = () -> {
@@ -2114,10 +2298,10 @@ public class BubbleStackView extends FrameLayout
})
.start();
};
- mDelayedAnimationExecutor.executeDelayed(mDelayedAnimation, startDelay);
+ mMainExecutor.executeDelayed(mDelayedAnimation, startDelay);
}
- private void animateCollapse() {
+ private void animateCollapseWithScale() {
cancelDelayedExpandCollapseSwitchAnimations();
if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
@@ -2217,6 +2401,68 @@ public class BubbleStackView extends FrameLayout
.start();
}
+ private void animateCollapse() {
+ cancelDelayedExpandCollapseSwitchAnimations();
+
+ if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ mManageEduView.hide();
+ }
+
+ mIsExpanded = false;
+ mIsExpansionAnimating = true;
+
+ showScrim(false);
+
+ mBubbleContainer.cancelAllAnimations();
+
+ // If we were in the middle of swapping, the animating-out surface would have been scaling
+ // to zero - finish it off.
+ PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
+ mAnimatingOutSurfaceContainer.setScaleX(0f);
+ mAnimatingOutSurfaceContainer.setScaleY(0f);
+
+ // Let the expanded animation controller know that it shouldn't animate child adds/reorders
+ // since we're about to animate collapsed.
+ mExpandedAnimationController.notifyPreparingToCollapse();
+
+ final Runnable collapseBackToStack = () -> mExpandedAnimationController.collapseBackToStack(
+ mStackAnimationController
+ .getStackPositionAlongNearestHorizontalEdge()
+ /* collapseTo */,
+ () -> mBubbleContainer.setActiveController(mStackAnimationController));
+
+ final Runnable after = () -> {
+ final BubbleViewProvider previouslySelected = mExpandedBubble;
+ // TODO(b/231350255): investigate why this call is needed here
+ beforeExpandedViewAnimation();
+ if (mManageEduView != null) {
+ mManageEduView.hide();
+ }
+
+ if (DEBUG_BUBBLE_STACK_VIEW) {
+ Log.d(TAG, "animateCollapse");
+ Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
+ mExpandedBubble));
+ }
+ updateOverflowVisibility();
+ updateZOrder();
+ updateBadges(true /* setBadgeForCollapsedStack */);
+ afterExpandedViewAnimation();
+ if (previouslySelected != null) {
+ previouslySelected.setTaskViewVisibility(false);
+ }
+ mExpandedViewAnimationController.reset();
+ };
+ mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after);
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ // When the animation completes, we should no longer be showing the content.
+ // This won't actually update content visibility immediately, if we are currently
+ // animating. But updates the internal state for the content to be hidden after
+ // animation completes.
+ mExpandedBubble.getExpandedView().setContentVisibility(false);
+ }
+ }
+
private void animateSwitchBubbles() {
// If we're no longer expanded, this is meaningless.
if (!mIsExpanded) {
@@ -2278,7 +2524,7 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
- mDelayedAnimationExecutor.executeDelayed(() -> {
+ mMainExecutor.executeDelayed(() -> {
if (!mIsExpanded) {
mIsBubbleSwitchAnimating = false;
return;
@@ -2309,7 +2555,7 @@ public class BubbleStackView extends FrameLayout
* animating flags for those animations.
*/
private void cancelDelayedExpandCollapseSwitchAnimations() {
- mDelayedAnimationExecutor.removeCallbacks(mDelayedAnimation);
+ mMainExecutor.removeCallbacks(mDelayedAnimation);
mIsExpansionAnimating = false;
mIsBubbleSwitchAnimating = false;
@@ -2333,9 +2579,18 @@ public class BubbleStackView extends FrameLayout
/**
* Updates the stack based for IME changes. When collapsed it'll move the stack if it
* overlaps where they IME would be. When expanded it'll shift the expanded bubbles
- * if they might overlap with the IME (this only happens for large screens).
+ * if they might overlap with the IME (this only happens for large screens)
+ * and clip the expanded view.
*/
- public void animateForIme(boolean visible) {
+ public void setImeVisible(boolean visible) {
+ if (HOME_GESTURE_ENABLED) {
+ setImeVisibleInternal(visible);
+ } else {
+ setImeVisibleWithoutClipping(visible);
+ }
+ }
+
+ private void setImeVisibleWithoutClipping(boolean visible) {
if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
// This will update the animation so the bubbles move to position for the IME
mExpandedAnimationController.expandFromStack(() -> {
@@ -2386,6 +2641,62 @@ public class BubbleStackView extends FrameLayout
}
}
+ private void setImeVisibleInternal(boolean visible) {
+ if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
+ // This will update the animation so the bubbles move to position for the IME
+ mExpandedAnimationController.expandFromStack(() -> {
+ updatePointerPosition(false /* forIme */);
+ afterExpandedViewAnimation();
+ mExpandedViewAnimationController.animateForImeVisibilityChange(visible);
+ } /* after */);
+ return;
+ }
+
+ if (!mIsExpanded && getBubbleCount() > 0) {
+ final float stackDestinationY =
+ mStackAnimationController.animateForImeVisibility(visible);
+
+ // How far the stack is animating due to IME, we'll just animate the flyout by that
+ // much too.
+ final float stackDy =
+ stackDestinationY - mStackAnimationController.getStackPosition().y;
+
+ // If the flyout is visible, translate it along with the bubble stack.
+ if (mFlyout.getVisibility() == VISIBLE) {
+ PhysicsAnimator.getInstance(mFlyout)
+ .spring(DynamicAnimation.TRANSLATION_Y,
+ mFlyout.getTranslationY() + stackDy,
+ FLYOUT_IME_ANIMATION_SPRING_CONFIG)
+ .start();
+ }
+ }
+
+ if (mIsExpanded) {
+ mExpandedViewAnimationController.animateForImeVisibilityChange(visible);
+ if (mPositioner.showBubblesVertically()
+ && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ float selectedY = mPositioner.getExpandedBubbleXY(getState().selectedIndex,
+ getState()).y;
+ float newExpandedViewTop = mPositioner.getExpandedViewY(mExpandedBubble, selectedY);
+ mExpandedBubble.getExpandedView().setImeVisible(visible);
+ if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) {
+ mExpandedViewContainer.animate().translationY(newExpandedViewTop);
+ }
+ List<Animator> animList = new ArrayList();
+ for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
+ View child = mBubbleContainer.getChildAt(i);
+ float transY = mPositioner.getExpandedBubbleXY(i, getState()).y;
+ ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY);
+ animList.add(anim);
+ }
+ updatePointerPosition(true /* forIme */);
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(animList);
+ set.start();
+ }
+ }
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() != MotionEvent.ACTION_DOWN && ev.getActionIndex() != mPointerIndexDown) {
@@ -2474,8 +2785,10 @@ public class BubbleStackView extends FrameLayout
private void dismissBubbleIfExists(@Nullable BubbleViewProvider bubble) {
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
- if (mIsExpanded && mBubbleData.getBubbles().size() > 1) {
- // If we have more than 1 bubble we will perform the switch animation
+ if (mIsExpanded && mBubbleData.getBubbles().size() > 1
+ && Objects.equals(bubble, mExpandedBubble)) {
+ // If we have more than 1 bubble and it's the current bubble being dismissed,
+ // we will perform the switch animation
mIsBubbleSwitchAnimating = true;
}
mBubbleData.dismissBubbleWithKey(bubble.getKey(), Bubbles.DISMISS_USER_GESTURE);
@@ -2821,7 +3134,7 @@ public class BubbleStackView extends FrameLayout
&& mExpandedBubble.getExpandedView() != null) {
BubbleExpandedView bev = mExpandedBubble.getExpandedView();
bev.setContentVisibility(false);
- bev.setAlphaAnimating(!mIsExpansionAnimating);
+ bev.setAnimating(!mIsExpansionAnimating);
mExpandedViewContainerMatrix.setScaleX(0f);
mExpandedViewContainerMatrix.setScaleY(0f);
mExpandedViewContainerMatrix.setTranslate(0f, 0f);
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 8a0db0a12711..0072da19b9ef 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
@@ -214,6 +214,11 @@ public interface Bubbles {
int modificationType);
/**
+ * Called when notification panel is expanded or collapsed
+ */
+ void onNotificationPanelExpandedChanged(boolean expanded);
+
+ /**
* Called when the status bar has become visible or invisible (either permanently or
* temporarily).
*/
@@ -285,7 +290,7 @@ public interface Bubbles {
/** Callback to tell SysUi components execute some methods. */
interface SysuiProxy {
- void isNotificationShadeExpand(Consumer<Boolean> callback);
+ void isNotificationPanelExpand(Consumer<Boolean> callback);
void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
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
new file mode 100644
index 000000000000..e7beeeb06534
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
@@ -0,0 +1,104 @@
+/*
+ * 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.bubbles;
+
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
+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;
+import android.view.InputMonitor;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
+
+/**
+ * Set up tracking bubbles gestures that begin in navigation bar
+ */
+class BubblesNavBarGestureTracker {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubblesGestureTracker" : TAG_BUBBLES;
+
+ private static final String GESTURE_MONITOR = "bubbles-gesture";
+ private final Context mContext;
+ private final BubblePositioner mPositioner;
+
+ @Nullable
+ private InputMonitor mInputMonitor;
+ @Nullable
+ private InputEventReceiver mInputEventReceiver;
+
+ BubblesNavBarGestureTracker(Context context, BubblePositioner positioner) {
+ mContext = context;
+ mPositioner = positioner;
+ }
+
+ /**
+ * Start tracking gestures
+ *
+ * @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");
+ }
+
+ stopInternal();
+
+ mInputMonitor = InputManager.getInstance().monitorGestureInput(GESTURE_MONITOR,
+ mContext.getDisplayId());
+ InputChannel inputChannel = mInputMonitor.getInputChannel();
+
+ BubblesNavBarMotionEventHandler motionEventHandler =
+ new BubblesNavBarMotionEventHandler(mContext, mPositioner,
+ this::onInterceptTouch, listener);
+ mInputEventReceiver = new BubblesNavBarInputEventReceiver(inputChannel,
+ Choreographer.getInstance(), motionEventHandler);
+ }
+
+ void stop() {
+ if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "stop monitoring bubbles swipe up gesture");
+ }
+ stopInternal();
+ }
+
+ private void stopInternal() {
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
+ }
+
+ private void onInterceptTouch() {
+ if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "intercept touch event");
+ }
+ if (mInputMonitor != null) {
+ mInputMonitor.pilferPointers();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarInputEventReceiver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarInputEventReceiver.java
new file mode 100644
index 000000000000..45037b87830f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarInputEventReceiver.java
@@ -0,0 +1,51 @@
+/*
+ * 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.bubbles;
+
+import android.os.Looper;
+import android.view.BatchedInputEventReceiver;
+import android.view.Choreographer;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+
+/**
+ * Bubbles {@link BatchedInputEventReceiver} for monitoring touches from navbar gesture area
+ */
+class BubblesNavBarInputEventReceiver extends BatchedInputEventReceiver {
+
+ private final BubblesNavBarMotionEventHandler mMotionEventHandler;
+
+ BubblesNavBarInputEventReceiver(InputChannel inputChannel,
+ Choreographer choreographer, BubblesNavBarMotionEventHandler motionEventHandler) {
+ super(inputChannel, Looper.myLooper(), choreographer);
+ mMotionEventHandler = motionEventHandler;
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = false;
+ try {
+ if (!(event instanceof MotionEvent)) {
+ return;
+ }
+ handled = mMotionEventHandler.onMotionEvent((MotionEvent) event);
+ } finally {
+ finishInputEvent(event, handled);
+ }
+ }
+}
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
new file mode 100644
index 000000000000..844526ca0f35
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
@@ -0,0 +1,175 @@
+/*
+ * 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.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 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;
+
+/**
+ * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area
+ */
+class BubblesNavBarMotionEventHandler {
+ private static final String TAG =
+ TAG_WITH_CLASS_NAME ? "BubblesNavBarMotionEventHandler" : TAG_BUBBLES;
+ private static final int VELOCITY_UNITS = 1000;
+
+ private final Runnable mOnInterceptTouch;
+ private final MotionEventListener mMotionEventListener;
+ private final int mTouchSlop;
+ private final BubblePositioner mPositioner;
+ private final PointF mTouchDown = new PointF();
+ private boolean mTrackingTouches;
+ private boolean mInterceptingTouches;
+ @Nullable
+ private VelocityTracker mVelocityTracker;
+
+ BubblesNavBarMotionEventHandler(Context context, BubblePositioner positioner,
+ Runnable onInterceptTouch, MotionEventListener motionEventListener) {
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mPositioner = positioner;
+ mOnInterceptTouch = onInterceptTouch;
+ mMotionEventListener = motionEventListener;
+ }
+
+ /**
+ * Handle {@link MotionEvent} and forward it to {@code motionEventListener} defined in
+ * constructor
+ *
+ * @return {@code true} if this {@link MotionEvent} is handled (it started in the gesture area)
+ */
+ public boolean onMotionEvent(MotionEvent motionEvent) {
+ float dx = motionEvent.getX() - mTouchDown.x;
+ float dy = motionEvent.getY() - mTouchDown.y;
+
+ switch (motionEvent.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (isInGestureRegion(motionEvent)) {
+ mTouchDown.set(motionEvent.getX(), motionEvent.getY());
+ mMotionEventListener.onDown(motionEvent.getX(), motionEvent.getY());
+ mTrackingTouches = true;
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mTrackingTouches) {
+ if (!mInterceptingTouches && Math.hypot(dx, dy) > mTouchSlop) {
+ mInterceptingTouches = true;
+ mOnInterceptTouch.run();
+ }
+ if (mInterceptingTouches) {
+ getVelocityTracker().addMovement(motionEvent);
+ mMotionEventListener.onMove(dx, dy);
+ }
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (mTrackingTouches) {
+ mMotionEventListener.onCancel();
+ finishTracking();
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mTrackingTouches) {
+ if (mInterceptingTouches) {
+ getVelocityTracker().computeCurrentVelocity(VELOCITY_UNITS);
+ mMotionEventListener.onUp(getVelocityTracker().getXVelocity(),
+ getVelocityTracker().getYVelocity());
+ }
+ finishTracking();
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ 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());
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private VelocityTracker getVelocityTracker() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ return mVelocityTracker;
+ }
+
+ private void finishTracking() {
+ mTouchDown.set(0, 0);
+ mTrackingTouches = false;
+ mInterceptingTouches = false;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ /**
+ * Callback for receiving {@link MotionEvent} updates
+ */
+ interface MotionEventListener {
+ /**
+ * Touch down action.
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ */
+ void onDown(float x, float y);
+
+ /**
+ * Move action.
+ * Reports distance from point reported in {@link #onDown(float, float)}
+ *
+ * @param dx distance moved on x-axis from starting point, in pixels
+ * @param dy distance moved on y-axis from starting point, in pixels
+ */
+ void onMove(float dx, float dy);
+
+ /**
+ * Touch up action.
+ *
+ * @param velX velocity of the move action on x axis
+ * @param velY velocity of the move actin on y axis
+ */
+ void onUp(float velX, float velY);
+
+ /**
+ * Motion action was cancelled.
+ */
+ void onCancel();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
index cf0cefec401a..ea9d065d5f53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
@@ -17,8 +17,6 @@
package com.android.wm.shell.bubbles
import android.graphics.PointF
-import android.os.Handler
-import android.os.Looper
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
@@ -146,6 +144,12 @@ abstract class RelativeTouchListener : View.OnTouchListener {
velocityTracker.clear()
movedEnough = false
}
+
+ MotionEvent.ACTION_CANCEL -> {
+ v.handler.removeCallbacksAndMessages(null)
+ velocityTracker.clear()
+ movedEnough = false
+ }
}
return true
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 573f42468512..b521cb6a3d38 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
@@ -16,12 +16,16 @@
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.HOME_GESTURE_ENABLED;
import android.content.res.Resources;
import android.graphics.Path;
import android.graphics.PointF;
import android.view.View;
+import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -61,7 +65,10 @@ public class ExpandedAnimationController
private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f;
/** Stiffness for the expand/collapse path-following animation. */
- private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
+ private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 400;
+
+ /** Stiffness for the expand/collapse animation when home gesture handling is off */
+ private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS_WITHOUT_HOME_GESTURE = 1000;
/**
* Velocity required to dismiss an individual bubble without dragging it into the dismiss
@@ -73,6 +80,11 @@ public class ExpandedAnimationController
new PhysicsAnimator.SpringConfig(
EXPAND_COLLAPSE_ANIM_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+ private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfigWithoutHomeGesture =
+ new PhysicsAnimator.SpringConfig(
+ EXPAND_COLLAPSE_ANIM_STIFFNESS_WITHOUT_HOME_GESTURE,
+ SpringForce.DAMPING_RATIO_NO_BOUNCY);
+
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
private float mStackOffsetPx;
/** Size of each bubble. */
@@ -233,6 +245,11 @@ public class ExpandedAnimationController
};
}
+ boolean showBubblesVertically = mPositioner.showBubblesVertically();
+ final boolean isRtl =
+ mLayout.getContext().getResources().getConfiguration().getLayoutDirection()
+ == LAYOUT_DIRECTION_RTL;
+
// Animate each bubble individually, since each path will end in a different spot.
animationsForChildrenFromIndex(0, (index, animation) -> {
final View bubble = mLayout.getChildAt(index);
@@ -267,9 +284,20 @@ public class ExpandedAnimationController
// right side, the first bubble is traveling to the top left, so it leads. During
// collapse to the left, the first bubble has the shortest travel time back to the stack
// position, so it leads (and vice versa).
- final boolean firstBubbleLeads =
- (expanding && !mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX()))
+ final boolean firstBubbleLeads;
+ if (showBubblesVertically || !isRtl) {
+ firstBubbleLeads =
+ (expanding && !mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX()))
|| (!expanding && mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x));
+ } else {
+ // For RTL languages, when showing bubbles horizontally, it is reversed. The bubbles
+ // are positioned right to left. This means that when expanding from left, the top
+ // bubble will lead as it will be positioned on the right. And when expanding from
+ // right, the top bubble will have the least travel distance.
+ firstBubbleLeads =
+ (expanding && mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX()))
+ || (!expanding && !mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x));
+ }
final int startDelay = firstBubbleLeads
? (index * 10)
: ((mLayout.getChildCount() - index) * 10);
@@ -278,11 +306,20 @@ public class ExpandedAnimationController
(firstBubbleLeads && index == 0)
|| (!firstBubbleLeads && index == mLayout.getChildCount() - 1);
+ Interpolator interpolator;
+ if (HOME_GESTURE_ENABLED) {
+ // When home gesture is enabled, we use a different animation timing for collapse
+ interpolator = expanding
+ ? Interpolators.EMPHASIZED_ACCELERATE : Interpolators.EMPHASIZED_DECELERATE;
+ } else {
+ interpolator = Interpolators.LINEAR;
+ }
+
animation
.followAnimatedTargetAlongPath(
path,
EXPAND_COLLAPSE_TARGET_ANIM_DURATION /* targetAnimDuration */,
- Interpolators.LINEAR /* targetAnimInterpolator */,
+ interpolator /* targetAnimInterpolator */,
isLeadBubble ? mLeadBubbleEndAction : null /* endAction */,
() -> mLeadBubbleEndAction = null /* endAction */)
.withStartDelay(startDelay)
@@ -525,10 +562,16 @@ public class ExpandedAnimationController
finishRemoval.run();
mOnBubbleAnimatedOutAction.run();
} else {
+ PhysicsAnimator.SpringConfig springConfig;
+ if (HOME_GESTURE_ENABLED) {
+ springConfig = mAnimateOutSpringConfig;
+ } else {
+ springConfig = mAnimateOutSpringConfigWithoutHomeGesture;
+ }
PhysicsAnimator.getInstance(child)
.spring(DynamicAnimation.ALPHA, 0f)
- .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
- .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
+ .spring(DynamicAnimation.SCALE_X, 0f, springConfig)
+ .spring(DynamicAnimation.SCALE_Y, 0f, springConfig)
.withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
.start();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
new file mode 100644
index 000000000000..8a33780bc8d5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.animation;
+
+import com.android.wm.shell.bubbles.BubbleExpandedView;
+
+/**
+ * Animation controller for bubble expanded view collapsing
+ */
+public interface ExpandedViewAnimationController {
+ /**
+ * Set expanded view that this controller is working with.
+ */
+ void setExpandedView(BubbleExpandedView expandedView);
+
+ /**
+ * Set current collapse value, in pixels.
+ *
+ * @param distance pixels that user dragged the view by
+ */
+ void updateDrag(float distance);
+
+ /**
+ * Set current swipe velocity.
+ * Velocity is directional:
+ * <ul>
+ * <li>velocity < 0 means swipe direction is up</li>
+ * <li>velocity > 0 means swipe direction is down</li>
+ * </ul>
+ */
+ void setSwipeVelocity(float velocity);
+
+ /**
+ * Check if view is dragged past collapse threshold or swipe up velocity exceeds min velocity
+ * required to collapse the view
+ */
+ boolean shouldCollapse();
+
+ /**
+ * Animate view to collapsed state
+ *
+ * @param startStackCollapse runnable that is triggered when bubbles can start moving back to
+ * their collapsed location
+ * @param after runnable to run after animation is complete
+ */
+ void animateCollapse(Runnable startStackCollapse, Runnable after);
+
+ /**
+ * Animate the view back to fully expanded state.
+ */
+ void animateBackToExpanded();
+
+ /**
+ * Animate view for IME visibility change
+ */
+ void animateForImeVisibilityChange(boolean visible);
+
+ /**
+ * Reset the view to fully expanded state
+ */
+ void reset();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
new file mode 100644
index 000000000000..845dca34b41f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
@@ -0,0 +1,431 @@
+/*
+ * 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.bubbles.animation;
+
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_COLLAPSE_ANIMATOR;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_EXPANDED_VIEW_DRAGGING;
+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.BubbleExpandedView.BACKGROUND_ALPHA;
+import static com.android.wm.shell.bubbles.BubbleExpandedView.BOTTOM_CLIP_PROPERTY;
+import static com.android.wm.shell.bubbles.BubbleExpandedView.CONTENT_ALPHA;
+import static com.android.wm.shell.bubbles.BubbleExpandedView.MANAGE_BUTTON_ALPHA;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.wm.shell.animation.FlingAnimationUtils;
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.bubbles.BubbleExpandedView;
+import com.android.wm.shell.bubbles.BubblePositioner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of {@link ExpandedViewAnimationController} that uses a collapse animation to
+ * hide the {@link BubbleExpandedView}
+ */
+public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimationController {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ExpandedViewAnimCtrl" : TAG_BUBBLES;
+
+ private static final float COLLAPSE_THRESHOLD = 0.02f;
+
+ private static final int COLLAPSE_DURATION_MS = 250;
+
+ private static final int MANAGE_BUTTON_ANIM_DURATION_MS = 78;
+
+ private static final int CONTENT_OPACITY_ANIM_DELAY_MS = 93;
+ private static final int CONTENT_OPACITY_ANIM_DURATION_MS = 78;
+
+ private static final int BACKGROUND_OPACITY_ANIM_DELAY_MS = 172;
+ private static final int BACKGROUND_OPACITY_ANIM_DURATION_MS = 78;
+
+ /** Animation fraction threshold for content alpha animation when stack collapse should begin */
+ private static final float STACK_COLLAPSE_THRESHOLD = 0.5f;
+
+ private static final FloatPropertyCompat<ExpandedViewAnimationControllerImpl>
+ COLLAPSE_HEIGHT_PROPERTY =
+ new FloatPropertyCompat<ExpandedViewAnimationControllerImpl>("CollapseSpring") {
+ @Override
+ public float getValue(ExpandedViewAnimationControllerImpl controller) {
+ return controller.getCollapsedAmount();
+ }
+
+ @Override
+ public void setValue(ExpandedViewAnimationControllerImpl controller,
+ float value) {
+ controller.setCollapsedAmount(value);
+ }
+ };
+
+ private final int mMinFlingVelocity;
+ private float mSwipeUpVelocity;
+ private float mSwipeDownVelocity;
+ private final BubblePositioner mPositioner;
+ private final FlingAnimationUtils mFlingAnimationUtils;
+ private int mDraggedAmount;
+ private float mCollapsedAmount;
+ @Nullable
+ private BubbleExpandedView mExpandedView;
+ @Nullable
+ private AnimatorSet mCollapseAnimation;
+ private boolean mNotifiedAboutThreshold;
+ private SpringAnimation mBackToExpandedAnimation;
+ @Nullable
+ private ObjectAnimator mBottomClipAnim;
+
+ public ExpandedViewAnimationControllerImpl(Context context, BubblePositioner positioner) {
+ mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(),
+ COLLAPSE_DURATION_MS / 1000f);
+ mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
+ mPositioner = positioner;
+ }
+
+ private static void adjustAnimatorSetDuration(AnimatorSet animatorSet,
+ float durationAdjustment) {
+ for (Animator animator : animatorSet.getChildAnimations()) {
+ animator.setStartDelay((long) (animator.getStartDelay() * durationAdjustment));
+ animator.setDuration((long) (animator.getDuration() * durationAdjustment));
+ }
+ }
+
+ @Override
+ public void setExpandedView(BubbleExpandedView expandedView) {
+ if (mExpandedView != null) {
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "updating expandedView, resetting previous");
+ }
+ if (mCollapseAnimation != null) {
+ mCollapseAnimation.cancel();
+ }
+ if (mBackToExpandedAnimation != null) {
+ mBackToExpandedAnimation.cancel();
+ }
+ reset();
+ }
+ mExpandedView = expandedView;
+ }
+
+ @Override
+ public void updateDrag(float distance) {
+ if (mExpandedView != null) {
+ mDraggedAmount = OverScroll.dampedScroll(distance, mExpandedView.getContentHeight());
+
+ if (DEBUG_COLLAPSE_ANIMATOR && DEBUG_EXPANDED_VIEW_DRAGGING) {
+ Log.d(TAG, "updateDrag: distance=" + distance + " dragged=" + mDraggedAmount);
+ }
+
+ setCollapsedAmount(mDraggedAmount);
+
+ if (!mNotifiedAboutThreshold && isPastCollapseThreshold()) {
+ mNotifiedAboutThreshold = true;
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "notifying over collapse threshold");
+ }
+ vibrateIfEnabled();
+ }
+ }
+ }
+
+ @Override
+ public void setSwipeVelocity(float velocity) {
+ if (velocity < 0) {
+ mSwipeUpVelocity = Math.abs(velocity);
+ mSwipeDownVelocity = 0;
+ } else {
+ mSwipeUpVelocity = 0;
+ mSwipeDownVelocity = velocity;
+ }
+ }
+
+ @Override
+ public boolean shouldCollapse() {
+ if (mSwipeDownVelocity > mMinFlingVelocity) {
+ // Swipe velocity is positive and over fling velocity.
+ // This is a swipe down, always reset to expanded state, regardless of dragged amount.
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG,
+ "not collapsing expanded view, swipe down velocity: " + mSwipeDownVelocity
+ + " minV: " + mMinFlingVelocity);
+ }
+ return false;
+ }
+
+ if (mSwipeUpVelocity > mMinFlingVelocity) {
+ // Swiping up and over fling velocity, collapse the view.
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG,
+ "collapse expanded view, swipe up velocity: " + mSwipeUpVelocity + " minV: "
+ + mMinFlingVelocity);
+ }
+ return true;
+ }
+
+ if (isPastCollapseThreshold()) {
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "collapse expanded view, past threshold, dragged: " + mDraggedAmount);
+ }
+ return true;
+ }
+
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "not collapsing expanded view");
+ }
+
+ return false;
+ }
+
+ @Override
+ public void animateCollapse(Runnable startStackCollapse, Runnable after) {
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG,
+ "expandedView animate collapse swipeVel=" + mSwipeUpVelocity + " minFlingVel="
+ + mMinFlingVelocity);
+ }
+ if (mExpandedView != null) {
+ // Mark it as animating immediately to avoid updates to the view before animation starts
+ mExpandedView.setAnimating(true);
+
+ if (mCollapseAnimation != null) {
+ mCollapseAnimation.cancel();
+ }
+ mCollapseAnimation = createCollapseAnimation(mExpandedView, startStackCollapse, after);
+
+ if (mSwipeUpVelocity >= mMinFlingVelocity) {
+ int contentHeight = mExpandedView.getContentHeight();
+
+ // Use a temp animator to get adjusted duration value for swipe.
+ // This new value will be used to adjust animation times proportionally in the
+ // animator set. If we adjust animator set duration directly, all child animations
+ // will get the same animation time.
+ ValueAnimator tempAnimator = new ValueAnimator();
+ mFlingAnimationUtils.applyDismissing(tempAnimator, mCollapsedAmount, contentHeight,
+ mSwipeUpVelocity, (contentHeight - mCollapsedAmount));
+
+ float durationAdjustment =
+ (float) tempAnimator.getDuration() / COLLAPSE_DURATION_MS;
+
+ adjustAnimatorSetDuration(mCollapseAnimation, durationAdjustment);
+ mCollapseAnimation.setInterpolator(tempAnimator.getInterpolator());
+ }
+ mCollapseAnimation.start();
+ }
+ }
+
+ @Override
+ public void animateBackToExpanded() {
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "expandedView animate back to expanded");
+ }
+ BubbleExpandedView expandedView = mExpandedView;
+ if (expandedView == null) {
+ return;
+ }
+
+ expandedView.setAnimating(true);
+
+ mBackToExpandedAnimation = new SpringAnimation(this, COLLAPSE_HEIGHT_PROPERTY);
+ mBackToExpandedAnimation.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ );
+ mBackToExpandedAnimation.addEndListener(new OneTimeEndListener() {
+ @Override
+ public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+ float velocity) {
+ super.onAnimationEnd(animation, canceled, value, velocity);
+ mNotifiedAboutThreshold = false;
+ mBackToExpandedAnimation = null;
+ expandedView.setAnimating(false);
+ }
+ });
+ mBackToExpandedAnimation.setStartValue(mCollapsedAmount);
+ mBackToExpandedAnimation.animateToFinalPosition(0);
+ }
+
+ @Override
+ public void animateForImeVisibilityChange(boolean visible) {
+ if (mExpandedView != null) {
+ if (mBottomClipAnim != null) {
+ mBottomClipAnim.cancel();
+ }
+ int clip = 0;
+ if (visible) {
+ // Clip the expanded view at the top of the IME view
+ clip = mExpandedView.getContentBottomOnScreen() - mPositioner.getImeTop();
+ // Don't allow negative clip value
+ clip = Math.max(clip, 0);
+ }
+ mBottomClipAnim = ObjectAnimator.ofInt(mExpandedView, BOTTOM_CLIP_PROPERTY, clip);
+ mBottomClipAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBottomClipAnim = null;
+ }
+ });
+ mBottomClipAnim.start();
+ }
+ }
+
+ @Override
+ public void reset() {
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "reset expandedView collapsed state");
+ }
+ if (mExpandedView == null) {
+ return;
+ }
+ mExpandedView.setAnimating(false);
+
+ if (mCollapseAnimation != null) {
+ mCollapseAnimation.cancel();
+ }
+ if (mBackToExpandedAnimation != null) {
+ mBackToExpandedAnimation.cancel();
+ }
+ mExpandedView.setContentAlpha(1);
+ mExpandedView.setBackgroundAlpha(1);
+ mExpandedView.setManageButtonAlpha(1);
+ setCollapsedAmount(0);
+ mExpandedView.setBottomClip(0);
+ mExpandedView.movePointerBy(0, 0);
+ mCollapsedAmount = 0;
+ mDraggedAmount = 0;
+ mSwipeUpVelocity = 0;
+ mSwipeDownVelocity = 0;
+ mNotifiedAboutThreshold = false;
+ }
+
+ private float getCollapsedAmount() {
+ return mCollapsedAmount;
+ }
+
+ private void setCollapsedAmount(float collapsed) {
+ if (mCollapsedAmount != collapsed) {
+ float previous = mCollapsedAmount;
+ mCollapsedAmount = collapsed;
+
+ if (mExpandedView != null) {
+ if (previous == 0) {
+ // View was not collapsed before. Apply z order change
+ mExpandedView.setSurfaceZOrderedOnTop(true);
+ mExpandedView.setAnimating(true);
+ }
+
+ mExpandedView.setTopClip((int) mCollapsedAmount);
+ // Move up with translationY. Use negative collapsed value
+ mExpandedView.setContentTranslationY(-mCollapsedAmount);
+ mExpandedView.setManageButtonTranslationY(-mCollapsedAmount);
+
+ if (mCollapsedAmount == 0) {
+ // View is no longer collapsed. Revert z order change
+ mExpandedView.setSurfaceZOrderedOnTop(false);
+ mExpandedView.setAnimating(false);
+ }
+ }
+ }
+ }
+
+ private boolean isPastCollapseThreshold() {
+ if (mExpandedView != null) {
+ return mDraggedAmount > mExpandedView.getContentHeight() * COLLAPSE_THRESHOLD;
+ }
+ return false;
+ }
+
+ private AnimatorSet createCollapseAnimation(BubbleExpandedView expandedView,
+ Runnable startStackCollapse, Runnable after) {
+ List<Animator> animatorList = new ArrayList<>();
+ animatorList.add(createHeightAnimation(expandedView));
+ animatorList.add(createManageButtonAnimation());
+ ObjectAnimator contentAlphaAnimation = createContentAlphaAnimation();
+ final boolean[] notified = {false};
+ contentAlphaAnimation.addUpdateListener(animation -> {
+ if (!notified[0] && animation.getAnimatedFraction() > STACK_COLLAPSE_THRESHOLD) {
+ notified[0] = true;
+ // Notify bubbles that they can start moving back to the collapsed position
+ startStackCollapse.run();
+ }
+ });
+ animatorList.add(contentAlphaAnimation);
+ animatorList.add(createBackgroundAlphaAnimation());
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ after.run();
+ }
+ });
+ animatorSet.playTogether(animatorList);
+ return animatorSet;
+ }
+
+ private ValueAnimator createHeightAnimation(BubbleExpandedView expandedView) {
+ ValueAnimator animator = ValueAnimator.ofInt((int) mCollapsedAmount,
+ expandedView.getContentHeight());
+ animator.setInterpolator(Interpolators.EMPHASIZED_ACCELERATE);
+ animator.setDuration(COLLAPSE_DURATION_MS);
+ animator.addUpdateListener(anim -> setCollapsedAmount((int) anim.getAnimatedValue()));
+ return animator;
+ }
+
+ private ObjectAnimator createManageButtonAnimation() {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, MANAGE_BUTTON_ALPHA, 0f);
+ animator.setDuration(MANAGE_BUTTON_ANIM_DURATION_MS);
+ animator.setInterpolator(Interpolators.LINEAR);
+ return animator;
+ }
+
+ private ObjectAnimator createContentAlphaAnimation() {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, CONTENT_ALPHA, 0f);
+ animator.setDuration(CONTENT_OPACITY_ANIM_DURATION_MS);
+ animator.setInterpolator(Interpolators.LINEAR);
+ animator.setStartDelay(CONTENT_OPACITY_ANIM_DELAY_MS);
+ return animator;
+ }
+
+ private ObjectAnimator createBackgroundAlphaAnimation() {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, BACKGROUND_ALPHA, 0f);
+ animator.setDuration(BACKGROUND_OPACITY_ANIM_DURATION_MS);
+ animator.setInterpolator(Interpolators.LINEAR);
+ animator.setStartDelay(BACKGROUND_OPACITY_ANIM_DELAY_MS);
+ return animator;
+ }
+
+ @SuppressLint("MissingPermission")
+ private void vibrateIfEnabled() {
+ if (mExpandedView != null) {
+ mExpandedView.performHapticFeedback(HapticFeedbackConstants.DRAG_CROSSING);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java
new file mode 100644
index 000000000000..bb8a3aaaf551
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java
@@ -0,0 +1,57 @@
+/*
+ * 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.bubbles.animation;
+
+import com.android.wm.shell.bubbles.BubbleExpandedView;
+
+/**
+ * Stub implementation {@link ExpandedViewAnimationController} that does not animate the
+ * {@link BubbleExpandedView}
+ */
+public class ExpandedViewAnimationControllerStub implements ExpandedViewAnimationController {
+ @Override
+ public void setExpandedView(BubbleExpandedView expandedView) {
+ }
+
+ @Override
+ public void updateDrag(float distance) {
+ }
+
+ @Override
+ public void setSwipeVelocity(float velocity) {
+ }
+
+ @Override
+ public boolean shouldCollapse() {
+ return false;
+ }
+
+ @Override
+ public void animateCollapse(Runnable startStackCollapse, Runnable after) {
+ }
+
+ @Override
+ public void animateBackToExpanded() {
+ }
+
+ @Override
+ public void animateForImeVisibilityChange(boolean visible) {
+ }
+
+ @Override
+ public void reset() {
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OverScroll.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OverScroll.java
new file mode 100644
index 000000000000..d4e76ed0282e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OverScroll.java
@@ -0,0 +1,57 @@
+/*
+ * 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.bubbles.animation;
+
+/**
+ * Utility methods for overscroll damping and related effect.
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/OverScroll.java
+ */
+public class OverScroll {
+
+ private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
+
+ /**
+ * This curve determines how the effect of scrolling over the limits of the page diminishes
+ * as the user pulls further and further from the bounds
+ *
+ * @param f The percentage of how much the user has overscrolled.
+ * @return A transformed percentage based on the influence curve.
+ */
+ private static float overScrollInfluenceCurve(float f) {
+ f -= 1.0f;
+ return f * f * f + 1.0f;
+ }
+
+ /**
+ * @param amount The original amount overscrolled.
+ * @param max The maximum amount that the View can overscroll.
+ * @return The dampened overscroll amount.
+ */
+ public static int dampedScroll(float amount, int max) {
+ if (Float.compare(amount, 0) == 0) return 0;
+
+ float f = amount / max;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ return Math.round(OVERSCROLL_DAMP_FACTOR * f * max);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index c32733d4f73c..28c7367662a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -16,11 +16,13 @@
package com.android.wm.shell.common;
+import android.annotation.Nullable;
import android.os.RemoteException;
import android.util.Slog;
-import android.view.IDisplayWindowRotationCallback;
-import android.view.IDisplayWindowRotationController;
+import android.view.IDisplayChangeWindowCallback;
+import android.view.IDisplayChangeWindowController;
import android.view.IWindowManager;
+import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
@@ -40,17 +42,17 @@ public class DisplayChangeController {
private final ShellExecutor mMainExecutor;
private final IWindowManager mWmService;
- private final IDisplayWindowRotationController mControllerImpl;
+ private final IDisplayChangeWindowController mControllerImpl;
- private final CopyOnWriteArrayList<OnDisplayChangingListener> mRotationListener =
+ private final CopyOnWriteArrayList<OnDisplayChangingListener> mDisplayChangeListener =
new CopyOnWriteArrayList<>();
public DisplayChangeController(IWindowManager wmService, ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
mWmService = wmService;
- mControllerImpl = new DisplayWindowRotationControllerImpl();
+ mControllerImpl = new DisplayChangeWindowControllerImpl();
try {
- mWmService.setDisplayWindowRotationController(mControllerImpl);
+ mWmService.setDisplayChangeWindowController(mControllerImpl);
} catch (RemoteException e) {
throw new RuntimeException("Unable to register rotation controller");
}
@@ -59,63 +61,64 @@ public class DisplayChangeController {
/**
* Adds a display rotation controller.
*/
- public void addRotationListener(OnDisplayChangingListener listener) {
- mRotationListener.add(listener);
+ public void addDisplayChangeListener(OnDisplayChangingListener listener) {
+ mDisplayChangeListener.add(listener);
}
/**
* Removes a display rotation controller.
*/
- public void removeRotationListener(OnDisplayChangingListener listener) {
- mRotationListener.remove(listener);
+ public void removeDisplayChangeListener(OnDisplayChangingListener listener) {
+ mDisplayChangeListener.remove(listener);
}
- /** Query all listeners for changes that should happen on rotation. */
- public void dispatchOnRotateDisplay(WindowContainerTransaction outWct, int displayId,
- final int fromRotation, final int toRotation) {
- for (OnDisplayChangingListener c : mRotationListener) {
- c.onRotateDisplay(displayId, fromRotation, toRotation, outWct);
+ /** Query all listeners for changes that should happen on display change. */
+ public void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId,
+ int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo) {
+ for (OnDisplayChangingListener c : mDisplayChangeListener) {
+ c.onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, outWct);
}
}
- private void onRotateDisplay(int displayId, final int fromRotation, final int toRotation,
- IDisplayWindowRotationCallback callback) {
+ private void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) {
WindowContainerTransaction t = new WindowContainerTransaction();
- dispatchOnRotateDisplay(t, displayId, fromRotation, toRotation);
+ dispatchOnDisplayChange(t, displayId, fromRotation, toRotation, newDisplayAreaInfo);
try {
- callback.continueRotateDisplay(toRotation, t);
+ callback.continueDisplayChange(t);
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to continue rotation", e);
+ Slog.e(TAG, "Failed to continue handling display change", e);
}
}
@BinderThread
- private class DisplayWindowRotationControllerImpl
- extends IDisplayWindowRotationController.Stub {
+ private class DisplayChangeWindowControllerImpl
+ extends IDisplayChangeWindowController.Stub {
@Override
- public void onRotateDisplay(int displayId, final int fromRotation,
- final int toRotation, IDisplayWindowRotationCallback callback) {
- mMainExecutor.execute(() -> {
- DisplayChangeController.this.onRotateDisplay(displayId, fromRotation, toRotation,
- callback);
- });
+ public void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) {
+ mMainExecutor.execute(() -> DisplayChangeController.this
+ .onDisplayChange(displayId, fromRotation, toRotation,
+ newDisplayAreaInfo, callback));
}
}
/**
* Give a listener a chance to queue up configuration changes to execute as part of a
- * display rotation. The contents of {@link #onRotateDisplay} must run synchronously.
+ * display rotation. The contents of {@link #onDisplayChange} must run synchronously.
*/
@ShellMainThread
public interface OnDisplayChangingListener {
/**
- * Called before the display is rotated. Contents of this method must run synchronously.
- * @param displayId Id of display that is rotating.
- * @param fromRotation starting rotation of the display.
- * @param toRotation target rotation of the display (after rotating).
+ * Called before the display size has changed.
+ * Contents of this method must run synchronously.
+ * @param displayId display id of the display that is under the change
+ * @param fromRotation rotation before the change
+ * @param toRotation rotation after the change
+ * @param newDisplayAreaInfo display area info after applying the update
* @param t A task transaction to populate.
*/
- void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- WindowContainerTransaction t);
+ void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 4ba32e93fb3d..764936cceb01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -156,14 +156,14 @@ public class DisplayController {
* Adds a display rotation controller.
*/
public void addDisplayChangingController(OnDisplayChangingListener controller) {
- mChangeController.addRotationListener(controller);
+ mChangeController.addDisplayChangeListener(controller);
}
/**
* Removes a display rotation controller.
*/
public void removeDisplayChangingController(OnDisplayChangingListener controller) {
- mChangeController.removeRotationListener(controller);
+ mChangeController.removeDisplayChangeListener(controller);
}
private void onDisplayAdded(int displayId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
index fd3aa05cfc06..ec344d345139 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
@@ -18,7 +18,9 @@ package com.android.wm.shell.common;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.text.TextUtils;
+import android.view.SurfaceControl;
import android.view.View;
import com.android.internal.jank.InteractionJankMonitor;
@@ -44,6 +46,24 @@ public class InteractionJankMonitorUtils {
}
/**
+ * Begin a trace session.
+ *
+ * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param context the context
+ * @param surface the surface to trace
+ * @param tag the tag to distinguish different flow of same type CUJ.
+ */
+ public static void beginTracing(@InteractionJankMonitor.CujType int cujType,
+ @NonNull Context context, @NonNull SurfaceControl surface, @Nullable String tag) {
+ final InteractionJankMonitor.Configuration.Builder builder =
+ InteractionJankMonitor.Configuration.Builder.withSurface(cujType, context, surface);
+ if (!TextUtils.isEmpty(tag)) {
+ builder.setTag(tag);
+ }
+ InteractionJankMonitor.getInstance().begin(builder);
+ }
+
+ /**
* End a trace session.
*
* @param cujType the specific {@link InteractionJankMonitor.CujType}.
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 6305959bb6ac..5ebe384d84b7 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
@@ -286,6 +286,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
setTouching();
mStartPos = touchPos;
mMoving = false;
+ mSplitLayout.onStartDragging();
break;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
@@ -301,7 +302,10 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
releaseTouching();
- if (!mMoving) break;
+ if (!mMoving) {
+ mSplitLayout.onDraggingCancelled();
+ break;
+ }
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000 /* units */);
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 c94455d9151a..172418057b86 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,6 +24,7 @@ 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_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.animation.Interpolators.DIM_INTERPOLATOR;
@@ -55,7 +56,6 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DockedDividerUtils;
import com.android.wm.shell.R;
@@ -178,6 +178,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return outBounds;
}
+ /** Gets root bounds of the whole split layout */
+ public Rect getRootBounds() {
+ return new Rect(mRootBounds);
+ }
+
/** Gets bounds of divider window with screen based coordinate. */
public Rect getDividerBounds() {
return new Rect(mDividerBounds);
@@ -407,6 +412,15 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
}
+ void onStartDragging() {
+ InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_RESIZE, mContext,
+ getDividerLeash(), null /* tag */);
+ }
+
+ void onDraggingCancelled() {
+ InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_RESIZE);
+ }
+
void onDoubleTappedDivider() {
mSplitLayoutHandler.onDoubleTappedDivider();
}
@@ -438,10 +452,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
if (from == to) {
// No animation run, still callback to stop resizing.
mSplitLayoutHandler.onLayoutSizeChanged(this);
+ InteractionJankMonitorUtils.endTracing(
+ CUJ_SPLIT_SCREEN_RESIZE);
return;
}
- InteractionJankMonitorUtils.beginTracing(InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE,
- mSplitWindowManager.getDividerView(), "Divider fling");
ValueAnimator animator = ValueAnimator
.ofInt(from, to)
.setDuration(250);
@@ -455,7 +469,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
flingFinishedCallback.run();
}
InteractionJankMonitorUtils.endTracing(
- InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE);
+ CUJ_SPLIT_SCREEN_RESIZE);
}
@Override
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 db6131a17114..2ea111b113d8 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
@@ -38,8 +38,6 @@ import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.TaskViewFactoryController;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.apppairs.AppPairs;
-import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.back.BackAnimationController;
import com.android.wm.shell.bubbles.BubbleController;
@@ -66,12 +64,9 @@ import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -92,6 +87,7 @@ import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import java.util.Optional;
@@ -180,9 +176,11 @@ public abstract class WMShellBaseModule {
static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
Context context,
CompatUIController compatUI,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasksOptional
) {
- return new ShellTaskOrganizer(mainExecutor, context, compatUI, recentTasksOptional);
+ return new ShellTaskOrganizer(mainExecutor, context, compatUI, unfoldAnimationController,
+ recentTasksOptional);
}
@WMSingleton
@@ -194,10 +192,12 @@ public abstract class WMShellBaseModule {
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasksOptional
) {
return new KidsModeTaskOrganizer(mainExecutor, mainHandler, context, syncTransactionQueue,
- displayController, displayInsetsController, recentTasksOptional);
+ displayController, displayInsetsController, unfoldAnimationController,
+ recentTasksOptional);
}
@WMSingleton
@@ -294,13 +294,11 @@ public abstract class WMShellBaseModule {
static FullscreenTaskListener provideFullscreenTaskListener(
@DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
SyncTransactionQueue syncQueue,
- Optional<FullscreenUnfoldController> optionalFullscreenUnfoldController,
Optional<RecentTasksController> recentTasksOptional) {
if (fullscreenTaskListener.isPresent()) {
return fullscreenTaskListener.get();
} else {
- return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
- recentTasksOptional);
+ return new FullscreenTaskListener(syncQueue, recentTasksOptional);
}
}
@@ -314,31 +312,33 @@ public abstract class WMShellBaseModule {
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@BindsOptionalOf
@DynamicOverride
- abstract FullscreenUnfoldController optionalFullscreenUnfoldController();
+ abstract UnfoldAnimationController optionalUnfoldController();
@WMSingleton
@Provides
- static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
- @DynamicOverride Optional<FullscreenUnfoldController> fullscreenUnfoldController,
+ static Optional<UnfoldAnimationController> provideUnfoldController(
+ @DynamicOverride Lazy<Optional<UnfoldAnimationController>>
+ fullscreenUnfoldController,
Optional<ShellUnfoldProgressProvider> progressProvider) {
if (progressProvider.isPresent()
&& progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) {
- return fullscreenUnfoldController;
+ return fullscreenUnfoldController.get();
}
return Optional.empty();
}
+ @BindsOptionalOf
+ @DynamicOverride
+ abstract UnfoldTransitionHandler optionalUnfoldTransitionHandler();
+
@WMSingleton
@Provides
static Optional<UnfoldTransitionHandler> provideUnfoldTransitionHandler(
Optional<ShellUnfoldProgressProvider> progressProvider,
- TransactionPool transactionPool,
- Transitions transitions,
- @ShellMainThread ShellExecutor executor) {
- if (progressProvider.isPresent()) {
- return Optional.of(
- new UnfoldTransitionHandler(progressProvider.get(), transactionPool, executor,
- transitions));
+ @DynamicOverride Lazy<Optional<UnfoldTransitionHandler>> handler) {
+ if (progressProvider.isPresent()
+ && progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) {
+ return handler.get();
}
return Optional.empty();
}
@@ -350,12 +350,12 @@ public abstract class WMShellBaseModule {
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@BindsOptionalOf
@DynamicOverride
- abstract FreeformTaskListener optionalFreeformTaskListener();
+ abstract FreeformTaskListener<?> optionalFreeformTaskListener();
@WMSingleton
@Provides
- static Optional<FreeformTaskListener> provideFreeformTaskListener(
- @DynamicOverride Optional<FreeformTaskListener> freeformTaskListener,
+ static Optional<FreeformTaskListener<?>> provideFreeformTaskListener(
+ @DynamicOverride Optional<FreeformTaskListener<?>> freeformTaskListener,
Context context) {
if (FreeformTaskListener.isFreeformEnabled(context)) {
return freeformTaskListener;
@@ -561,29 +561,6 @@ public abstract class WMShellBaseModule {
return Optional.empty();
}
- // Legacy split (optional feature)
-
- @WMSingleton
- @Provides
- static Optional<LegacySplitScreen> provideLegacySplitScreen(
- Optional<LegacySplitScreenController> splitScreenController) {
- return splitScreenController.map((controller) -> controller.asLegacySplitScreen());
- }
-
- @BindsOptionalOf
- abstract LegacySplitScreenController optionalLegacySplitScreenController();
-
- // App Pairs (optional feature)
-
- @WMSingleton
- @Provides
- static Optional<AppPairs> provideAppPairs(Optional<AppPairsController> appPairsController) {
- return appPairsController.map((controller) -> controller.asAppPairs());
- }
-
- @BindsOptionalOf
- abstract AppPairsController optionalAppPairs();
-
//
// Starting window
//
@@ -664,12 +641,11 @@ public abstract class WMShellBaseModule {
KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairsController> appPairsOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
- Optional<FullscreenUnfoldController> appUnfoldTransitionController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
- Optional<FreeformTaskListener> freeformTaskListener,
+ Optional<FreeformTaskListener<?>> freeformTaskListener,
Optional<RecentTasksController> recentTasksOptional,
Transitions transitions,
StartingWindowController startingWindow,
@@ -682,10 +658,9 @@ public abstract class WMShellBaseModule {
kidsModeTaskOrganizer,
bubblesOptional,
splitScreenOptional,
- appPairsOptional,
pipTouchHandlerOptional,
fullscreenTaskListener,
- appUnfoldTransitionController,
+ unfoldAnimationController,
unfoldTransitionHandler,
freeformTaskListener,
recentTasksOptional,
@@ -709,17 +684,15 @@ public abstract class WMShellBaseModule {
static ShellCommandHandlerImpl provideShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
Optional<OneHandedController> oneHandedOptional,
Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<AppPairsController> appPairsOptional,
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor) {
return new ShellCommandHandlerImpl(shellTaskOrganizer, kidsModeTaskOrganizer,
- legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
- hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor);
+ splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
+ recentTasksOptional, mainExecutor);
}
@WMSingleton
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 b3799e2cf8d9..1e369899e354 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,7 +16,6 @@
package com.android.wm.shell.dagger;
-import android.animation.AnimationHandler;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.os.Handler;
@@ -31,7 +30,6 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
@@ -43,13 +41,10 @@ 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.TransactionPool;
-import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
-import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
@@ -67,19 +62,29 @@ import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipController;
+import com.android.wm.shell.pip.phone.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.splitscreen.StageTaskUnfoldController;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
-
+import com.android.wm.shell.unfold.UnfoldTransitionHandler;
+import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
+import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
+import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
+import com.android.wm.shell.unfold.qualifier.UnfoldTransition;
+import com.android.wm.shell.unfold.qualifier.UnfoldShellTransition;
+import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+
+import java.util.ArrayList;
+import java.util.List;
import java.util.Optional;
-import javax.inject.Provider;
-
+import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -93,7 +98,7 @@ import dagger.Provides;
* dependencies should go into {@link WMShellBaseModule}.
*/
@Module(includes = WMShellBaseModule.class)
-public class WMShellModule {
+public abstract class WMShellModule {
//
// Bubbles
@@ -129,15 +134,36 @@ public class WMShellModule {
}
//
+ // Window decoration
+ //
+
+ @WMSingleton
+ @Provides
+ static WindowDecorViewModel<?> provideWindowDecorViewModel(
+ Context context,
+ @ShellMainThread Handler mainHandler,
+ ShellTaskOrganizer taskOrganizer,
+ DisplayController displayController,
+ SyncTransactionQueue syncQueue) {
+ return new CaptionWindowDecorViewModel(
+ context,
+ mainHandler,
+ taskOrganizer,
+ displayController,
+ syncQueue);
+ }
+
+ //
// Freeform
//
@WMSingleton
@Provides
@DynamicOverride
- static FreeformTaskListener provideFreeformTaskListener(
+ static FreeformTaskListener<?> provideFreeformTaskListener(
+ WindowDecorViewModel<?> windowDecorViewModel,
SyncTransactionQueue syncQueue) {
- return new FreeformTaskListener(syncQueue);
+ return new FreeformTaskListener<>(windowDecorViewModel, syncQueue);
}
//
@@ -174,37 +200,11 @@ public class WMShellModule {
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, IconProvider iconProvider,
- Optional<RecentTasksController> recentTasks,
- Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
+ Optional<RecentTasksController> recentTasks) {
return new SplitScreenController(shellTaskOrganizer, syncQueue, context,
rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
- recentTasks, stageTaskUnfoldControllerProvider);
- }
-
- @WMSingleton
- @Provides
- static LegacySplitScreenController provideLegacySplitScreen(Context context,
- DisplayController displayController, SystemWindows systemWindows,
- DisplayImeController displayImeController, TransactionPool transactionPool,
- ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
- TaskStackListenerImpl taskStackListener, Transitions transitions,
- @ShellMainThread ShellExecutor mainExecutor,
- @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
- return new LegacySplitScreenController(context, displayController, systemWindows,
- displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
- taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
- }
-
- @WMSingleton
- @Provides
- static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue, DisplayController displayController,
- @ShellMainThread ShellExecutor mainExecutor,
- DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController) {
- return new AppPairsController(shellTaskOrganizer, syncQueue, displayController,
- mainExecutor, displayImeController, displayInsetsController);
+ recentTasks);
}
//
@@ -215,7 +215,8 @@ public class WMShellModule {
@Provides
static Optional<Pip> providePip(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -224,7 +225,8 @@ public class WMShellModule {
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(context, displayController,
- pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState,
+ pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
+ pipMotionHelper,
pipMediaController, phonePipMenuController, pipTaskOrganizer,
pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor));
@@ -244,6 +246,12 @@ public class WMShellModule {
@WMSingleton
@Provides
+ static PipKeepClearAlgorithm providePipKeepClearAlgorithm() {
+ return new PipKeepClearAlgorithm();
+ }
+
+ @WMSingleton
+ @Provides
static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
@@ -351,40 +359,77 @@ public class WMShellModule {
//
// Unfold transition
//
-
@WMSingleton
@Provides
@DynamicOverride
- static FullscreenUnfoldController provideFullscreenUnfoldController(
- Context context,
+ static UnfoldAnimationController provideUnfoldAnimationController(
Optional<ShellUnfoldProgressProvider> progressProvider,
- Lazy<UnfoldBackgroundController> unfoldBackgroundController,
- DisplayInsetsController displayInsetsController,
+ TransactionPool transactionPool,
+ @UnfoldTransition SplitTaskUnfoldAnimator splitAnimator,
+ FullscreenUnfoldTaskAnimator fullscreenAnimator,
+ Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new FullscreenUnfoldController(context, mainExecutor,
- unfoldBackgroundController.get(), progressProvider.get(),
+ final List<UnfoldTaskAnimator> animators = new ArrayList<>();
+ animators.add(splitAnimator);
+ animators.add(fullscreenAnimator);
+
+ return new UnfoldAnimationController(
+ transactionPool,
+ progressProvider.get(),
+ animators,
+ unfoldTransitionHandler,
+ mainExecutor
+ );
+ }
+
+
+ @Provides
+ static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator(
+ Context context,
+ UnfoldBackgroundController unfoldBackgroundController,
+ DisplayInsetsController displayInsetsController
+ ) {
+ return new FullscreenUnfoldTaskAnimator(context, unfoldBackgroundController,
displayInsetsController);
}
@Provides
- static Optional<StageTaskUnfoldController> provideStageTaskUnfoldController(
- Optional<ShellUnfoldProgressProvider> progressProvider,
+ static SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimatorBase(
Context context,
- TransactionPool transactionPool,
- Lazy<UnfoldBackgroundController> unfoldBackgroundController,
- DisplayInsetsController displayInsetsController,
- @ShellMainThread ShellExecutor mainExecutor
+ UnfoldBackgroundController backgroundController,
+ @ShellMainThread ShellExecutor executor,
+ Lazy<Optional<SplitScreenController>> splitScreenOptional,
+ DisplayInsetsController displayInsetsController
) {
- return progressProvider.map(shellUnfoldTransitionProgressProvider ->
- new StageTaskUnfoldController(
- context,
- transactionPool,
- shellUnfoldTransitionProgressProvider,
- displayInsetsController,
- unfoldBackgroundController.get(),
- mainExecutor
- ));
+ return new SplitTaskUnfoldAnimator(context, executor, splitScreenOptional,
+ backgroundController, displayInsetsController);
+ }
+
+ @WMSingleton
+ @UnfoldShellTransition
+ @Binds
+ abstract SplitTaskUnfoldAnimator provideShellSplitTaskUnfoldAnimator(
+ SplitTaskUnfoldAnimator splitTaskUnfoldAnimator);
+
+ @WMSingleton
+ @UnfoldTransition
+ @Binds
+ abstract SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimator(
+ SplitTaskUnfoldAnimator splitTaskUnfoldAnimator);
+
+ @WMSingleton
+ @Provides
+ @DynamicOverride
+ static UnfoldTransitionHandler provideUnfoldTransitionHandler(
+ Optional<ShellUnfoldProgressProvider> progressProvider,
+ FullscreenUnfoldTaskAnimator animator,
+ @UnfoldShellTransition SplitTaskUnfoldAnimator unfoldAnimator,
+ TransactionPool transactionPool,
+ Transitions transitions,
+ @ShellMainThread ShellExecutor executor) {
+ return new UnfoldTransitionHandler(progressProvider.get(), animator,
+ unfoldAnimator, transactionPool, executor, transitions);
}
@WMSingleton
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 756831007c35..6373728344a4 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
@@ -67,6 +67,7 @@ import com.android.internal.logging.InstanceId;
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.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -75,6 +76,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* The policy for handling drag and drop operations to shell.
@@ -289,18 +291,15 @@ public class DragAndDropPolicy {
final ComponentName currentActivity;
if (!inSplitScreen) {
currentActivity = mSession.runningTaskInfo != null
- ? mSession.runningTaskInfo.baseActivity
+ ? mSession.runningTaskInfo.baseIntent.getComponent()
: null;
} else {
- final int nonReplacedSplitPosition = position == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT
- : SPLIT_POSITION_TOP_OR_LEFT;
- ActivityManager.RunningTaskInfo nonReplacedTaskInfo =
- mSplitScreen.getTaskInfo(nonReplacedSplitPosition);
- currentActivity = nonReplacedTaskInfo.baseActivity;
+ final ActivityManager.RunningTaskInfo nonReplacedTaskInfo =
+ mSplitScreen.getTaskInfo(SplitLayout.reversePosition(position));
+ currentActivity = nonReplacedTaskInfo.baseIntent.getComponent();
}
- if (currentActivity.equals(dragIntentActivity)) {
+ if (Objects.equals(currentActivity, dragIntentActivity)) {
// Only apply MULTIPLE_TASK if we are dragging the same activity
final Intent fillInIntent = new Intent();
fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index fef9be36a35f..692e6acb540c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -21,8 +21,6 @@ import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDO
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
@@ -32,26 +30,35 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.io.PrintWriter;
/**
* {@link ShellTaskOrganizer.TaskListener} for {@link
* ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
+ *
+ * @param <T> the type of window decoration instance
*/
-public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener<T extends AutoCloseable>
+ implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FreeformTaskListener";
+ private final WindowDecorViewModel<T> mWindowDecorationViewModel;
private final SyncTransactionQueue mSyncQueue;
- private final SparseArray<State> mTasks = new SparseArray<>();
+ private final SparseArray<State<T>> mTasks = new SparseArray<>();
- private static class State {
+ private static class State<T extends AutoCloseable> {
RunningTaskInfo mTaskInfo;
SurfaceControl mLeash;
+ T mWindowDecoration;
}
- public FreeformTaskListener(SyncTransactionQueue syncQueue) {
+ public FreeformTaskListener(
+ WindowDecorViewModel<T> windowDecorationViewModel,
+ SyncTransactionQueue syncQueue) {
+ mWindowDecorationViewModel = windowDecorationViewModel;
mSyncQueue = syncQueue;
}
@@ -62,23 +69,17 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
taskInfo.taskId);
- final State state = new State();
+ final State<T> state = new State<>();
state.mTaskInfo = taskInfo;
state.mLeash = leash;
+ state.mWindowDecoration =
+ mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash);
mTasks.put(taskInfo.taskId, state);
-
- final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
- mSyncQueue.runInSync(t -> {
- Point taskPosition = taskInfo.positionInParent;
- t.setPosition(leash, taskPosition.x, taskPosition.y)
- .setWindowCrop(leash, taskBounds.width(), taskBounds.height())
- .show(leash);
- });
}
@Override
public void onTaskVanished(RunningTaskInfo taskInfo) {
- State state = mTasks.get(taskInfo.taskId);
+ State<T> state = mTasks.get(taskInfo.taskId);
if (state == null) {
Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
return;
@@ -86,11 +87,17 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
+
+ try {
+ state.mWindowDecoration.close();
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to release window decoration.", e);
+ }
}
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- State state = mTasks.get(taskInfo.taskId);
+ State<T> state = mTasks.get(taskInfo.taskId);
if (state == null) {
throw new RuntimeException(
"Task info changed before appearing: #" + taskInfo.taskId);
@@ -98,15 +105,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
taskInfo.taskId);
state.mTaskInfo = taskInfo;
-
- final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
- final SurfaceControl leash = state.mLeash;
- mSyncQueue.runInSync(t -> {
- Point taskPosition = taskInfo.positionInParent;
- t.setPosition(leash, taskPosition.x, taskPosition.y)
- .setWindowCrop(leash, taskBounds.width(), taskBounds.height())
- .show(leash);
- });
+ mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 73e6cba43ec0..79e363bcdb41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -16,17 +16,13 @@
package com.android.wm.shell.fullscreen;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.TaskInfo;
import android.graphics.Point;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.view.SurfaceControl;
import androidx.annotation.NonNull;
@@ -48,22 +44,17 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FullscreenTaskListener";
private final SyncTransactionQueue mSyncQueue;
- private final FullscreenUnfoldController mFullscreenUnfoldController;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
- private final AnimatableTasksListener mAnimatableTasksListener = new AnimatableTasksListener();
- public FullscreenTaskListener(SyncTransactionQueue syncQueue,
- Optional<FullscreenUnfoldController> unfoldController) {
- this(syncQueue, unfoldController, Optional.empty());
+ public FullscreenTaskListener(SyncTransactionQueue syncQueue) {
+ this(syncQueue, Optional.empty());
}
public FullscreenTaskListener(SyncTransactionQueue syncQueue,
- Optional<FullscreenUnfoldController> unfoldController,
Optional<RecentTasksController> recentTasks) {
mSyncQueue = syncQueue;
- mFullscreenUnfoldController = unfoldController.orElse(null);
mRecentTasksOptional = recentTasks;
}
@@ -76,6 +67,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
taskInfo.taskId);
final Point positionInParent = taskInfo.positionInParent;
mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent));
+
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
mSyncQueue.runInSync(t -> {
// Reset several properties back to fullscreen (PiP, for example, leaves all these
@@ -87,7 +79,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
t.show(leash);
});
- mAnimatableTasksListener.onTaskAppeared(taskInfo);
updateRecentsForVisibleFullscreenTask(taskInfo);
}
@@ -95,7 +86,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
- mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
updateRecentsForVisibleFullscreenTask(taskInfo);
final TaskData data = mDataByTaskId.get(taskInfo.taskId);
@@ -115,7 +105,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
return;
}
- mAnimatableTasksListener.onTaskVanished(taskInfo);
mDataByTaskId.remove(taskInfo.taskId);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
@@ -173,65 +162,4 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
this.positionInParent = positionInParent;
}
}
-
- class AnimatableTasksListener {
- private final SparseBooleanArray mTaskIds = new SparseBooleanArray();
-
- public void onTaskAppeared(RunningTaskInfo taskInfo) {
- final boolean isApplicable = isAnimatable(taskInfo);
- if (isApplicable) {
- mTaskIds.put(taskInfo.taskId, true);
-
- if (mFullscreenUnfoldController != null) {
- SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface;
- mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash);
- }
- }
- }
-
- public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId);
- final boolean isApplicable = isAnimatable(taskInfo);
-
- if (isCurrentlyApplicable) {
- if (isApplicable) {
- // Still applicable, send update
- if (mFullscreenUnfoldController != null) {
- mFullscreenUnfoldController.onTaskInfoChanged(taskInfo);
- }
- } else {
- // Became inapplicable
- if (mFullscreenUnfoldController != null) {
- mFullscreenUnfoldController.onTaskVanished(taskInfo);
- }
- mTaskIds.put(taskInfo.taskId, false);
- }
- } else {
- if (isApplicable) {
- // Became applicable
- mTaskIds.put(taskInfo.taskId, true);
-
- if (mFullscreenUnfoldController != null) {
- SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface;
- mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash);
- }
- }
- }
- }
-
- public void onTaskVanished(RunningTaskInfo taskInfo) {
- final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId);
- if (isCurrentlyApplicable && mFullscreenUnfoldController != null) {
- mFullscreenUnfoldController.onTaskVanished(taskInfo);
- }
- mTaskIds.put(taskInfo.taskId, false);
- }
-
- private boolean isAnimatable(TaskInfo taskInfo) {
- // Filter all visible tasks that are not launcher tasks
- // We do not animate launcher as it handles the animation by itself
- return taskInfo != null && taskInfo.isVisible && taskInfo.getConfiguration()
- .windowConfiguration.getActivityType() != ACTIVITY_TYPE_HOME;
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index b4c87b6cbf95..2c8ba0970ccc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -51,6 +51,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import java.io.PrintWriter;
import java.util.List;
@@ -146,9 +147,11 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
KidsModeSettingsObserver kidsModeSettingsObserver) {
- super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null, recentTasks);
+ super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null,
+ unfoldAnimationController, recentTasks);
mContext = context;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
@@ -164,8 +167,9 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks) {
- super(mainExecutor, context, /* compatUI= */ null, recentTasks);
+ super(mainExecutor, context, /* compatUI= */ null, unfoldAnimationController, recentTasks);
mContext = context;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java
deleted file mode 100644
index aced072c8c71..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java
+++ /dev/null
@@ -1,418 +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.legacysplitscreen;
-
-import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
-import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.util.Slog;
-import android.view.Choreographer;
-import android.view.SurfaceControl;
-import android.window.TaskOrganizer;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
-
-class DividerImeController implements DisplayImeController.ImePositionProcessor {
- private static final String TAG = "DividerImeController";
- private static final boolean DEBUG = LegacySplitScreenController.DEBUG;
-
- private static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
-
- private final LegacySplitScreenTaskListener mSplits;
- private final TransactionPool mTransactionPool;
- private final ShellExecutor mMainExecutor;
- private final TaskOrganizer mTaskOrganizer;
-
- /**
- * These are the y positions of the top of the IME surface when it is hidden and when it is
- * shown respectively. These are NOT necessarily the top of the visible IME itself.
- */
- private int mHiddenTop = 0;
- private int mShownTop = 0;
-
- // The following are target states (what we are curretly animating towards).
- /**
- * {@code true} if, at the end of the animation, the split task positions should be
- * adjusted by height of the IME. This happens when the secondary split is the IME target.
- */
- private boolean mTargetAdjusted = false;
- /**
- * {@code true} if, at the end of the animation, the IME should be shown/visible
- * regardless of what has focus.
- */
- private boolean mTargetShown = false;
- private float mTargetPrimaryDim = 0.f;
- private float mTargetSecondaryDim = 0.f;
-
- // The following are the current (most recent) states set during animation
- /** {@code true} if the secondary split has IME focus. */
- private boolean mSecondaryHasFocus = false;
- /** The dimming currently applied to the primary/secondary splits. */
- private float mLastPrimaryDim = 0.f;
- private float mLastSecondaryDim = 0.f;
- /** The most recent y position of the top of the IME surface */
- private int mLastAdjustTop = -1;
-
- // The following are states reached last time an animation fully completed.
- /** {@code true} if the IME was shown/visible by the last-completed animation. */
- private boolean mImeWasShown = false;
- /** {@code true} if the split positions were adjusted by the last-completed animation. */
- private boolean mAdjusted = false;
-
- /**
- * When some aspect of split-screen needs to animate independent from the IME,
- * this will be non-null and control split animation.
- */
- @Nullable
- private ValueAnimator mAnimation = null;
-
- private boolean mPaused = true;
- private boolean mPausedTargetAdjusted = false;
-
- DividerImeController(LegacySplitScreenTaskListener splits, TransactionPool pool,
- ShellExecutor mainExecutor, TaskOrganizer taskOrganizer) {
- mSplits = splits;
- mTransactionPool = pool;
- mMainExecutor = mainExecutor;
- mTaskOrganizer = taskOrganizer;
- }
-
- private DividerView getView() {
- return mSplits.mSplitScreenController.getDividerView();
- }
-
- private LegacySplitDisplayLayout getLayout() {
- return mSplits.mSplitScreenController.getSplitLayout();
- }
-
- private boolean isDividerHidden() {
- final DividerView view = mSplits.mSplitScreenController.getDividerView();
- return view == null || view.isHidden();
- }
-
- private boolean getSecondaryHasFocus(int displayId) {
- WindowContainerToken imeSplit = mTaskOrganizer.getImeTarget(displayId);
- return imeSplit != null
- && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder());
- }
-
- void reset() {
- mPaused = true;
- mPausedTargetAdjusted = false;
- mAnimation = null;
- mAdjusted = mTargetAdjusted = false;
- mImeWasShown = mTargetShown = false;
- mTargetPrimaryDim = mTargetSecondaryDim = mLastPrimaryDim = mLastSecondaryDim = 0.f;
- mSecondaryHasFocus = false;
- mLastAdjustTop = -1;
- }
-
- private void updateDimTargets() {
- final boolean splitIsVisible = !getView().isHidden();
- mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
- ? ADJUSTED_NONFOCUS_DIM : 0.f;
- mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
- ? ADJUSTED_NONFOCUS_DIM : 0.f;
- }
-
-
- @Override
- public void onImeControlTargetChanged(int displayId, boolean controlling) {
- // Restore the split layout when wm-shell is not controlling IME insets anymore.
- if (!controlling && mTargetShown) {
- mPaused = false;
- mTargetAdjusted = mTargetShown = false;
- mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
- updateImeAdjustState(true /* force */);
- startAsyncAnimation();
- }
- }
-
- @Override
- @ImeAnimationFlags
- public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
- boolean imeShouldShow, boolean imeIsFloating, SurfaceControl.Transaction t) {
- if (isDividerHidden()) {
- return 0;
- }
- mHiddenTop = hiddenTop;
- mShownTop = shownTop;
- mTargetShown = imeShouldShow;
- mSecondaryHasFocus = getSecondaryHasFocus(displayId);
- final boolean targetAdjusted = imeShouldShow && mSecondaryHasFocus
- && !imeIsFloating && !getLayout().mDisplayLayout.isLandscape()
- && !mSplits.mSplitScreenController.isMinimized();
- if (mLastAdjustTop < 0) {
- mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
- } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
- if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) {
- // Check for an "interruption" of an existing animation. In this case, we
- // need to fake-flip the last-known state direction so that the animation
- // completes in the other direction.
- mAdjusted = mTargetAdjusted;
- } else if (targetAdjusted && mTargetAdjusted && mAdjusted) {
- // Already fully adjusted for IME, but IME height has changed; so, force-start
- // an async animation to the new IME height.
- mAdjusted = false;
- }
- }
- if (mPaused) {
- mPausedTargetAdjusted = targetAdjusted;
- if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState());
- return (targetAdjusted || mAdjusted) ? IME_ANIMATION_NO_ALPHA : 0;
- }
- mTargetAdjusted = targetAdjusted;
- updateDimTargets();
- if (DEBUG) Slog.d(TAG, " ime starting. " + dumpState());
- if (mAnimation != null || (mImeWasShown && imeShouldShow
- && mTargetAdjusted != mAdjusted)) {
- // We need to animate adjustment independently of the IME position, so
- // start our own animation to drive adjustment. This happens when a
- // different split's editor has gained focus while the IME is still visible.
- startAsyncAnimation();
- }
- updateImeAdjustState();
-
- return (mTargetAdjusted || mAdjusted) ? IME_ANIMATION_NO_ALPHA : 0;
- }
-
- private void updateImeAdjustState() {
- updateImeAdjustState(false /* force */);
- }
-
- private void updateImeAdjustState(boolean force) {
- if (mAdjusted != mTargetAdjusted || force) {
- // Reposition the server's secondary split position so that it evaluates
- // insets properly.
- WindowContainerTransaction wct = new WindowContainerTransaction();
- final LegacySplitDisplayLayout splitLayout = getLayout();
- if (mTargetAdjusted) {
- splitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop);
- wct.setBounds(mSplits.mSecondary.token, splitLayout.mAdjustedSecondary);
- // "Freeze" the configuration size so that the app doesn't get a config
- // or relaunch. This is required because normally nav-bar contributes
- // to configuration bounds (via nondecorframe).
- Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration
- .windowConfiguration.getAppBounds());
- adjustAppBounds.offset(0, splitLayout.mAdjustedSecondary.top
- - splitLayout.mSecondary.top);
- wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds);
- wct.setScreenSizeDp(mSplits.mSecondary.token,
- mSplits.mSecondary.configuration.screenWidthDp,
- mSplits.mSecondary.configuration.screenHeightDp);
-
- wct.setBounds(mSplits.mPrimary.token, splitLayout.mAdjustedPrimary);
- adjustAppBounds = new Rect(mSplits.mPrimary.configuration
- .windowConfiguration.getAppBounds());
- adjustAppBounds.offset(0, splitLayout.mAdjustedPrimary.top
- - splitLayout.mPrimary.top);
- wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds);
- wct.setScreenSizeDp(mSplits.mPrimary.token,
- mSplits.mPrimary.configuration.screenWidthDp,
- mSplits.mPrimary.configuration.screenHeightDp);
- } else {
- wct.setBounds(mSplits.mSecondary.token, splitLayout.mSecondary);
- wct.setAppBounds(mSplits.mSecondary.token, null);
- wct.setScreenSizeDp(mSplits.mSecondary.token,
- SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
- wct.setBounds(mSplits.mPrimary.token, splitLayout.mPrimary);
- wct.setAppBounds(mSplits.mPrimary.token, null);
- wct.setScreenSizeDp(mSplits.mPrimary.token,
- SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
- }
-
- if (!mSplits.mSplitScreenController.getWmProxy().queueSyncTransactionIfWaiting(wct)) {
- mTaskOrganizer.applyTransaction(wct);
- }
- }
-
- // Update all the adjusted-for-ime states
- if (!mPaused) {
- final DividerView view = getView();
- if (view != null) {
- view.setAdjustedForIme(mTargetShown, mTargetShown
- ? DisplayImeController.ANIMATION_DURATION_SHOW_MS
- : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
- }
- }
- mSplits.mSplitScreenController.setAdjustedForIme(mTargetShown && !mPaused);
- }
-
- @Override
- public void onImePositionChanged(int displayId, int imeTop,
- SurfaceControl.Transaction t) {
- if (mAnimation != null || isDividerHidden() || mPaused) {
- // Not synchronized with IME anymore, so return.
- return;
- }
- final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop);
- final float progress = mTargetShown ? fraction : 1.f - fraction;
- onProgress(progress, t);
- }
-
- @Override
- public void onImeEndPositioning(int displayId, boolean cancelled,
- SurfaceControl.Transaction t) {
- if (mAnimation != null || isDividerHidden() || mPaused) {
- // Not synchronized with IME anymore, so return.
- return;
- }
- onEnd(cancelled, t);
- }
-
- private void onProgress(float progress, SurfaceControl.Transaction t) {
- final DividerView view = getView();
- if (mTargetAdjusted != mAdjusted && !mPaused) {
- final LegacySplitDisplayLayout splitLayout = getLayout();
- final float fraction = mTargetAdjusted ? progress : 1.f - progress;
- mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop);
- splitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop);
- view.resizeSplitSurfaces(t, splitLayout.mAdjustedPrimary,
- splitLayout.mAdjustedSecondary);
- }
- final float invProg = 1.f - progress;
- view.setResizeDimLayer(t, true /* primary */,
- mLastPrimaryDim * invProg + progress * mTargetPrimaryDim);
- view.setResizeDimLayer(t, false /* primary */,
- mLastSecondaryDim * invProg + progress * mTargetSecondaryDim);
- }
-
- void setDimsHidden(SurfaceControl.Transaction t, boolean hidden) {
- final DividerView view = getView();
- if (hidden) {
- view.setResizeDimLayer(t, true /* primary */, 0.f /* alpha */);
- view.setResizeDimLayer(t, false /* primary */, 0.f /* alpha */);
- } else {
- updateDimTargets();
- view.setResizeDimLayer(t, true /* primary */, mTargetPrimaryDim);
- view.setResizeDimLayer(t, false /* primary */, mTargetSecondaryDim);
- }
- }
-
- private void onEnd(boolean cancelled, SurfaceControl.Transaction t) {
- if (!cancelled) {
- onProgress(1.f, t);
- mAdjusted = mTargetAdjusted;
- mImeWasShown = mTargetShown;
- mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop;
- mLastPrimaryDim = mTargetPrimaryDim;
- mLastSecondaryDim = mTargetSecondaryDim;
- }
- }
-
- private void startAsyncAnimation() {
- if (mAnimation != null) {
- mAnimation.cancel();
- }
- mAnimation = ValueAnimator.ofFloat(0.f, 1.f);
- mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS);
- if (mTargetAdjusted != mAdjusted) {
- final float fraction =
- ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop);
- final float progress = mTargetAdjusted ? fraction : 1.f - fraction;
- mAnimation.setCurrentFraction(progress);
- }
-
- mAnimation.addUpdateListener(animation -> {
- SurfaceControl.Transaction t = mTransactionPool.acquire();
- float value = (float) animation.getAnimatedValue();
- onProgress(value, t);
- t.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
- t.apply();
- mTransactionPool.release(t);
- });
- mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR);
- mAnimation.addListener(new AnimatorListenerAdapter() {
- private boolean mCancel = false;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancel = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- SurfaceControl.Transaction t = mTransactionPool.acquire();
- onEnd(mCancel, t);
- t.apply();
- mTransactionPool.release(t);
- mAnimation = null;
- }
- });
- mAnimation.start();
- }
-
- private String dumpState() {
- return "top:" + mHiddenTop + "->" + mShownTop
- + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")"
- + " shw:" + mImeWasShown + "->" + mTargetShown
- + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim
- + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim
- + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null)
- + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]";
- }
-
- /** Completely aborts/resets adjustment state */
- public void pause(int displayId) {
- if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState());
- mMainExecutor.execute(() -> {
- if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState());
- if (mPaused) {
- return;
- }
- mPaused = true;
- mPausedTargetAdjusted = mTargetAdjusted;
- mTargetAdjusted = false;
- mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
- updateImeAdjustState();
- startAsyncAnimation();
- if (mAnimation != null) {
- mAnimation.end();
- }
- });
- }
-
- public void resume(int displayId) {
- if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState());
- mMainExecutor.execute(() -> {
- if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState());
- if (!mPaused) {
- return;
- }
- mPaused = false;
- mTargetAdjusted = mPausedTargetAdjusted;
- updateDimTargets();
- final DividerView view = getView();
- if ((mTargetAdjusted != mAdjusted) && !mSplits.mSplitScreenController.isMinimized()
- && view != null) {
- // End unminimize animations since they conflict with adjustment animations.
- view.finishAnimations();
- }
- updateImeAdjustState();
- startAsyncAnimation();
- });
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
deleted file mode 100644
index 73be2835d2cd..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
+++ /dev/null
@@ -1,1314 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.legacysplitscreen;
-
-import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
-import static android.view.WindowManager.DOCKED_RIGHT;
-
-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.DividerView.TOUCH_ANIMATION_DURATION;
-import static com.android.wm.shell.common.split.DividerView.TOUCH_RELEASE_ANIMATION_DURATION;
-
-import android.animation.AnimationHandler;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.Region.Op;
-import android.hardware.display.DisplayManager;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.Slog;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.MotionEvent;
-import android.view.PointerIcon;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
-import android.view.ViewTreeObserver.InternalInsetsInfo;
-import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-import android.widget.FrameLayout;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
-import com.android.internal.policy.DockedDividerUtils;
-import com.android.wm.shell.R;
-import com.android.wm.shell.animation.FlingAnimationUtils;
-import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.common.split.DividerHandleView;
-
-import java.util.function.Consumer;
-
-/**
- * Docked stack divider.
- */
-public class DividerView extends FrameLayout implements OnTouchListener,
- OnComputeInternalInsetsListener {
- private static final String TAG = "DividerView";
- private static final boolean DEBUG = LegacySplitScreenController.DEBUG;
-
- interface DividerCallbacks {
- void onDraggingStart();
- void onDraggingEnd();
- }
-
- public static final int INVALID_RECENTS_GROW_TARGET = -1;
-
- private static final int LOG_VALUE_RESIZE_50_50 = 0;
- private static final int LOG_VALUE_RESIZE_DOCKED_SMALLER = 1;
- private static final int LOG_VALUE_RESIZE_DOCKED_LARGER = 2;
-
- private static final int LOG_VALUE_UNDOCK_MAX_DOCKED = 0;
- private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1;
-
- private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
-
- /**
- * How much the background gets scaled when we are in the minimized dock state.
- */
- private static final float MINIMIZE_DOCK_SCALE = 0f;
- private static final float ADJUSTED_FOR_IME_SCALE = 0.5f;
-
- private static final Interpolator IME_ADJUST_INTERPOLATOR =
- new PathInterpolator(0.2f, 0f, 0.1f, 1f);
-
- private DividerHandleView mHandle;
- private View mBackground;
- private MinimizedDockShadow mMinimizedShadow;
- private int mStartX;
- private int mStartY;
- private int mStartPosition;
- private int mDockSide;
- private boolean mMoving;
- private int mTouchSlop;
- private boolean mBackgroundLifted;
- private boolean mIsInMinimizeInteraction;
- SnapTarget mSnapTargetBeforeMinimized;
-
- private int mDividerInsets;
- private final Display mDefaultDisplay;
-
- private int mDividerSize;
- private int mTouchElevation;
- private int mLongPressEntraceAnimDuration;
-
- private final Rect mDockedRect = new Rect();
- private final Rect mDockedTaskRect = new Rect();
- private final Rect mOtherTaskRect = new Rect();
- private final Rect mOtherRect = new Rect();
- private final Rect mDockedInsetRect = new Rect();
- private final Rect mOtherInsetRect = new Rect();
- private final Rect mLastResizeRect = new Rect();
- private final Rect mTmpRect = new Rect();
- private LegacySplitScreenController mSplitScreenController;
- private WindowManagerProxy mWindowManagerProxy;
- private DividerWindowManager mWindowManager;
- private VelocityTracker mVelocityTracker;
- private FlingAnimationUtils mFlingAnimationUtils;
- private LegacySplitDisplayLayout mSplitLayout;
- private DividerImeController mImeController;
- private DividerCallbacks mCallback;
-
- private AnimationHandler mSfVsyncAnimationHandler;
- private ValueAnimator mCurrentAnimator;
- private boolean mEntranceAnimationRunning;
- private boolean mExitAnimationRunning;
- private int mExitStartPosition;
- private boolean mDockedStackMinimized;
- private boolean mHomeStackResizable;
- private boolean mAdjustedForIme;
- private DividerState mState;
-
- private LegacySplitScreenTaskListener mTiles;
- boolean mFirstLayout = true;
- int mDividerPositionX;
- int mDividerPositionY;
-
- private final Matrix mTmpMatrix = new Matrix();
- private final float[] mTmpValues = new float[9];
-
- // The view is removed or in the process of been removed from the system.
- private boolean mRemoved;
-
- // Whether the surface for this view has been hidden regardless of actual visibility. This is
- // used interact with keyguard.
- private boolean mSurfaceHidden = false;
-
- private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- final DividerSnapAlgorithm snapAlgorithm = getSnapAlgorithm();
- if (isHorizontalDivision()) {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
- mContext.getString(R.string.accessibility_action_divider_top_full)));
- if (snapAlgorithm.isFirstSplitTargetAvailable()) {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
- mContext.getString(R.string.accessibility_action_divider_top_70)));
- }
- if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
- // Only show the middle target if there are more than 1 split target
- info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
- mContext.getString(R.string.accessibility_action_divider_top_50)));
- }
- if (snapAlgorithm.isLastSplitTargetAvailable()) {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
- mContext.getString(R.string.accessibility_action_divider_top_30)));
- }
- info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
- mContext.getString(R.string.accessibility_action_divider_bottom_full)));
- } else {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
- mContext.getString(R.string.accessibility_action_divider_left_full)));
- if (snapAlgorithm.isFirstSplitTargetAvailable()) {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
- mContext.getString(R.string.accessibility_action_divider_left_70)));
- }
- if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
- // Only show the middle target if there are more than 1 split target
- info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
- mContext.getString(R.string.accessibility_action_divider_left_50)));
- }
- if (snapAlgorithm.isLastSplitTargetAvailable()) {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
- mContext.getString(R.string.accessibility_action_divider_left_30)));
- }
- info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
- mContext.getString(R.string.accessibility_action_divider_right_full)));
- }
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- int currentPosition = getCurrentPosition();
- SnapTarget nextTarget = null;
- DividerSnapAlgorithm snapAlgorithm = mSplitLayout.getSnapAlgorithm();
- if (action == R.id.action_move_tl_full) {
- nextTarget = snapAlgorithm.getDismissEndTarget();
- } else if (action == R.id.action_move_tl_70) {
- nextTarget = snapAlgorithm.getLastSplitTarget();
- } else if (action == R.id.action_move_tl_50) {
- nextTarget = snapAlgorithm.getMiddleTarget();
- } else if (action == R.id.action_move_tl_30) {
- nextTarget = snapAlgorithm.getFirstSplitTarget();
- } else if (action == R.id.action_move_rb_full) {
- nextTarget = snapAlgorithm.getDismissStartTarget();
- }
- if (nextTarget != null) {
- startDragging(true /* animate */, false /* touching */);
- stopDragging(currentPosition, nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN);
- return true;
- }
- return super.performAccessibilityAction(host, action, args);
- }
- };
-
- private final Runnable mResetBackgroundRunnable = new Runnable() {
- @Override
- public void run() {
- resetBackground();
- }
- };
-
- public DividerView(Context context) {
- this(context, null);
- }
-
- public DividerView(Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- final DisplayManager displayManager =
- (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
- mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
- }
-
- public void setAnimationHandler(AnimationHandler sfVsyncAnimationHandler) {
- mSfVsyncAnimationHandler = sfVsyncAnimationHandler;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mHandle = findViewById(R.id.docked_divider_handle);
- mBackground = findViewById(R.id.docked_divider_background);
- mMinimizedShadow = findViewById(R.id.minimized_dock_shadow);
- mHandle.setOnTouchListener(this);
- final int dividerWindowWidth = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_thickness);
- mDividerInsets = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_insets);
- mDividerSize = dividerWindowWidth - 2 * mDividerInsets;
- mTouchElevation = getResources().getDimensionPixelSize(
- R.dimen.docked_stack_divider_lift_elevation);
- mLongPressEntraceAnimDuration = getResources().getInteger(
- R.integer.long_press_dock_anim_duration);
- mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- mFlingAnimationUtils = new FlingAnimationUtils(getResources().getDisplayMetrics(), 0.3f);
- boolean landscape = getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
- mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
- landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
- getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- mHandle.setAccessibilityDelegate(mHandleDelegate);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- // Save the current target if not minimized once attached to window
- if (mDockSide != WindowManager.DOCKED_INVALID && !mIsInMinimizeInteraction) {
- saveSnapTargetBeforeMinimized(mSnapTargetBeforeMinimized);
- }
- mFirstLayout = true;
- }
-
- void onDividerRemoved() {
- mRemoved = true;
- mCallback = null;
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (mFirstLayout) {
- // Wait for first layout so that the ViewRootImpl surface has been created.
- initializeSurfaceState();
- mFirstLayout = false;
- }
- int minimizeLeft = 0;
- int minimizeTop = 0;
- if (mDockSide == WindowManager.DOCKED_TOP) {
- minimizeTop = mBackground.getTop();
- } else if (mDockSide == WindowManager.DOCKED_LEFT) {
- minimizeLeft = mBackground.getLeft();
- } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
- minimizeLeft = mBackground.getRight() - mMinimizedShadow.getWidth();
- }
- mMinimizedShadow.layout(minimizeLeft, minimizeTop,
- minimizeLeft + mMinimizedShadow.getMeasuredWidth(),
- minimizeTop + mMinimizedShadow.getMeasuredHeight());
- if (changed) {
- notifySplitScreenBoundsChanged();
- }
- }
-
- void injectDependencies(LegacySplitScreenController splitScreenController,
- DividerWindowManager windowManager, DividerState dividerState,
- DividerCallbacks callback, LegacySplitScreenTaskListener tiles,
- LegacySplitDisplayLayout sdl, DividerImeController imeController,
- WindowManagerProxy wmProxy) {
- mSplitScreenController = splitScreenController;
- mWindowManager = windowManager;
- mState = dividerState;
- mCallback = callback;
- mTiles = tiles;
- mSplitLayout = sdl;
- mImeController = imeController;
- mWindowManagerProxy = wmProxy;
-
- if (mState.mRatioPositionBeforeMinimized == 0) {
- // Set the middle target as the initial state
- mSnapTargetBeforeMinimized = mSplitLayout.getSnapAlgorithm().getMiddleTarget();
- } else {
- repositionSnapTargetBeforeMinimized();
- }
- }
-
- /** Gets non-minimized secondary bounds of split screen. */
- public Rect getNonMinimizedSplitScreenSecondaryBounds() {
- mOtherTaskRect.set(mSplitLayout.mSecondary);
- return mOtherTaskRect;
- }
-
- private boolean inSplitMode() {
- return getVisibility() == VISIBLE;
- }
-
- /** Unlike setVisible, this directly hides the surface without changing view visibility. */
- void setHidden(boolean hidden) {
- if (mSurfaceHidden == hidden) {
- return;
- }
- mSurfaceHidden = hidden;
- post(() -> {
- final SurfaceControl sc = getWindowSurfaceControl();
- if (sc == null) {
- return;
- }
- Transaction t = mTiles.getTransaction();
- if (hidden) {
- t.hide(sc);
- } else {
- t.show(sc);
- }
- mImeController.setDimsHidden(t, hidden);
- t.apply();
- mTiles.releaseTransaction(t);
- });
- }
-
- boolean isHidden() {
- return getVisibility() != View.VISIBLE || mSurfaceHidden;
- }
-
- /** Starts dragging the divider bar. */
- public boolean startDragging(boolean animate, boolean touching) {
- cancelFlingAnimation();
- if (touching) {
- mHandle.setTouching(true, animate);
- }
- mDockSide = mSplitLayout.getPrimarySplitSide();
-
- mWindowManagerProxy.setResizing(true);
- if (touching) {
- mWindowManager.setSlippery(false);
- liftBackground();
- }
- if (mCallback != null) {
- mCallback.onDraggingStart();
- }
- return inSplitMode();
- }
-
- /** Stops dragging the divider bar. */
- public void stopDragging(int position, float velocity, boolean avoidDismissStart,
- boolean logMetrics) {
- mHandle.setTouching(false, true /* animate */);
- fling(position, velocity, avoidDismissStart, logMetrics);
- mWindowManager.setSlippery(true);
- releaseBackground();
- }
-
- private void stopDragging(int position, SnapTarget target, long duration,
- Interpolator interpolator) {
- stopDragging(position, target, duration, 0 /* startDelay*/, 0 /* endDelay */, interpolator);
- }
-
- private void stopDragging(int position, SnapTarget target, long duration,
- Interpolator interpolator, long endDelay) {
- stopDragging(position, target, duration, 0 /* startDelay*/, endDelay, interpolator);
- }
-
- private void stopDragging(int position, SnapTarget target, long duration, long startDelay,
- long endDelay, Interpolator interpolator) {
- mHandle.setTouching(false, true /* animate */);
- flingTo(position, target, duration, startDelay, endDelay, interpolator);
- mWindowManager.setSlippery(true);
- releaseBackground();
- }
-
- private void stopDragging() {
- mHandle.setTouching(false, true /* animate */);
- mWindowManager.setSlippery(true);
- mWindowManagerProxy.setResizing(false);
- releaseBackground();
- }
-
- private void updateDockSide() {
- mDockSide = mSplitLayout.getPrimarySplitSide();
- mMinimizedShadow.setDockSide(mDockSide);
- }
-
- public DividerSnapAlgorithm getSnapAlgorithm() {
- return mDockedStackMinimized ? mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
- : mSplitLayout.getSnapAlgorithm();
- }
-
- public int getCurrentPosition() {
- return isHorizontalDivision() ? mDividerPositionY : mDividerPositionX;
- }
-
- public boolean isMinimized() {
- return mDockedStackMinimized;
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- convertToScreenCoordinates(event);
- final int action = event.getAction() & MotionEvent.ACTION_MASK;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mVelocityTracker = VelocityTracker.obtain();
- mVelocityTracker.addMovement(event);
- mStartX = (int) event.getX();
- mStartY = (int) event.getY();
- boolean result = startDragging(true /* animate */, true /* touching */);
- if (!result) {
-
- // Weren't able to start dragging successfully, so cancel it again.
- stopDragging();
- }
- mStartPosition = getCurrentPosition();
- mMoving = false;
- return result;
- case MotionEvent.ACTION_MOVE:
- mVelocityTracker.addMovement(event);
- int x = (int) event.getX();
- int y = (int) event.getY();
- boolean exceededTouchSlop =
- isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop
- || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop);
- if (!mMoving && exceededTouchSlop) {
- mStartX = x;
- mStartY = y;
- mMoving = true;
- }
- if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
- SnapTarget snapTarget = getSnapAlgorithm().calculateSnapTarget(
- mStartPosition, 0 /* velocity */, false /* hardDismiss */);
- resizeStackSurfaces(calculatePosition(x, y), mStartPosition, snapTarget,
- null /* transaction */);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (!mMoving) {
- stopDragging();
- break;
- }
-
- x = (int) event.getRawX();
- y = (int) event.getRawY();
- mVelocityTracker.addMovement(event);
- mVelocityTracker.computeCurrentVelocity(1000);
- int position = calculatePosition(x, y);
- stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
- : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */,
- true /* log */);
- mMoving = false;
- break;
- }
- return true;
- }
-
- private void logResizeEvent(SnapTarget snapTarget) {
- if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissStartTarget()) {
- MetricsLogger.action(
- mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide)
- ? LOG_VALUE_UNDOCK_MAX_OTHER
- : LOG_VALUE_UNDOCK_MAX_DOCKED);
- } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissEndTarget()) {
- MetricsLogger.action(
- mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide)
- ? LOG_VALUE_UNDOCK_MAX_OTHER
- : LOG_VALUE_UNDOCK_MAX_DOCKED);
- } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getMiddleTarget()) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
- LOG_VALUE_RESIZE_50_50);
- } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getFirstSplitTarget()) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
- dockSideTopLeft(mDockSide)
- ? LOG_VALUE_RESIZE_DOCKED_SMALLER
- : LOG_VALUE_RESIZE_DOCKED_LARGER);
- } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getLastSplitTarget()) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
- dockSideTopLeft(mDockSide)
- ? LOG_VALUE_RESIZE_DOCKED_LARGER
- : LOG_VALUE_RESIZE_DOCKED_SMALLER);
- }
- }
-
- private void convertToScreenCoordinates(MotionEvent event) {
- event.setLocation(event.getRawX(), event.getRawY());
- }
-
- private void fling(int position, float velocity, boolean avoidDismissStart,
- boolean logMetrics) {
- DividerSnapAlgorithm currentSnapAlgorithm = getSnapAlgorithm();
- SnapTarget snapTarget = currentSnapAlgorithm.calculateSnapTarget(position, velocity);
- if (avoidDismissStart && snapTarget == currentSnapAlgorithm.getDismissStartTarget()) {
- snapTarget = currentSnapAlgorithm.getFirstSplitTarget();
- }
- if (logMetrics) {
- logResizeEvent(snapTarget);
- }
- ValueAnimator anim = getFlingAnimator(position, snapTarget, 0 /* endDelay */);
- mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity);
- anim.start();
- }
-
- private void flingTo(int position, SnapTarget target, long duration, long startDelay,
- long endDelay, Interpolator interpolator) {
- ValueAnimator anim = getFlingAnimator(position, target, endDelay);
- anim.setDuration(duration);
- anim.setStartDelay(startDelay);
- anim.setInterpolator(interpolator);
- anim.start();
- }
-
- private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget,
- final long endDelay) {
- if (mCurrentAnimator != null) {
- cancelFlingAnimation();
- updateDockSide();
- }
- if (DEBUG) Slog.d(TAG, "Getting fling " + position + "->" + snapTarget.position);
- final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE;
- ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
- anim.addUpdateListener(animation -> resizeStackSurfaces((int) animation.getAnimatedValue(),
- taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f
- ? TASK_POSITION_SAME
- : snapTarget.taskPosition,
- snapTarget, null /* transaction */));
- Consumer<Boolean> endAction = cancelled -> {
- if (DEBUG) Slog.d(TAG, "End Fling " + cancelled + " min:" + mIsInMinimizeInteraction);
- final boolean wasMinimizeInteraction = mIsInMinimizeInteraction;
- // Reset minimized divider position after unminimized state animation finishes.
- if (!cancelled && !mDockedStackMinimized && mIsInMinimizeInteraction) {
- mIsInMinimizeInteraction = false;
- }
- boolean dismissed = commitSnapFlags(snapTarget);
- mWindowManagerProxy.setResizing(false);
- updateDockSide();
- mCurrentAnimator = null;
- mEntranceAnimationRunning = false;
- mExitAnimationRunning = false;
- if (!dismissed && !wasMinimizeInteraction) {
- mWindowManagerProxy.applyResizeSplits(snapTarget.position, mSplitLayout);
- }
- if (mCallback != null) {
- mCallback.onDraggingEnd();
- }
-
- // Record last snap target the divider moved to
- if (!mIsInMinimizeInteraction) {
- // The last snapTarget position can be negative when the last divider position was
- // offscreen. In that case, save the middle (default) SnapTarget so calculating next
- // position isn't negative.
- final SnapTarget saveTarget;
- if (snapTarget.position < 0) {
- saveTarget = mSplitLayout.getSnapAlgorithm().getMiddleTarget();
- } else {
- saveTarget = snapTarget;
- }
- final DividerSnapAlgorithm snapAlgo = mSplitLayout.getSnapAlgorithm();
- if (saveTarget.position != snapAlgo.getDismissEndTarget().position
- && saveTarget.position != snapAlgo.getDismissStartTarget().position) {
- saveSnapTargetBeforeMinimized(saveTarget);
- }
- }
- notifySplitScreenBoundsChanged();
- };
- anim.addListener(new AnimatorListenerAdapter() {
-
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- long delay = 0;
- if (endDelay != 0) {
- delay = endDelay;
- } else if (mCancelled) {
- delay = 0;
- }
- if (delay == 0) {
- endAction.accept(mCancelled);
- } else {
- final Boolean cancelled = mCancelled;
- if (DEBUG) Slog.d(TAG, "Posting endFling " + cancelled + " d:" + delay + "ms");
- getHandler().postDelayed(() -> endAction.accept(cancelled), delay);
- }
- }
- });
- mCurrentAnimator = anim;
- mCurrentAnimator.setAnimationHandler(mSfVsyncAnimationHandler);
- return anim;
- }
-
- private void notifySplitScreenBoundsChanged() {
- if (mSplitLayout.mPrimary == null || mSplitLayout.mSecondary == null) {
- return;
- }
- mOtherTaskRect.set(mSplitLayout.mSecondary);
-
- mTmpRect.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), mHandle.getBottom());
- if (isHorizontalDivision()) {
- mTmpRect.offsetTo(mHandle.getLeft(), mDividerPositionY);
- } else {
- mTmpRect.offsetTo(mDividerPositionX, mHandle.getTop());
- }
- mWindowManagerProxy.setTouchRegion(mTmpRect);
-
- mTmpRect.set(mSplitLayout.mDisplayLayout.stableInsets());
- switch (mSplitLayout.getPrimarySplitSide()) {
- case WindowManager.DOCKED_LEFT:
- mTmpRect.left = 0;
- break;
- case WindowManager.DOCKED_RIGHT:
- mTmpRect.right = 0;
- break;
- case WindowManager.DOCKED_TOP:
- mTmpRect.top = 0;
- break;
- }
- mSplitScreenController.notifyBoundsChanged(mOtherTaskRect, mTmpRect);
- }
-
- private void cancelFlingAnimation() {
- if (mCurrentAnimator != null) {
- mCurrentAnimator.cancel();
- }
- }
-
- private boolean commitSnapFlags(SnapTarget target) {
- if (target.flag == SnapTarget.FLAG_NONE) {
- return false;
- }
- final boolean dismissOrMaximize;
- if (target.flag == SnapTarget.FLAG_DISMISS_START) {
- dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
- || mDockSide == WindowManager.DOCKED_TOP;
- } else {
- dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
- || mDockSide == WindowManager.DOCKED_BOTTOM;
- }
- mWindowManagerProxy.dismissOrMaximizeDocked(mTiles, mSplitLayout, dismissOrMaximize);
- Transaction t = mTiles.getTransaction();
- setResizeDimLayer(t, true /* primary */, 0f);
- setResizeDimLayer(t, false /* primary */, 0f);
- t.apply();
- mTiles.releaseTransaction(t);
- return true;
- }
-
- private void liftBackground() {
- if (mBackgroundLifted) {
- return;
- }
- if (isHorizontalDivision()) {
- mBackground.animate().scaleY(1.4f);
- } else {
- mBackground.animate().scaleX(1.4f);
- }
- mBackground.animate()
- .setInterpolator(Interpolators.TOUCH_RESPONSE)
- .setDuration(TOUCH_ANIMATION_DURATION)
- .translationZ(mTouchElevation)
- .start();
-
- // Lift handle as well so it doesn't get behind the background, even though it doesn't
- // cast shadow.
- mHandle.animate()
- .setInterpolator(Interpolators.TOUCH_RESPONSE)
- .setDuration(TOUCH_ANIMATION_DURATION)
- .translationZ(mTouchElevation)
- .start();
- mBackgroundLifted = true;
- }
-
- private void releaseBackground() {
- if (!mBackgroundLifted) {
- return;
- }
- mBackground.animate()
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
- .translationZ(0)
- .scaleX(1f)
- .scaleY(1f)
- .start();
- mHandle.animate()
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
- .translationZ(0)
- .start();
- mBackgroundLifted = false;
- }
-
- private void initializeSurfaceState() {
- int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
- // Recalculate the split-layout's internal tile bounds
- mSplitLayout.resizeSplits(midPos);
- Transaction t = mTiles.getTransaction();
- if (mDockedStackMinimized) {
- int position = mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
- .getMiddleTarget().position;
- calculateBoundsForPosition(position, mDockSide, mDockedRect);
- calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
- mOtherRect);
- mDividerPositionX = mDividerPositionY = position;
- resizeSplitSurfaces(t, mDockedRect, mSplitLayout.mPrimary,
- mOtherRect, mSplitLayout.mSecondary);
- } else {
- resizeSplitSurfaces(t, mSplitLayout.mPrimary, null,
- mSplitLayout.mSecondary, null);
- }
- setResizeDimLayer(t, true /* primary */, 0.f /* alpha */);
- setResizeDimLayer(t, false /* secondary */, 0.f /* alpha */);
- t.apply();
- mTiles.releaseTransaction(t);
-
- // Get the actually-visible bar dimensions (relative to full window). This is a thin
- // bar going through the center.
- final Rect dividerBar = isHorizontalDivision()
- ? new Rect(0, mDividerInsets, mSplitLayout.mDisplayLayout.width(),
- mDividerInsets + mDividerSize)
- : new Rect(mDividerInsets, 0, mDividerInsets + mDividerSize,
- mSplitLayout.mDisplayLayout.height());
- final Region touchRegion = new Region(dividerBar);
- // Add in the "draggable" portion. While not visible, this is an expanded area that the
- // user can interact with.
- touchRegion.union(new Rect(mHandle.getLeft(), mHandle.getTop(),
- mHandle.getRight(), mHandle.getBottom()));
- mWindowManager.setTouchRegion(touchRegion);
- }
-
- void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable,
- Transaction t) {
- mHomeStackResizable = isHomeStackResizable;
- updateDockSide();
- if (!minimized) {
- resetBackground();
- }
- mMinimizedShadow.setAlpha(minimized ? 1f : 0f);
- if (mDockedStackMinimized != minimized) {
- mDockedStackMinimized = minimized;
- if (mSplitLayout.mDisplayLayout.rotation() != mDefaultDisplay.getRotation()) {
- // Splitscreen to minimize is about to starts after rotating landscape to seascape,
- // update display info and snap algorithm targets
- repositionSnapTargetBeforeMinimized();
- }
- if (mIsInMinimizeInteraction != minimized || mCurrentAnimator != null) {
- cancelFlingAnimation();
- if (minimized) {
- // Relayout to recalculate the divider shadow when minimizing
- requestLayout();
- mIsInMinimizeInteraction = true;
- resizeStackSurfaces(mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
- .getMiddleTarget(), t);
- } else {
- resizeStackSurfaces(mSnapTargetBeforeMinimized, t);
- mIsInMinimizeInteraction = false;
- }
- }
- }
- }
-
- void enterSplitMode(boolean isHomeStackResizable) {
- setHidden(false);
-
- SnapTarget miniMid =
- mSplitLayout.getMinimizedSnapAlgorithm(isHomeStackResizable).getMiddleTarget();
- if (mDockedStackMinimized) {
- mDividerPositionY = mDividerPositionX = miniMid.position;
- }
- }
-
- /**
- * Tries to grab a surface control from ViewRootImpl. If this isn't available for some reason
- * (ie. the window isn't ready yet), it will get the surfacecontrol that the WindowlessWM has
- * assigned to it.
- */
- private SurfaceControl getWindowSurfaceControl() {
- return mWindowManager.mSystemWindows.getViewSurface(this);
- }
-
- void exitSplitMode() {
- // The view is going to be removed right after this function involved, updates the surface
- // in the current thread instead of posting it to the view's UI thread.
- final SurfaceControl sc = getWindowSurfaceControl();
- if (sc == null) {
- return;
- }
- Transaction t = mTiles.getTransaction();
- t.hide(sc);
- mImeController.setDimsHidden(t, true);
- t.apply();
- mTiles.releaseTransaction(t);
-
- // Reset tile bounds
- int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
- mWindowManagerProxy.applyResizeSplits(midPos, mSplitLayout);
- }
-
- void setMinimizedDockStack(boolean minimized, long animDuration,
- boolean isHomeStackResizable) {
- if (DEBUG) Slog.d(TAG, "setMinDock: " + mDockedStackMinimized + "->" + minimized);
- mHomeStackResizable = isHomeStackResizable;
- updateDockSide();
- if (mDockedStackMinimized != minimized) {
- mIsInMinimizeInteraction = true;
- mDockedStackMinimized = minimized;
- stopDragging(minimized
- ? mSnapTargetBeforeMinimized.position
- : getCurrentPosition(),
- minimized
- ? mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
- .getMiddleTarget()
- : mSnapTargetBeforeMinimized,
- animDuration, Interpolators.FAST_OUT_SLOW_IN, 0);
- setAdjustedForIme(false, animDuration);
- }
- if (!minimized) {
- mBackground.animate().withEndAction(mResetBackgroundRunnable);
- }
- mBackground.animate()
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setDuration(animDuration)
- .start();
- }
-
- // Needed to end any currently playing animations when they might compete with other anims
- // (specifically, IME adjust animation immediately after leaving minimized). Someday maybe
- // these can be unified, but not today.
- void finishAnimations() {
- if (mCurrentAnimator != null) {
- mCurrentAnimator.end();
- }
- }
-
- void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
- if (mAdjustedForIme == adjustedForIme) {
- return;
- }
- updateDockSide();
- mHandle.animate()
- .setInterpolator(IME_ADJUST_INTERPOLATOR)
- .setDuration(animDuration)
- .alpha(adjustedForIme ? 0f : 1f)
- .start();
- if (mDockSide == WindowManager.DOCKED_TOP) {
- mBackground.setPivotY(0);
- mBackground.animate()
- .scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f);
- }
- if (!adjustedForIme) {
- mBackground.animate().withEndAction(mResetBackgroundRunnable);
- }
- mBackground.animate()
- .setInterpolator(IME_ADJUST_INTERPOLATOR)
- .setDuration(animDuration)
- .start();
- mAdjustedForIme = adjustedForIme;
- }
-
- private void saveSnapTargetBeforeMinimized(SnapTarget target) {
- mSnapTargetBeforeMinimized = target;
- mState.mRatioPositionBeforeMinimized = (float) target.position
- / (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height()
- : mSplitLayout.mDisplayLayout.width());
- }
-
- private void resetBackground() {
- mBackground.setPivotX(mBackground.getWidth() / 2);
- mBackground.setPivotY(mBackground.getHeight() / 2);
- mBackground.setScaleX(1f);
- mBackground.setScaleY(1f);
- mMinimizedShadow.setAlpha(0f);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- }
-
- private void repositionSnapTargetBeforeMinimized() {
- int position = (int) (mState.mRatioPositionBeforeMinimized
- * (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height()
- : mSplitLayout.mDisplayLayout.width()));
-
- // Set the snap target before minimized but do not save until divider is attached and not
- // minimized because it does not know its minimized state yet.
- mSnapTargetBeforeMinimized =
- mSplitLayout.getSnapAlgorithm().calculateNonDismissingSnapTarget(position);
- }
-
- private int calculatePosition(int touchX, int touchY) {
- return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
- }
-
- public boolean isHorizontalDivision() {
- return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
- }
-
- private int calculateXPosition(int touchX) {
- return mStartPosition + touchX - mStartX;
- }
-
- private int calculateYPosition(int touchY) {
- return mStartPosition + touchY - mStartY;
- }
-
- private void alignTopLeft(Rect containingRect, Rect rect) {
- int width = rect.width();
- int height = rect.height();
- rect.set(containingRect.left, containingRect.top,
- containingRect.left + width, containingRect.top + height);
- }
-
- private void alignBottomRight(Rect containingRect, Rect rect) {
- int width = rect.width();
- int height = rect.height();
- rect.set(containingRect.right - width, containingRect.bottom - height,
- containingRect.right, containingRect.bottom);
- }
-
- private void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
- DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect,
- mSplitLayout.mDisplayLayout.width(), mSplitLayout.mDisplayLayout.height(),
- mDividerSize);
- }
-
- private void resizeStackSurfaces(SnapTarget taskSnapTarget, Transaction t) {
- resizeStackSurfaces(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget, t);
- }
-
- void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect otherRect) {
- resizeSplitSurfaces(t, dockedRect, null, otherRect, null);
- }
-
- private void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect dockedTaskRect,
- Rect otherRect, Rect otherTaskRect) {
- dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect;
- otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect;
-
- mDividerPositionX = mSplitLayout.getPrimarySplitSide() == DOCKED_RIGHT
- ? otherRect.right : dockedRect.right;
- mDividerPositionY = dockedRect.bottom;
-
- if (DEBUG) {
- Slog.d(TAG, "Resizing split surfaces: " + dockedRect + " " + dockedTaskRect
- + " " + otherRect + " " + otherTaskRect);
- }
-
- t.setPosition(mTiles.mPrimarySurface, dockedTaskRect.left, dockedTaskRect.top);
- Rect crop = new Rect(dockedRect);
- crop.offsetTo(-Math.min(dockedTaskRect.left - dockedRect.left, 0),
- -Math.min(dockedTaskRect.top - dockedRect.top, 0));
- t.setWindowCrop(mTiles.mPrimarySurface, crop);
- t.setPosition(mTiles.mSecondarySurface, otherTaskRect.left, otherTaskRect.top);
- crop.set(otherRect);
- crop.offsetTo(-(otherTaskRect.left - otherRect.left),
- -(otherTaskRect.top - otherRect.top));
- t.setWindowCrop(mTiles.mSecondarySurface, crop);
- final SurfaceControl dividerCtrl = getWindowSurfaceControl();
- if (dividerCtrl != null) {
- if (isHorizontalDivision()) {
- t.setPosition(dividerCtrl, 0, mDividerPositionY - mDividerInsets);
- } else {
- t.setPosition(dividerCtrl, mDividerPositionX - mDividerInsets, 0);
- }
- }
- }
-
- void setResizeDimLayer(Transaction t, boolean primary, float alpha) {
- SurfaceControl dim = primary ? mTiles.mPrimaryDim : mTiles.mSecondaryDim;
- if (alpha <= 0.001f) {
- t.hide(dim);
- } else {
- t.setAlpha(dim, alpha);
- t.show(dim);
- }
- }
-
- void resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget,
- Transaction transaction) {
- if (mRemoved) {
- // This divider view has been removed so shouldn't have any additional influence.
- return;
- }
- calculateBoundsForPosition(position, mDockSide, mDockedRect);
- calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
- mOtherRect);
-
- if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) {
- return;
- }
-
- // Make sure shadows are updated
- if (mBackground.getZ() > 0f) {
- mBackground.invalidate();
- }
-
- final boolean ownTransaction = transaction == null;
- final Transaction t = ownTransaction ? mTiles.getTransaction() : transaction;
- mLastResizeRect.set(mDockedRect);
- if (mIsInMinimizeInteraction) {
- calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide,
- mDockedTaskRect);
- calculateBoundsForPosition(mSnapTargetBeforeMinimized.position,
- DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
-
- // Move a right-docked-app to line up with the divider while dragging it
- if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(Math.max(position, -mDividerSize)
- - mDockedTaskRect.left + mDividerSize, 0);
- }
- resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
- if (ownTransaction) {
- t.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
- t.apply();
- mTiles.releaseTransaction(t);
- }
- return;
- }
-
- if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) {
- calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
-
- // Move a docked app if from the right in position with the divider up to insets
- if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(Math.max(position, -mDividerSize)
- - mDockedTaskRect.left + mDividerSize, 0);
- }
- calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
- mOtherTaskRect);
- resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
- } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) {
- calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
- mDockedInsetRect.set(mDockedTaskRect);
- calculateBoundsForPosition(mExitStartPosition,
- DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
- mOtherInsetRect.set(mOtherTaskRect);
- applyExitAnimationParallax(mOtherTaskRect, position);
-
- // Move a right-docked-app to line up with the divider while dragging it
- if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(position + mDividerSize, 0);
- }
- resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
- } else if (taskPosition != TASK_POSITION_SAME) {
- calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
- mOtherRect);
- int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide);
- int taskPositionDocked =
- restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
- int taskPositionOther =
- restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
- calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
- calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
- mTmpRect.set(0, 0, mSplitLayout.mDisplayLayout.width(),
- mSplitLayout.mDisplayLayout.height());
- alignTopLeft(mDockedRect, mDockedTaskRect);
- alignTopLeft(mOtherRect, mOtherTaskRect);
- mDockedInsetRect.set(mDockedTaskRect);
- mOtherInsetRect.set(mOtherTaskRect);
- if (dockSideTopLeft(mDockSide)) {
- alignTopLeft(mTmpRect, mDockedInsetRect);
- alignBottomRight(mTmpRect, mOtherInsetRect);
- } else {
- alignBottomRight(mTmpRect, mDockedInsetRect);
- alignTopLeft(mTmpRect, mOtherInsetRect);
- }
- applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position,
- taskPositionDocked);
- applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
- taskPositionOther);
- resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
- } else {
- resizeSplitSurfaces(t, mDockedRect, null, mOtherRect, null);
- }
- SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
- float dimFraction = getDimFraction(position, closestDismissTarget);
- setResizeDimLayer(t, isDismissTargetPrimary(closestDismissTarget), dimFraction);
- if (ownTransaction) {
- t.apply();
- mTiles.releaseTransaction(t);
- }
- }
-
- private void applyExitAnimationParallax(Rect taskRect, int position) {
- if (mDockSide == WindowManager.DOCKED_TOP) {
- taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f));
- } else if (mDockSide == WindowManager.DOCKED_LEFT) {
- taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0);
- } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
- taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0);
- }
- }
-
- private float getDimFraction(int position, SnapTarget dismissTarget) {
- if (mEntranceAnimationRunning) {
- return 0f;
- }
- float fraction = getSnapAlgorithm().calculateDismissingFraction(position);
- fraction = Math.max(0, Math.min(fraction, 1f));
- fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
- return fraction;
- }
-
- /**
- * When the snap target is dismissing one side, make sure that the dismissing side doesn't get
- * 0 size.
- */
- private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
- SnapTarget snapTarget) {
- if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
- return Math.max(mSplitLayout.getSnapAlgorithm().getFirstSplitTarget().position,
- mStartPosition);
- } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
- && dockSideBottomRight(dockSide)) {
- return Math.min(mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position,
- mStartPosition);
- } else {
- return taskPosition;
- }
- }
-
- /**
- * Applies a parallax to the task when dismissing.
- */
- private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
- int position, int taskPosition) {
- float fraction = Math.min(1, Math.max(0,
- mSplitLayout.getSnapAlgorithm().calculateDismissingFraction(position)));
- SnapTarget dismissTarget = null;
- SnapTarget splitTarget = null;
- int start = 0;
- if (position <= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position
- && dockSideTopLeft(dockSide)) {
- dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissStartTarget();
- splitTarget = mSplitLayout.getSnapAlgorithm().getFirstSplitTarget();
- start = taskPosition;
- } else if (position >= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position
- && dockSideBottomRight(dockSide)) {
- dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissEndTarget();
- splitTarget = mSplitLayout.getSnapAlgorithm().getLastSplitTarget();
- start = splitTarget.position;
- }
- if (dismissTarget != null && fraction > 0f
- && isDismissing(splitTarget, position, dockSide)) {
- fraction = calculateParallaxDismissingFraction(fraction, dockSide);
- int offsetPosition = (int) (start + fraction
- * (dismissTarget.position - splitTarget.position));
- int width = taskRect.width();
- int height = taskRect.height();
- switch (dockSide) {
- case WindowManager.DOCKED_LEFT:
- taskRect.left = offsetPosition - width;
- taskRect.right = offsetPosition;
- break;
- case WindowManager.DOCKED_RIGHT:
- taskRect.left = offsetPosition + mDividerSize;
- taskRect.right = offsetPosition + width + mDividerSize;
- break;
- case WindowManager.DOCKED_TOP:
- taskRect.top = offsetPosition - height;
- taskRect.bottom = offsetPosition;
- break;
- case WindowManager.DOCKED_BOTTOM:
- taskRect.top = offsetPosition + mDividerSize;
- taskRect.bottom = offsetPosition + height + mDividerSize;
- break;
- }
- }
- }
-
- /**
- * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
- * slowing down parallax effect
- */
- private static float calculateParallaxDismissingFraction(float fraction, int dockSide) {
- float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
-
- // Less parallax at the top, just because.
- if (dockSide == WindowManager.DOCKED_TOP) {
- result /= 2f;
- }
- return result;
- }
-
- private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) {
- if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) {
- return position < snapTarget.position;
- } else {
- return position > snapTarget.position;
- }
- }
-
- private boolean isDismissTargetPrimary(SnapTarget dismissTarget) {
- return (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
- || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
- && dockSideBottomRight(mDockSide));
- }
-
- /**
- * @return true if and only if {@code dockSide} is top or left
- */
- private static boolean dockSideTopLeft(int dockSide) {
- return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT;
- }
-
- /**
- * @return true if and only if {@code dockSide} is bottom or right
- */
- private static boolean dockSideBottomRight(int dockSide) {
- return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT;
- }
-
- @Override
- public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
- inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
- mHandle.getBottom());
- inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
- mBackground.getRight(), mBackground.getBottom(), Op.UNION);
- }
-
- void onUndockingTask() {
- int dockSide = mSplitLayout.getPrimarySplitSide();
- if (inSplitMode()) {
- startDragging(false /* animate */, false /* touching */);
- SnapTarget target = dockSideTopLeft(dockSide)
- ? mSplitLayout.getSnapAlgorithm().getDismissEndTarget()
- : mSplitLayout.getSnapAlgorithm().getDismissStartTarget();
-
- // Don't start immediately - give a little bit time to settle the drag resize change.
- mExitAnimationRunning = true;
- mExitStartPosition = getCurrentPosition();
- stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */,
- 0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN);
- }
- }
-
- private int calculatePositionForInsetBounds() {
- mSplitLayout.mDisplayLayout.getStableBounds(mTmpRect);
- return DockedDividerUtils.calculatePositionForBounds(mTmpRect, mDockSide, mDividerSize);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerWindowManager.java
deleted file mode 100644
index 2c3ae68e4749..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerWindowManager.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.legacysplitscreen;
-
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static android.view.WindowManager.SHELL_ROOT_LAYER_DIVIDER;
-
-import android.graphics.PixelFormat;
-import android.graphics.Region;
-import android.os.Binder;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.wm.shell.common.SystemWindows;
-
-/**
- * Manages the window parameters of the docked stack divider.
- */
-final class DividerWindowManager {
-
- private static final String WINDOW_TITLE = "DockedStackDivider";
-
- final SystemWindows mSystemWindows;
- private WindowManager.LayoutParams mLp;
- private View mView;
-
- DividerWindowManager(SystemWindows systemWindows) {
- mSystemWindows = systemWindows;
- }
-
- /** Add a divider view */
- void add(View view, int width, int height, int displayId) {
- mLp = new WindowManager.LayoutParams(
- width, height, TYPE_DOCK_DIVIDER,
- FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
- | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
- PixelFormat.TRANSLUCENT);
- mLp.token = new Binder();
- mLp.setTitle(WINDOW_TITLE);
- mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
- mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
- mSystemWindows.addView(view, mLp, displayId, SHELL_ROOT_LAYER_DIVIDER);
- mView = view;
- }
-
- void remove() {
- if (mView != null) {
- mSystemWindows.removeView(mView);
- }
- mView = null;
- }
-
- void setSlippery(boolean slippery) {
- boolean changed = false;
- if (slippery && (mLp.flags & FLAG_SLIPPERY) == 0) {
- mLp.flags |= FLAG_SLIPPERY;
- changed = true;
- } else if (!slippery && (mLp.flags & FLAG_SLIPPERY) != 0) {
- mLp.flags &= ~FLAG_SLIPPERY;
- changed = true;
- }
- if (changed) {
- mSystemWindows.updateViewLayout(mView, mLp);
- }
- }
-
- void setTouchable(boolean touchable) {
- if (mView == null) {
- return;
- }
- boolean changed = false;
- if (!touchable && (mLp.flags & FLAG_NOT_TOUCHABLE) == 0) {
- mLp.flags |= FLAG_NOT_TOUCHABLE;
- changed = true;
- } else if (touchable && (mLp.flags & FLAG_NOT_TOUCHABLE) != 0) {
- mLp.flags &= ~FLAG_NOT_TOUCHABLE;
- changed = true;
- }
- if (changed) {
- mSystemWindows.updateViewLayout(mView, mLp);
- }
- }
-
- /** Sets the touch region to `touchRegion`. Use null to unset.*/
- void setTouchRegion(Region touchRegion) {
- if (mView == null) {
- return;
- }
- mSystemWindows.setTouchableRegion(mView, touchRegion);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivity.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivity.java
deleted file mode 100644
index 4fe28e630114..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivity.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
-import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
-
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.os.Bundle;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.widget.TextView;
-
-import com.android.wm.shell.R;
-
-/**
- * Translucent activity that gets started on top of a task in multi-window to inform the user that
- * we forced the activity below to be resizable.
- *
- * Note: This activity runs on the main thread of the process hosting the Shell lib.
- */
-public class ForcedResizableInfoActivity extends Activity implements OnTouchListener {
-
- public static final String EXTRA_FORCED_RESIZEABLE_REASON = "extra_forced_resizeable_reason";
-
- private static final long DISMISS_DELAY = 2500;
-
- private final Runnable mFinishRunnable = new Runnable() {
- @Override
- public void run() {
- finish();
- }
- };
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.forced_resizable_activity);
- TextView tv = findViewById(com.android.internal.R.id.message);
- int reason = getIntent().getIntExtra(EXTRA_FORCED_RESIZEABLE_REASON, -1);
- String text;
- switch (reason) {
- case FORCED_RESIZEABLE_REASON_SPLIT_SCREEN:
- text = getString(R.string.dock_forced_resizable);
- break;
- case FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY:
- text = getString(R.string.forced_resizable_secondary_display);
- break;
- default:
- throw new IllegalArgumentException("Unexpected forced resizeable reason: "
- + reason);
- }
- tv.setText(text);
- getWindow().setTitle(text);
- getWindow().getDecorView().setOnTouchListener(this);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- getWindow().getDecorView().postDelayed(mFinishRunnable, DISMISS_DELAY);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- finish();
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- finish();
- return true;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- finish();
- return true;
- }
-
- @Override
- public void finish() {
- super.finish();
- overridePendingTransition(0, R.anim.forced_resizable_exit);
- }
-
- @Override
- public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
- // Do nothing
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java
deleted file mode 100644
index 139544f951ce..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-
-import static com.android.wm.shell.legacysplitscreen.ForcedResizableInfoActivity.EXTRA_FORCED_RESIZEABLE_REASON;
-
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.content.Intent;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.widget.Toast;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.ShellExecutor;
-
-import java.util.function.Consumer;
-
-/**
- * Controller that decides when to show the {@link ForcedResizableInfoActivity}.
- */
-final class ForcedResizableInfoActivityController implements DividerView.DividerCallbacks {
-
- private static final String SELF_PACKAGE_NAME = "com.android.systemui";
-
- private static final int TIMEOUT = 1000;
- private final Context mContext;
- private final ShellExecutor mMainExecutor;
- private final ArraySet<PendingTaskRecord> mPendingTasks = new ArraySet<>();
- private final ArraySet<String> mPackagesShownInSession = new ArraySet<>();
- private boolean mDividerDragging;
-
- private final Runnable mTimeoutRunnable = this::showPending;
-
- private final Consumer<Boolean> mDockedStackExistsListener = exists -> {
- if (!exists) {
- mPackagesShownInSession.clear();
- }
- };
-
- /** Record of force resized task that's pending to be handled. */
- private class PendingTaskRecord {
- int mTaskId;
- /**
- * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SPLIT_SCREEN} or
- * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY}
- */
- int mReason;
-
- PendingTaskRecord(int taskId, int reason) {
- this.mTaskId = taskId;
- this.mReason = reason;
- }
- }
-
- ForcedResizableInfoActivityController(Context context,
- LegacySplitScreenController splitScreenController,
- ShellExecutor mainExecutor) {
- mContext = context;
- mMainExecutor = mainExecutor;
- splitScreenController.registerInSplitScreenListener(mDockedStackExistsListener);
- }
-
- @Override
- public void onDraggingStart() {
- mDividerDragging = true;
- mMainExecutor.removeCallbacks(mTimeoutRunnable);
- }
-
- @Override
- public void onDraggingEnd() {
- mDividerDragging = false;
- showPending();
- }
-
- void onAppTransitionFinished() {
- if (!mDividerDragging) {
- showPending();
- }
- }
-
- void activityForcedResizable(String packageName, int taskId, int reason) {
- if (debounce(packageName)) {
- return;
- }
- mPendingTasks.add(new PendingTaskRecord(taskId, reason));
- postTimeout();
- }
-
- void activityDismissingSplitScreen() {
- Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
- Toast.LENGTH_SHORT).show();
- }
-
- void activityLaunchOnSecondaryDisplayFailed() {
- Toast.makeText(mContext, R.string.activity_launch_on_secondary_display_failed_text,
- Toast.LENGTH_SHORT).show();
- }
-
- private void showPending() {
- mMainExecutor.removeCallbacks(mTimeoutRunnable);
- for (int i = mPendingTasks.size() - 1; i >= 0; i--) {
- PendingTaskRecord pendingRecord = mPendingTasks.valueAt(i);
- Intent intent = new Intent(mContext, ForcedResizableInfoActivity.class);
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchTaskId(pendingRecord.mTaskId);
- // Set as task overlay and allow to resume, so that when an app enters split-screen and
- // becomes paused, the overlay will still be shown.
- options.setTaskOverlay(true, true /* canResume */);
- intent.putExtra(EXTRA_FORCED_RESIZEABLE_REASON, pendingRecord.mReason);
- mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
- }
- mPendingTasks.clear();
- }
-
- private void postTimeout() {
- mMainExecutor.removeCallbacks(mTimeoutRunnable);
- mMainExecutor.executeDelayed(mTimeoutRunnable, TIMEOUT);
- }
-
- private boolean debounce(String packageName) {
- if (packageName == null) {
- return false;
- }
-
- // We launch ForcedResizableInfoActivity into a task that was forced resizable, so that
- // triggers another notification. So ignore our own activity.
- if (SELF_PACKAGE_NAME.equals(packageName)) {
- return true;
- }
- boolean debounce = mPackagesShownInSession.contains(packageName);
- mPackagesShownInSession.add(packageName);
- return debounce;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java
deleted file mode 100644
index f201634d3d4a..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java
+++ /dev/null
@@ -1,326 +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.legacysplitscreen;
-
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.util.RotationUtils.rotateBounds;
-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.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.util.TypedValue;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.internal.policy.DockedDividerUtils;
-import com.android.wm.shell.common.DisplayLayout;
-
-/**
- * Handles split-screen related internal display layout. In general, this represents the
- * WM-facing understanding of the splits.
- */
-public class LegacySplitDisplayLayout {
- /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
- * restrict IME adjustment so that a min portion of top stack remains visible.*/
- private static final float ADJUSTED_STACK_FRACTION_MIN = 0.3f;
-
- private static final int DIVIDER_WIDTH_INACTIVE_DP = 4;
-
- LegacySplitScreenTaskListener mTiles;
- DisplayLayout mDisplayLayout;
- Context mContext;
-
- // Lazy stuff
- boolean mResourcesValid = false;
- int mDividerSize;
- int mDividerSizeInactive;
- private DividerSnapAlgorithm mSnapAlgorithm = null;
- private DividerSnapAlgorithm mMinimizedSnapAlgorithm = null;
- Rect mPrimary = null;
- Rect mSecondary = null;
- Rect mAdjustedPrimary = null;
- Rect mAdjustedSecondary = null;
- final Rect mTmpBounds = new Rect();
-
- public LegacySplitDisplayLayout(Context ctx, DisplayLayout dl,
- LegacySplitScreenTaskListener taskTiles) {
- mTiles = taskTiles;
- mDisplayLayout = dl;
- mContext = ctx;
- }
-
- void rotateTo(int newRotation) {
- mDisplayLayout.rotateTo(mContext.getResources(), newRotation);
- final Configuration config = new Configuration();
- config.unset();
- config.orientation = mDisplayLayout.getOrientation();
- Rect tmpRect = new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
- tmpRect.inset(mDisplayLayout.nonDecorInsets());
- config.windowConfiguration.setAppBounds(tmpRect);
- tmpRect.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
- tmpRect.inset(mDisplayLayout.stableInsets());
- config.screenWidthDp = (int) (tmpRect.width() / mDisplayLayout.density());
- config.screenHeightDp = (int) (tmpRect.height() / mDisplayLayout.density());
- mContext = mContext.createConfigurationContext(config);
- mSnapAlgorithm = null;
- mMinimizedSnapAlgorithm = null;
- mResourcesValid = false;
- }
-
- private void updateResources() {
- if (mResourcesValid) {
- return;
- }
- mResourcesValid = true;
- Resources res = mContext.getResources();
- mDividerSize = DockedDividerUtils.getDividerSize(res,
- DockedDividerUtils.getDividerInsets(res));
- mDividerSizeInactive = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, DIVIDER_WIDTH_INACTIVE_DP, res.getDisplayMetrics());
- }
-
- int getPrimarySplitSide() {
- switch (mDisplayLayout.getNavigationBarPosition(mContext.getResources())) {
- case DisplayLayout.NAV_BAR_BOTTOM:
- return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP;
- case DisplayLayout.NAV_BAR_LEFT:
- return DOCKED_RIGHT;
- case DisplayLayout.NAV_BAR_RIGHT:
- return DOCKED_LEFT;
- default:
- return DOCKED_INVALID;
- }
- }
-
- DividerSnapAlgorithm getSnapAlgorithm() {
- if (mSnapAlgorithm == null) {
- updateResources();
- boolean isHorizontalDivision = !mDisplayLayout.isLandscape();
- mSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(),
- mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize,
- isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide());
- }
- return mSnapAlgorithm;
- }
-
- DividerSnapAlgorithm getMinimizedSnapAlgorithm(boolean homeStackResizable) {
- if (mMinimizedSnapAlgorithm == null) {
- updateResources();
- boolean isHorizontalDivision = !mDisplayLayout.isLandscape();
- mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(),
- mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize,
- isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide(),
- true /* isMinimized */, homeStackResizable);
- }
- return mMinimizedSnapAlgorithm;
- }
-
- /**
- * Resize primary bounds and secondary bounds by divider position.
- *
- * @param position divider position.
- * @return true if calculated bounds changed.
- */
- boolean resizeSplits(int position) {
- mPrimary = mPrimary == null ? new Rect() : mPrimary;
- mSecondary = mSecondary == null ? new Rect() : mSecondary;
- int dockSide = getPrimarySplitSide();
- boolean boundsChanged;
-
- mTmpBounds.set(mPrimary);
- DockedDividerUtils.calculateBoundsForPosition(position, dockSide, mPrimary,
- mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize);
- boundsChanged = !mPrimary.equals(mTmpBounds);
-
- mTmpBounds.set(mSecondary);
- DockedDividerUtils.calculateBoundsForPosition(position,
- DockedDividerUtils.invertDockSide(dockSide), mSecondary, mDisplayLayout.width(),
- mDisplayLayout.height(), mDividerSize);
- boundsChanged |= !mSecondary.equals(mTmpBounds);
- return boundsChanged;
- }
-
- void resizeSplits(int position, WindowContainerTransaction t) {
- if (resizeSplits(position)) {
- t.setBounds(mTiles.mPrimary.token, mPrimary);
- t.setBounds(mTiles.mSecondary.token, mSecondary);
-
- t.setSmallestScreenWidthDp(mTiles.mPrimary.token,
- getSmallestWidthDpForBounds(mContext, mDisplayLayout, mPrimary));
- t.setSmallestScreenWidthDp(mTiles.mSecondary.token,
- getSmallestWidthDpForBounds(mContext, mDisplayLayout, mSecondary));
- }
- }
-
- Rect calcResizableMinimizedHomeStackBounds() {
- DividerSnapAlgorithm.SnapTarget miniMid =
- getMinimizedSnapAlgorithm(true /* resizable */).getMiddleTarget();
- Rect homeBounds = new Rect();
- DockedDividerUtils.calculateBoundsForPosition(miniMid.position,
- DockedDividerUtils.invertDockSide(getPrimarySplitSide()), homeBounds,
- mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize);
- return homeBounds;
- }
-
- /**
- * Updates the adjustment depending on it's current state.
- */
- void updateAdjustedBounds(int currImeTop, int hiddenTop, int shownTop) {
- adjustForIME(mDisplayLayout, currImeTop, hiddenTop, shownTop, mDividerSize,
- mDividerSizeInactive, mPrimary, mSecondary);
- }
-
- /** Assumes top/bottom split. Splits are not adjusted for left/right splits. */
- private void adjustForIME(DisplayLayout dl, int currImeTop, int hiddenTop, int shownTop,
- int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) {
- if (mAdjustedPrimary == null) {
- mAdjustedPrimary = new Rect();
- mAdjustedSecondary = new Rect();
- }
-
- final Rect displayStableRect = new Rect();
- dl.getStableBounds(displayStableRect);
-
- final float shownFraction = ((float) (currImeTop - hiddenTop)) / (shownTop - hiddenTop);
- final int currDividerWidth =
- (int) (dividerWidthInactive * shownFraction + dividerWidth * (1.f - shownFraction));
-
- // Calculate the highest we can move the bottom of the top stack to keep 30% visible.
- final int minTopStackBottom = displayStableRect.top
- + (int) ((mPrimary.bottom - displayStableRect.top) * ADJUSTED_STACK_FRACTION_MIN);
- // Based on that, calculate the maximum amount we'll allow the ime to shift things.
- final int maxOffset = mPrimary.bottom - minTopStackBottom;
- // Calculate how much we would shift things without limits (basically the height of ime).
- final int desiredOffset = hiddenTop - shownTop;
- // Calculate an "adjustedTop" which is the currImeTop but restricted by our constraints.
- // We want an effect where the adjustment only occurs during the "highest" portion of the
- // ime animation. This is done by shifting the adjustment values by the difference in
- // offsets (effectively playing the whole adjustment animation some fixed amount of pixels
- // below the ime top).
- final int topCorrection = Math.max(0, desiredOffset - maxOffset);
- final int adjustedTop = currImeTop + topCorrection;
- // The actual yOffset is the distance between adjustedTop and the bottom of the display.
- // Since our adjustedTop values are playing "below" the ime, we clamp at 0 so we only
- // see adjustment upward.
- final int yOffset = Math.max(0, dl.height() - adjustedTop);
-
- // TOP
- // Reduce the offset by an additional small amount to squish the divider bar.
- mAdjustedPrimary.set(primaryBounds);
- mAdjustedPrimary.offset(0, -yOffset + (dividerWidth - currDividerWidth));
-
- // BOTTOM
- mAdjustedSecondary.set(secondaryBounds);
- mAdjustedSecondary.offset(0, -yOffset);
- }
-
- static int getSmallestWidthDpForBounds(@NonNull Context context, DisplayLayout dl,
- Rect bounds) {
- int dividerSize = DockedDividerUtils.getDividerSize(context.getResources(),
- DockedDividerUtils.getDividerInsets(context.getResources()));
-
- int minWidth = Integer.MAX_VALUE;
-
- // Go through all screen orientations and find the orientation in which the task has the
- // smallest width.
- Rect tmpRect = new Rect();
- Rect rotatedDisplayRect = new Rect();
- Rect displayRect = new Rect(0, 0, dl.width(), dl.height());
-
- DisplayLayout tmpDL = new DisplayLayout();
- for (int rotation = 0; rotation < 4; rotation++) {
- tmpDL.set(dl);
- tmpDL.rotateTo(context.getResources(), rotation);
- DividerSnapAlgorithm snap = initSnapAlgorithmForRotation(context, tmpDL, dividerSize);
-
- tmpRect.set(bounds);
- rotateBounds(tmpRect, displayRect, dl.rotation(), rotation);
- rotatedDisplayRect.set(0, 0, tmpDL.width(), tmpDL.height());
- final int dockSide = getPrimarySplitSide(tmpRect, rotatedDisplayRect,
- tmpDL.getOrientation());
- final int position = DockedDividerUtils.calculatePositionForBounds(tmpRect, dockSide,
- dividerSize);
-
- final int snappedPosition =
- snap.calculateNonDismissingSnapTarget(position).position;
- DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, tmpRect,
- tmpDL.width(), tmpDL.height(), dividerSize);
- Rect insettedDisplay = new Rect(rotatedDisplayRect);
- insettedDisplay.inset(tmpDL.stableInsets());
- tmpRect.intersect(insettedDisplay);
- minWidth = Math.min(tmpRect.width(), minWidth);
- }
- return (int) (minWidth / dl.density());
- }
-
- static DividerSnapAlgorithm initSnapAlgorithmForRotation(Context context, DisplayLayout dl,
- int dividerSize) {
- final Configuration config = new Configuration();
- config.unset();
- config.orientation = dl.getOrientation();
- Rect tmpRect = new Rect(0, 0, dl.width(), dl.height());
- tmpRect.inset(dl.nonDecorInsets());
- config.windowConfiguration.setAppBounds(tmpRect);
- tmpRect.set(0, 0, dl.width(), dl.height());
- tmpRect.inset(dl.stableInsets());
- config.screenWidthDp = (int) (tmpRect.width() / dl.density());
- config.screenHeightDp = (int) (tmpRect.height() / dl.density());
- final Context rotationContext = context.createConfigurationContext(config);
- return new DividerSnapAlgorithm(
- rotationContext.getResources(), dl.width(), dl.height(), dividerSize,
- config.orientation == ORIENTATION_PORTRAIT, dl.stableInsets());
- }
-
- /**
- * Get the current primary-split side. Determined by its location of {@param bounds} within
- * {@param displayRect} but if both are the same, it will try to dock to each side and determine
- * if allowed in its respected {@param orientation}.
- *
- * @param bounds bounds of the primary split task to get which side is docked
- * @param displayRect bounds of the display that contains the primary split task
- * @param orientation the origination of device
- * @return current primary-split side
- */
- static int getPrimarySplitSide(Rect bounds, Rect displayRect, int orientation) {
- if (orientation == ORIENTATION_PORTRAIT) {
- // Portrait mode, docked either at the top or the bottom.
- final int diff = (displayRect.bottom - bounds.bottom) - (bounds.top - displayRect.top);
- if (diff < 0) {
- return DOCKED_BOTTOM;
- } else {
- // Top is default
- return DOCKED_TOP;
- }
- } else if (orientation == ORIENTATION_LANDSCAPE) {
- // Landscape mode, docked either on the left or on the right.
- final int diff = (displayRect.right - bounds.right) - (bounds.left - displayRect.left);
- if (diff < 0) {
- return DOCKED_RIGHT;
- }
- return DOCKED_LEFT;
- }
- return DOCKED_INVALID;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreen.java
deleted file mode 100644
index 499a9c5fa631..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreen.java
+++ /dev/null
@@ -1,85 +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.legacysplitscreen;
-
-import android.graphics.Rect;
-import android.window.WindowContainerToken;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-import java.io.PrintWriter;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-
-/**
- * Interface to engage split screen feature.
- */
-@ExternalThread
-public interface LegacySplitScreen {
- /** Called when keyguard showing state changed. */
- void onKeyguardVisibilityChanged(boolean isShowing);
-
- /** Returns {@link DividerView}. */
- DividerView getDividerView();
-
- /** Returns {@code true} if one of the split screen is in minimized mode. */
- boolean isMinimized();
-
- /** Returns {@code true} if the home stack is resizable. */
- boolean isHomeStackResizable();
-
- /** Returns {@code true} if the divider is visible. */
- boolean isDividerVisible();
-
- /** Switch to minimized state if appropriate. */
- void setMinimized(boolean minimized);
-
- /** Called when there's a task undocking. */
- void onUndockingTask();
-
- /** Called when app transition finished. */
- void onAppTransitionFinished();
-
- /** Dumps current status of Split Screen. */
- void dump(PrintWriter pw);
-
- /** Registers listener that gets called whenever the existence of the divider changes. */
- void registerInSplitScreenListener(Consumer<Boolean> listener);
-
- /** Unregisters listener that gets called whenever the existence of the divider changes. */
- void unregisterInSplitScreenListener(Consumer<Boolean> listener);
-
- /** Registers listener that gets called whenever the split screen bounds changes. */
- void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener);
-
- /** @return the container token for the secondary split root task. */
- WindowContainerToken getSecondaryRoot();
-
- /**
- * Splits the primary task if feasible, this is to preserve legacy way to toggle split screen.
- * Like triggering split screen through long pressing recents app button or through
- * {@link android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN}.
- *
- * @return {@code true} if it successes to split the primary task.
- */
- boolean splitPrimaryTask();
-
- /**
- * Exits the split to make the primary task fullscreen.
- */
- void dismissSplitToPrimaryTask();
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
deleted file mode 100644
index 67e487de0993..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ /dev/null
@@ -1,762 +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.legacysplitscreen;
-
-import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.animation.AnimationHandler;
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.util.Slog;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Toast;
-import android.window.TaskOrganizer;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayChangeController;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TaskStackListenerCallback;
-import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.transition.Transitions;
-
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-
-/**
- * Controls split screen feature.
- */
-public class LegacySplitScreenController implements DisplayController.OnDisplaysChangedListener {
- static final boolean DEBUG = false;
-
- private static final String TAG = "SplitScreenCtrl";
- private static final int DEFAULT_APP_TRANSITION_DURATION = 336;
-
- private final Context mContext;
- private final DisplayChangeController.OnDisplayChangingListener mRotationController;
- private final DisplayController mDisplayController;
- private final DisplayImeController mImeController;
- private final DividerImeController mImePositionProcessor;
- private final DividerState mDividerState = new DividerState();
- private final ForcedResizableInfoActivityController mForcedResizableController;
- private final ShellExecutor mMainExecutor;
- private final AnimationHandler mSfVsyncAnimationHandler;
- private final LegacySplitScreenTaskListener mSplits;
- private final SystemWindows mSystemWindows;
- final TransactionPool mTransactionPool;
- private final WindowManagerProxy mWindowManagerProxy;
- private final TaskOrganizer mTaskOrganizer;
- private final SplitScreenImpl mImpl = new SplitScreenImpl();
-
- private final CopyOnWriteArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners
- = new CopyOnWriteArrayList<>();
- private final ArrayList<WeakReference<BiConsumer<Rect, Rect>>> mBoundsChangedListeners =
- new ArrayList<>();
-
-
- private DividerWindowManager mWindowManager;
- private DividerView mView;
-
- // Keeps track of real-time split geometry including snap positions and ime adjustments
- private LegacySplitDisplayLayout mSplitLayout;
-
- // Transient: this contains the layout calculated for a new rotation requested by WM. This is
- // kept around so that we can wait for a matching configuration change and then use the exact
- // layout that we sent back to WM.
- private LegacySplitDisplayLayout mRotateSplitLayout;
-
- private boolean mIsKeyguardShowing;
- private boolean mVisible = false;
- private volatile boolean mMinimized = false;
- private volatile boolean mAdjustedForIme = false;
- private boolean mHomeStackResizable = false;
-
- public LegacySplitScreenController(Context context,
- DisplayController displayController, SystemWindows systemWindows,
- DisplayImeController imeController, TransactionPool transactionPool,
- ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
- TaskStackListenerImpl taskStackListener, Transitions transitions,
- ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) {
- mContext = context;
- mDisplayController = displayController;
- mSystemWindows = systemWindows;
- mImeController = imeController;
- mMainExecutor = mainExecutor;
- mSfVsyncAnimationHandler = sfVsyncAnimationHandler;
- mForcedResizableController = new ForcedResizableInfoActivityController(context, this,
- mainExecutor);
- mTransactionPool = transactionPool;
- mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer);
- mTaskOrganizer = shellTaskOrganizer;
- mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, transitions,
- syncQueue);
- mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mMainExecutor,
- shellTaskOrganizer);
- mRotationController =
- (display, fromRotation, toRotation, wct) -> {
- if (!mSplits.isSplitScreenSupported() || mWindowManagerProxy == null) {
- return;
- }
- WindowContainerTransaction t = new WindowContainerTransaction();
- DisplayLayout displayLayout =
- new DisplayLayout(mDisplayController.getDisplayLayout(display));
- LegacySplitDisplayLayout sdl =
- new LegacySplitDisplayLayout(mContext, displayLayout, mSplits);
- sdl.rotateTo(toRotation);
- mRotateSplitLayout = sdl;
- // snap resets to middle target when not minimized and rotation changed.
- final int position = mMinimized ? mView.mSnapTargetBeforeMinimized.position
- : sdl.getSnapAlgorithm().getMiddleTarget().position;
- DividerSnapAlgorithm snap = sdl.getSnapAlgorithm();
- final DividerSnapAlgorithm.SnapTarget target =
- snap.calculateNonDismissingSnapTarget(position);
- sdl.resizeSplits(target.position, t);
-
- if (isSplitActive() && mHomeStackResizable) {
- mWindowManagerProxy
- .applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t);
- }
- if (mWindowManagerProxy.queueSyncTransactionIfWaiting(t)) {
- // Because sync transactions are serialized, its possible for an "older"
- // bounds-change to get applied after a screen rotation. In that case, we
- // want to actually defer on that rather than apply immediately. Of course,
- // this means that the bounds may not change until after the rotation so
- // the user might see some artifacts. This should be rare.
- Slog.w(TAG, "Screen rotated while other operations were pending, this may"
- + " result in some graphical artifacts.");
- } else {
- wct.merge(t, true /* transfer */);
- }
- };
-
- mWindowManager = new DividerWindowManager(mSystemWindows);
-
- // No need to listen to display window container or create root tasks if the device is not
- // using legacy split screen.
- if (!context.getResources().getBoolean(com.android.internal.R.bool.config_useLegacySplit)) {
- return;
- }
-
-
- mDisplayController.addDisplayWindowListener(this);
- // Don't initialize the divider or anything until we get the default display.
-
- taskStackListener.addListener(
- new TaskStackListenerCallback() {
- @Override
- public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (!wasVisible || task.getWindowingMode()
- != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || !mSplits.isSplitScreenSupported()) {
- return;
- }
-
- if (isMinimized()) {
- onUndockingTask();
- }
- }
-
- @Override
- public void onActivityForcedResizable(String packageName, int taskId,
- int reason) {
- mForcedResizableController.activityForcedResizable(packageName, taskId,
- reason);
- }
-
- @Override
- public void onActivityDismissingDockedStack() {
- mForcedResizableController.activityDismissingSplitScreen();
- }
-
- @Override
- public void onActivityLaunchOnSecondaryDisplayFailed() {
- mForcedResizableController.activityLaunchOnSecondaryDisplayFailed();
- }
- });
- }
-
- public LegacySplitScreen asLegacySplitScreen() {
- return mImpl;
- }
-
- public void onSplitScreenSupported() {
- // Set starting tile bounds based on middle target
- final WindowContainerTransaction tct = new WindowContainerTransaction();
- int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
- mSplitLayout.resizeSplits(midPos, tct);
- mTaskOrganizer.applyTransaction(tct);
- }
-
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (!isSplitActive() || mView == null) {
- return;
- }
- mView.setHidden(showing);
- mIsKeyguardShowing = showing;
- }
-
- @Override
- public void onDisplayAdded(int displayId) {
- if (displayId != DEFAULT_DISPLAY) {
- return;
- }
- mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
- mDisplayController.getDisplayLayout(displayId), mSplits);
- mImeController.addPositionProcessor(mImePositionProcessor);
- mDisplayController.addDisplayChangingController(mRotationController);
- if (!ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)) {
- removeDivider();
- return;
- }
- try {
- mSplits.init();
- } catch (Exception e) {
- Slog.e(TAG, "Failed to register docked stack listener", e);
- removeDivider();
- return;
- }
- }
-
- @Override
- public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- if (displayId != DEFAULT_DISPLAY || !mSplits.isSplitScreenSupported()) {
- return;
- }
- mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
- mDisplayController.getDisplayLayout(displayId), mSplits);
- if (mRotateSplitLayout == null) {
- int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
- final WindowContainerTransaction tct = new WindowContainerTransaction();
- mSplitLayout.resizeSplits(midPos, tct);
- mTaskOrganizer.applyTransaction(tct);
- } else if (mSplitLayout.mDisplayLayout.rotation()
- == mRotateSplitLayout.mDisplayLayout.rotation()) {
- mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary);
- mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary);
- mRotateSplitLayout = null;
- }
- if (isSplitActive()) {
- update(newConfig);
- }
- }
-
- public boolean isMinimized() {
- return mMinimized;
- }
-
- public boolean isHomeStackResizable() {
- return mHomeStackResizable;
- }
-
- public DividerView getDividerView() {
- return mView;
- }
-
- public boolean isDividerVisible() {
- return mView != null && mView.getVisibility() == View.VISIBLE;
- }
-
- /**
- * This indicates that at-least one of the splits has content. This differs from
- * isDividerVisible because the divider is only visible once *everything* is in split mode
- * while this only cares if some things are (eg. while entering/exiting as well).
- */
- public boolean isSplitActive() {
- return mSplits.mPrimary != null && mSplits.mSecondary != null
- && (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
- || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED);
- }
-
- public void addDivider(Configuration configuration) {
- Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
- mView = (DividerView)
- LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
- mView.setAnimationHandler(mSfVsyncAnimationHandler);
- DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
- mView.injectDependencies(this, mWindowManager, mDividerState, mForcedResizableController,
- mSplits, mSplitLayout, mImePositionProcessor, mWindowManagerProxy);
- mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
- mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null /* transaction */);
- final int size = dctx.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_thickness);
- final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
- final int width = landscape ? size : displayLayout.width();
- final int height = landscape ? displayLayout.height() : size;
- mWindowManager.add(mView, width, height, mContext.getDisplayId());
- }
-
- public void removeDivider() {
- if (mView != null) {
- mView.onDividerRemoved();
- }
- mWindowManager.remove();
- }
-
- public void update(Configuration configuration) {
- final boolean isDividerHidden = mView != null && mIsKeyguardShowing;
-
- removeDivider();
- addDivider(configuration);
-
- if (mMinimized) {
- mView.setMinimizedDockStack(true, mHomeStackResizable, null /* transaction */);
- updateTouchable();
- }
- mView.setHidden(isDividerHidden);
- }
-
- public void onTaskVanished() {
- removeDivider();
- }
-
- public void updateVisibility(final boolean visible) {
- if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
- if (mVisible != visible) {
- mVisible = visible;
- mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-
- if (visible) {
- mView.enterSplitMode(mHomeStackResizable);
- // Update state because animations won't finish.
- mWindowManagerProxy.runInSync(
- t -> mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, t));
-
- } else {
- mView.exitSplitMode();
- mWindowManagerProxy.runInSync(
- t -> mView.setMinimizedDockStack(false, mHomeStackResizable, t));
- }
- // Notify existence listeners
- synchronized (mDockedStackExistsListeners) {
- mDockedStackExistsListeners.removeIf(wf -> {
- Consumer<Boolean> l = wf.get();
- if (l != null) l.accept(visible);
- return l == null;
- });
- }
- }
- }
-
- public void setMinimized(final boolean minimized) {
- if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
- mMainExecutor.execute(() -> {
- if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
- if (!mVisible) {
- return;
- }
- setHomeMinimized(minimized);
- });
- }
-
- public void setHomeMinimized(final boolean minimized) {
- if (DEBUG) {
- Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:"
- + mHomeStackResizable + " split:" + isDividerVisible());
- }
- WindowContainerTransaction wct = new WindowContainerTransaction();
- final boolean minimizedChanged = mMinimized != minimized;
- // Update minimized state
- if (minimizedChanged) {
- mMinimized = minimized;
- }
- // Always set this because we could be entering split when mMinimized is already true
- wct.setFocusable(mSplits.mPrimary.token, !mMinimized);
-
- // Sync state to DividerView if it exists.
- if (mView != null) {
- final int displayId = mView.getDisplay() != null
- ? mView.getDisplay().getDisplayId() : DEFAULT_DISPLAY;
- // pause ime here (before updateMinimizedDockedStack)
- if (mMinimized) {
- mImePositionProcessor.pause(displayId);
- }
- if (minimizedChanged) {
- // This conflicts with IME adjustment, so only call it when things change.
- mView.setMinimizedDockStack(minimized, getAnimDuration(), mHomeStackResizable);
- }
- if (!mMinimized) {
- // afterwards so it can end any animations started in view
- mImePositionProcessor.resume(displayId);
- }
- }
- updateTouchable();
-
- // If we are only setting focusability, a sync transaction isn't necessary (in fact it
- // can interrupt other animations), so see if it can be submitted on pending instead.
- if (!mWindowManagerProxy.queueSyncTransactionIfWaiting(wct)) {
- mTaskOrganizer.applyTransaction(wct);
- }
- }
-
- public void setAdjustedForIme(boolean adjustedForIme) {
- if (mAdjustedForIme == adjustedForIme) {
- return;
- }
- mAdjustedForIme = adjustedForIme;
- updateTouchable();
- }
-
- public void updateTouchable() {
- mWindowManager.setTouchable(!mAdjustedForIme);
- }
-
- public void onUndockingTask() {
- if (mView != null) {
- mView.onUndockingTask();
- }
- }
-
- public void onAppTransitionFinished() {
- if (mView == null) {
- return;
- }
- mForcedResizableController.onAppTransitionFinished();
- }
-
- public void dump(PrintWriter pw) {
- pw.print(" mVisible="); pw.println(mVisible);
- pw.print(" mMinimized="); pw.println(mMinimized);
- pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme);
- }
-
- public long getAnimDuration() {
- float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- mContext.getResources().getFloat(
- com.android.internal.R.dimen
- .config_appTransitionAnimationDurationScaleDefault));
- final long transitionDuration = DEFAULT_APP_TRANSITION_DURATION;
- return (long) (transitionDuration * transitionScale);
- }
-
- public void registerInSplitScreenListener(Consumer<Boolean> listener) {
- listener.accept(isDividerVisible());
- synchronized (mDockedStackExistsListeners) {
- mDockedStackExistsListeners.add(new WeakReference<>(listener));
- }
- }
-
- public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
- synchronized (mDockedStackExistsListeners) {
- for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) {
- if (mDockedStackExistsListeners.get(i) == listener) {
- mDockedStackExistsListeners.remove(i);
- }
- }
- }
- }
-
- public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
- synchronized (mBoundsChangedListeners) {
- mBoundsChangedListeners.add(new WeakReference<>(listener));
- }
- }
-
- public boolean splitPrimaryTask() {
- try {
- if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED) {
- return false;
- }
- } catch (RemoteException e) {
- return false;
- }
- if (isSplitActive() || mSplits.mPrimary == null) {
- return false;
- }
-
- // Try fetching the top running task.
- final List<RunningTaskInfo> runningTasks =
- ActivityTaskManager.getInstance().getTasks(1 /* maxNum */);
- if (runningTasks == null || runningTasks.isEmpty()) {
- return false;
- }
- // Note: The set of running tasks from the system is ordered by recency.
- final RunningTaskInfo topRunningTask = runningTasks.get(0);
- final int activityType = topRunningTask.getActivityType();
- if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
- return false;
- }
-
- if (!topRunningTask.supportsSplitScreenMultiWindow) {
- Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
- Toast.LENGTH_SHORT).show();
- return false;
- }
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Clear out current windowing mode before reparenting to split task.
- wct.setWindowingMode(topRunningTask.token, WINDOWING_MODE_UNDEFINED);
- wct.reparent(topRunningTask.token, mSplits.mPrimary.token, true /* onTop */);
- mWindowManagerProxy.applySyncTransaction(wct);
- return true;
- }
-
- public void dismissSplitToPrimaryTask() {
- startDismissSplit(true /* toPrimaryTask */);
- }
-
- /** Notifies the bounds of split screen changed. */
- public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
- synchronized (mBoundsChangedListeners) {
- mBoundsChangedListeners.removeIf(wf -> {
- BiConsumer<Rect, Rect> l = wf.get();
- if (l != null) l.accept(secondaryWindowBounds, secondaryWindowInsets);
- return l == null;
- });
- }
- }
-
- public void startEnterSplit() {
- update(mDisplayController.getDisplayContext(
- mContext.getDisplayId()).getResources().getConfiguration());
- // Set resizable directly here because applyEnterSplit already resizes home stack.
- mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits,
- mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout);
- }
-
- public void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
- // Set resizable directly here because buildEnterSplit already resizes home stack.
- mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits,
- mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout);
- }
-
- public void finishEnterSplitTransition(boolean minimized) {
- update(mDisplayController.getDisplayContext(
- mContext.getDisplayId()).getResources().getConfiguration());
- if (minimized) {
- ensureMinimizedSplit();
- } else {
- ensureNormalSplit();
- }
- }
-
- public void startDismissSplit(boolean toPrimaryTask) {
- startDismissSplit(toPrimaryTask, false /* snapped */);
- }
-
- public void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mSplits.getSplitTransitions().dismissSplit(
- mSplits, mSplitLayout, !toPrimaryTask, snapped);
- } else {
- mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask);
- onDismissSplit();
- }
- }
-
- public void onDismissSplit() {
- updateVisibility(false /* visible */);
- mMinimized = false;
- // Resets divider bar position to undefined, so new divider bar will apply default position
- // next time entering split mode.
- mDividerState.mRatioPositionBeforeMinimized = 0;
- removeDivider();
- mImePositionProcessor.reset();
- }
-
- public void ensureMinimizedSplit() {
- setHomeMinimized(true /* minimized */);
- if (mView != null && !isDividerVisible()) {
- // Wasn't in split-mode yet, so enter now.
- if (DEBUG) {
- Slog.d(TAG, " entering split mode with minimized=true");
- }
- updateVisibility(true /* visible */);
- }
- }
-
- public void ensureNormalSplit() {
- setHomeMinimized(false /* minimized */);
- if (mView != null && !isDividerVisible()) {
- // Wasn't in split-mode, so enter now.
- if (DEBUG) {
- Slog.d(TAG, " enter split mode unminimized ");
- }
- updateVisibility(true /* visible */);
- }
- }
-
- public LegacySplitDisplayLayout getSplitLayout() {
- return mSplitLayout;
- }
-
- public WindowManagerProxy getWmProxy() {
- return mWindowManagerProxy;
- }
-
- public WindowContainerToken getSecondaryRoot() {
- if (mSplits == null || mSplits.mSecondary == null) {
- return null;
- }
- return mSplits.mSecondary.token;
- }
-
- private class SplitScreenImpl implements LegacySplitScreen {
- @Override
- public boolean isMinimized() {
- return mMinimized;
- }
-
- @Override
- public boolean isHomeStackResizable() {
- return mHomeStackResizable;
- }
-
- /**
- * TODO: Remove usage from outside the shell.
- */
- @Override
- public DividerView getDividerView() {
- return LegacySplitScreenController.this.getDividerView();
- }
-
- @Override
- public boolean isDividerVisible() {
- boolean[] result = new boolean[1];
- try {
- mMainExecutor.executeBlocking(() -> {
- result[0] = LegacySplitScreenController.this.isDividerVisible();
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to get divider visible");
- }
- return result[0];
- }
-
- @Override
- public void onKeyguardVisibilityChanged(boolean isShowing) {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.onKeyguardVisibilityChanged(isShowing);
- });
- }
-
- @Override
- public void setMinimized(boolean minimized) {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.setMinimized(minimized);
- });
- }
-
- @Override
- public void onUndockingTask() {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.onUndockingTask();
- });
- }
-
- @Override
- public void onAppTransitionFinished() {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.onAppTransitionFinished();
- });
- }
-
- @Override
- public void registerInSplitScreenListener(Consumer<Boolean> listener) {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.registerInSplitScreenListener(listener);
- });
- }
-
- @Override
- public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.unregisterInSplitScreenListener(listener);
- });
- }
-
- @Override
- public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.registerBoundsChangeListener(listener);
- });
- }
-
- @Override
- public WindowContainerToken getSecondaryRoot() {
- WindowContainerToken[] result = new WindowContainerToken[1];
- try {
- mMainExecutor.executeBlocking(() -> {
- result[0] = LegacySplitScreenController.this.getSecondaryRoot();
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to get secondary root");
- }
- return result[0];
- }
-
- @Override
- public boolean splitPrimaryTask() {
- boolean[] result = new boolean[1];
- try {
- mMainExecutor.executeBlocking(() -> {
- result[0] = LegacySplitScreenController.this.splitPrimaryTask();
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to split primary task");
- }
- return result[0];
- }
-
- @Override
- public void dismissSplitToPrimaryTask() {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.dismissSplitToPrimaryTask();
- });
- }
-
- @Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> {
- LegacySplitScreenController.this.dump(pw);
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to dump LegacySplitScreenController in 2s");
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
deleted file mode 100644
index d2f42c39acd5..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
+++ /dev/null
@@ -1,376 +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.legacysplitscreen;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.TaskOrganizer;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.SurfaceUtils;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.transition.Transitions;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
- private static final String TAG = LegacySplitScreenTaskListener.class.getSimpleName();
- private static final boolean DEBUG = LegacySplitScreenController.DEBUG;
-
- private final ShellTaskOrganizer mTaskOrganizer;
- private final SyncTransactionQueue mSyncQueue;
- private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>();
-
- // TODO(shell-transitions): Remove when switched to shell-transitions.
- private final SparseArray<Point> mPositionByTaskId = new SparseArray<>();
-
- RunningTaskInfo mPrimary;
- RunningTaskInfo mSecondary;
- SurfaceControl mPrimarySurface;
- SurfaceControl mSecondarySurface;
- SurfaceControl mPrimaryDim;
- SurfaceControl mSecondaryDim;
- Rect mHomeBounds = new Rect();
- final LegacySplitScreenController mSplitScreenController;
- private boolean mSplitScreenSupported = false;
-
- final SurfaceSession mSurfaceSession = new SurfaceSession();
-
- private final LegacySplitScreenTransitions mSplitTransitions;
-
- LegacySplitScreenTaskListener(LegacySplitScreenController splitScreenController,
- ShellTaskOrganizer shellTaskOrganizer,
- Transitions transitions,
- SyncTransactionQueue syncQueue) {
- mSplitScreenController = splitScreenController;
- mTaskOrganizer = shellTaskOrganizer;
- mSplitTransitions = new LegacySplitScreenTransitions(splitScreenController.mTransactionPool,
- transitions, mSplitScreenController, this);
- transitions.addHandler(mSplitTransitions);
- mSyncQueue = syncQueue;
- }
-
- void init() {
- synchronized (this) {
- try {
- mTaskOrganizer.createRootTask(
- DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, this);
- mTaskOrganizer.createRootTask(
- DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, this);
- } catch (Exception e) {
- // teardown to prevent callbacks
- mTaskOrganizer.removeListener(this);
- throw e;
- }
- }
- }
-
- boolean isSplitScreenSupported() {
- return mSplitScreenSupported;
- }
-
- SurfaceControl.Transaction getTransaction() {
- return mSplitScreenController.mTransactionPool.acquire();
- }
-
- void releaseTransaction(SurfaceControl.Transaction t) {
- mSplitScreenController.mTransactionPool.release(t);
- }
-
- TaskOrganizer getTaskOrganizer() {
- return mTaskOrganizer;
- }
-
- LegacySplitScreenTransitions getSplitTransitions() {
- return mSplitTransitions;
- }
-
- @Override
- public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
- synchronized (this) {
- if (taskInfo.hasParentTask()) {
- handleChildTaskAppeared(taskInfo, leash);
- return;
- }
-
- final int winMode = taskInfo.getWindowingMode();
- if (winMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- ProtoLog.v(WM_SHELL_TASK_ORG,
- "%s onTaskAppeared Primary taskId=%d", TAG, taskInfo.taskId);
- mPrimary = taskInfo;
- mPrimarySurface = leash;
- } else if (winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
- ProtoLog.v(WM_SHELL_TASK_ORG,
- "%s onTaskAppeared Secondary taskId=%d", TAG, taskInfo.taskId);
- mSecondary = taskInfo;
- mSecondarySurface = leash;
- } else {
- ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared unknown taskId=%d winMode=%d",
- TAG, taskInfo.taskId, winMode);
- }
-
- if (!mSplitScreenSupported && mPrimarySurface != null && mSecondarySurface != null) {
- mSplitScreenSupported = true;
- mSplitScreenController.onSplitScreenSupported();
- ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared Supported", TAG);
-
- // Initialize dim surfaces:
- SurfaceControl.Transaction t = getTransaction();
- mPrimaryDim = SurfaceUtils.makeDimLayer(
- t, mPrimarySurface, "Primary Divider Dim", mSurfaceSession);
- mSecondaryDim = SurfaceUtils.makeDimLayer(
- t, mSecondarySurface, "Secondary Divider Dim", mSurfaceSession);
- t.apply();
- releaseTransaction(t);
- }
- }
- }
-
- @Override
- public void onTaskVanished(RunningTaskInfo taskInfo) {
- synchronized (this) {
- mPositionByTaskId.remove(taskInfo.taskId);
- if (taskInfo.hasParentTask()) {
- mLeashByTaskId.remove(taskInfo.taskId);
- return;
- }
-
- final boolean isPrimaryTask = mPrimary != null
- && taskInfo.token.equals(mPrimary.token);
- final boolean isSecondaryTask = mSecondary != null
- && taskInfo.token.equals(mSecondary.token);
-
- if (mSplitScreenSupported && (isPrimaryTask || isSecondaryTask)) {
- mSplitScreenSupported = false;
-
- SurfaceControl.Transaction t = getTransaction();
- t.remove(mPrimaryDim);
- t.remove(mSecondaryDim);
- t.remove(mPrimarySurface);
- t.remove(mSecondarySurface);
- t.apply();
- releaseTransaction(t);
-
- mSplitScreenController.onTaskVanished();
- }
- }
- }
-
- @Override
- public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- if (taskInfo.displayId != DEFAULT_DISPLAY) {
- return;
- }
- synchronized (this) {
- if (!taskInfo.supportsMultiWindow) {
- if (mSplitScreenController.isDividerVisible()) {
- // Dismiss the split screen if the task no longer supports multi window.
- if (taskInfo.taskId == mPrimary.taskId
- || taskInfo.parentTaskId == mPrimary.taskId) {
- // If the primary is focused, dismiss to primary.
- mSplitScreenController
- .startDismissSplit(taskInfo.isFocused /* toPrimaryTask */);
- } else {
- // If the secondary is not focused, dismiss to primary.
- mSplitScreenController
- .startDismissSplit(!taskInfo.isFocused /* toPrimaryTask */);
- }
- }
- return;
- }
- if (taskInfo.hasParentTask()) {
- // changed messages are noisy since it reports on every ensureVisibility. This
- // conflicts with legacy app-transitions which "swaps" the position to a
- // leash. For now, only update when position actually changes to avoid
- // poorly-timed duplicate calls.
- if (taskInfo.positionInParent.equals(mPositionByTaskId.get(taskInfo.taskId))) {
- return;
- }
- handleChildTaskChanged(taskInfo);
- } else {
- handleTaskInfoChanged(taskInfo);
- }
- mPositionByTaskId.put(taskInfo.taskId, new Point(taskInfo.positionInParent));
- }
- }
-
- private void handleChildTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
- mLeashByTaskId.put(taskInfo.taskId, leash);
- mPositionByTaskId.put(taskInfo.taskId, new Point(taskInfo.positionInParent));
- if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
- updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
- }
-
- private void handleChildTaskChanged(RunningTaskInfo taskInfo) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
- final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
- updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */);
- }
-
- private void updateChildTaskSurface(
- RunningTaskInfo taskInfo, SurfaceControl leash, boolean firstAppeared) {
- final Point taskPositionInParent = taskInfo.positionInParent;
- mSyncQueue.runInSync(t -> {
- t.setWindowCrop(leash, null);
- t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
- if (firstAppeared && !Transitions.ENABLE_SHELL_TRANSITIONS) {
- t.setAlpha(leash, 1f);
- t.setMatrix(leash, 1, 0, 0, 1);
- t.show(leash);
- }
- });
- }
-
- /**
- * This is effectively a finite state machine which moves between the various split-screen
- * presentations based on the contents of the split regions.
- */
- private void handleTaskInfoChanged(RunningTaskInfo info) {
- if (!mSplitScreenSupported) {
- // This shouldn't happen; but apparently there is a chance that SysUI crashes without
- // system server receiving binder-death (or maybe it receives binder-death too late?).
- // In this situation, when sys-ui restarts, the split root-tasks will still exist so
- // there is a small window of time during init() where WM might send messages here
- // before init() fails. So, avoid a cycle of crashes by returning early.
- Log.e(TAG, "Got handleTaskInfoChanged when not initialized: " + info);
- return;
- }
- final boolean secondaryImpliedMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
- || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
- && mSplitScreenController.isHomeStackResizable());
- final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
- final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
- if (info.token.asBinder() == mPrimary.token.asBinder()) {
- mPrimary = info;
- } else if (info.token.asBinder() == mSecondary.token.asBinder()) {
- mSecondary = info;
- }
- if (DEBUG) {
- Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary);
- }
- if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
- final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
- final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
- final boolean secondaryImpliesMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
- || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
- && mSplitScreenController.isHomeStackResizable());
- if (primaryIsEmpty == primaryWasEmpty && secondaryWasEmpty == secondaryIsEmpty
- && secondaryImpliedMinimize == secondaryImpliesMinimize) {
- // No relevant changes
- return;
- }
- if (primaryIsEmpty || secondaryIsEmpty) {
- // At-least one of the splits is empty which means we are currently transitioning
- // into or out-of split-screen mode.
- if (DEBUG) {
- Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType
- + " " + mSecondary.topActivityType);
- }
- if (mSplitScreenController.isDividerVisible()) {
- // Was in split-mode, which means we are leaving split, so continue that.
- // This happens when the stack in the primary-split is dismissed.
- if (DEBUG) {
- Log.d(TAG, " was in split, so this means leave it "
- + mPrimary.topActivityType + " " + mSecondary.topActivityType);
- }
- mSplitScreenController.startDismissSplit(false /* toPrimaryTask */);
- } else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {
- // Wasn't in split-mode (both were empty), but now that the primary split is
- // populated, we should fully enter split by moving everything else into secondary.
- // This just tells window-manager to reparent things, the UI will respond
- // when it gets new task info for the secondary split.
- if (DEBUG) {
- Log.d(TAG, " was not in split, but primary is populated, so enter it");
- }
- mSplitScreenController.startEnterSplit();
- }
- } else if (secondaryImpliesMinimize) {
- // Workaround for b/172686383, we can't rely on the sync bounds change transaction for
- // the home task to finish before the last updateChildTaskSurface() call even if it's
- // queued on the sync transaction queue, so ensure that the home task surface is updated
- // again before we minimize
- final ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
- mSplitScreenController.getWmProxy().getHomeAndRecentsTasks(tasks,
- mSplitScreenController.getSecondaryRoot());
- for (int i = 0; i < tasks.size(); i++) {
- final RunningTaskInfo taskInfo = tasks.get(i);
- final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
- if (leash != null) {
- updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */);
- }
- }
-
- // Both splits are populated but the secondary split has a home/recents stack on top,
- // so enter minimized mode.
- mSplitScreenController.ensureMinimizedSplit();
- } else {
- // Both splits are populated by normal activities, so make sure we aren't minimized.
- mSplitScreenController.ensureNormalSplit();
- }
- }
-
- @Override
- public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
- b.setParent(findTaskSurface(taskId));
- }
-
- @Override
- public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
- SurfaceControl.Transaction t) {
- t.reparent(sc, findTaskSurface(taskId));
- }
-
- private SurfaceControl findTaskSurface(int taskId) {
- if (!mLeashByTaskId.contains(taskId)) {
- throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
- }
- return mLeashByTaskId.get(taskId);
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
- pw.println(innerPrefix + "mSplitScreenSupported=" + mSplitScreenSupported);
- if (mPrimary != null) pw.println(innerPrefix + "mPrimary.taskId=" + mPrimary.taskId);
- if (mSecondary != null) pw.println(innerPrefix + "mSecondary.taskId=" + mSecondary.taskId);
- }
-
- @Override
- public String toString() {
- return TAG;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
deleted file mode 100644
index b1fa2ac25fe7..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
+++ /dev/null
@@ -1,348 +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.legacysplitscreen;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-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_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.WindowConfiguration;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.transition.Transitions;
-
-import java.util.ArrayList;
-
-/** Plays transition animations for split-screen */
-public class LegacySplitScreenTransitions implements Transitions.TransitionHandler {
- private static final String TAG = "SplitScreenTransitions";
-
- public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 10;
-
- private final TransactionPool mTransactionPool;
- private final Transitions mTransitions;
- private final LegacySplitScreenController mSplitScreen;
- private final LegacySplitScreenTaskListener mListener;
-
- private IBinder mPendingDismiss = null;
- private boolean mDismissFromSnap = false;
- private IBinder mPendingEnter = null;
- private IBinder mAnimatingTransition = null;
-
- /** Keeps track of currently running animations */
- private final ArrayList<Animator> mAnimations = new ArrayList<>();
-
- private Transitions.TransitionFinishCallback mFinishCallback = null;
- private SurfaceControl.Transaction mFinishTransaction;
-
- LegacySplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
- @NonNull LegacySplitScreenController splitScreen,
- @NonNull LegacySplitScreenTaskListener listener) {
- mTransactionPool = pool;
- mTransitions = transitions;
- mSplitScreen = splitScreen;
- mListener = listener;
- }
-
- @Override
- public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
- @Nullable TransitionRequestInfo request) {
- WindowContainerTransaction out = null;
- final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
- final @WindowManager.TransitionType int type = request.getType();
- if (mSplitScreen.isDividerVisible()) {
- // try to handle everything while in split-screen
- out = new WindowContainerTransaction();
- if (triggerTask != null) {
- final boolean shouldDismiss =
- // if we close the primary-docked task, then leave split-screen since there
- // is nothing behind it.
- ((type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK)
- && triggerTask.parentTaskId == mListener.mPrimary.taskId)
- // if an activity that is not supported in multi window mode is launched,
- // we also need to leave split-screen.
- || ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)
- && !triggerTask.supportsMultiWindow);
- // In both cases, dismiss the primary
- if (shouldDismiss) {
- WindowManagerProxy.buildDismissSplit(out, mListener,
- mSplitScreen.getSplitLayout(), true /* dismiss */);
- if (type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) {
- out.reorder(triggerTask.token, true /* onTop */);
- }
- mPendingDismiss = transition;
- }
- }
- } else if (triggerTask != null) {
- // Not in split mode, so look for an open with a trigger task.
- if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)
- && triggerTask.configuration.windowConfiguration.getWindowingMode()
- == WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- out = new WindowContainerTransaction();
- mSplitScreen.prepareEnterSplitTransition(out);
- mPendingEnter = transition;
- }
- }
- return out;
- }
-
- // TODO(shell-transitions): real animations
- private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
- final float end = show ? 1.f : 0.f;
- final float start = 1.f - end;
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- final ValueAnimator va = ValueAnimator.ofFloat(start, end);
- va.setDuration(500);
- va.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
- transaction.apply();
- });
- final Runnable finisher = () -> {
- transaction.setAlpha(leash, end);
- transaction.apply();
- mTransactionPool.release(transaction);
- mTransitions.getMainExecutor().execute(() -> {
- mAnimations.remove(va);
- onFinish();
- });
- };
- va.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) { }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) { }
- });
- mAnimations.add(va);
- mTransitions.getAnimExecutor().execute(va::start);
- }
-
- // TODO(shell-transitions): real animations
- private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
- @NonNull Rect startBounds, @NonNull Rect endBounds) {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
- va.setDuration(500);
- va.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- transaction.setWindowCrop(leash,
- (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
- (int) (startBounds.height() * (1.f - fraction)
- + endBounds.height() * fraction));
- transaction.setPosition(leash,
- startBounds.left * (1.f - fraction) + endBounds.left * fraction,
- startBounds.top * (1.f - fraction) + endBounds.top * fraction);
- transaction.apply();
- });
- final Runnable finisher = () -> {
- transaction.setWindowCrop(leash, 0, 0);
- transaction.setPosition(leash, endBounds.left, endBounds.top);
- transaction.apply();
- mTransactionPool.release(transaction);
- mTransitions.getMainExecutor().execute(() -> {
- mAnimations.remove(va);
- onFinish();
- });
- };
- va.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- finisher.run();
- }
- });
- mAnimations.add(va);
- mTransitions.getAnimExecutor().execute(va::start);
- }
-
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (transition != mPendingDismiss && transition != mPendingEnter) {
- // If we're not in split-mode, just abort
- if (!mSplitScreen.isDividerVisible()) return false;
- // Check to see if HOME is involved
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() == null
- || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) continue;
- if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
- mSplitScreen.ensureMinimizedSplit();
- } else if (change.getMode() == TRANSIT_CLOSE
- || change.getMode() == TRANSIT_TO_BACK) {
- mSplitScreen.ensureNormalSplit();
- }
- }
- // Use normal animations.
- return false;
- }
-
- mFinishCallback = finishCallback;
- mFinishTransaction = mTransactionPool.acquire();
- mAnimatingTransition = transition;
-
- // Play fade animations
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final SurfaceControl leash = change.getLeash();
- final int mode = info.getChanges().get(i).getMode();
-
- if (mode == TRANSIT_CHANGE) {
- if (change.getParent() != null) {
- // This is probably reparented, so we want the parent to be immediately visible
- final TransitionInfo.Change parentChange = info.getChange(change.getParent());
- startTransaction.show(parentChange.getLeash());
- startTransaction.setAlpha(parentChange.getLeash(), 1.f);
- // and then animate this layer outside the parent (since, for example, this is
- // the home task animating from fullscreen to part-screen).
- startTransaction.reparent(leash, info.getRootLeash());
- startTransaction.setLayer(leash, info.getChanges().size() - i);
- // build the finish reparent/reposition
- mFinishTransaction.reparent(leash, parentChange.getLeash());
- mFinishTransaction.setPosition(leash,
- change.getEndRelOffset().x, change.getEndRelOffset().y);
- }
- // TODO(shell-transitions): screenshot here
- final Rect startBounds = new Rect(change.getStartAbsBounds());
- final boolean isHome = change.getTaskInfo() != null
- && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
- if (mPendingDismiss == transition && mDismissFromSnap && !isHome) {
- // Home is special since it doesn't move during fling. Everything else, though,
- // when dismissing from snap, the top/left is at 0,0.
- startBounds.offsetTo(0, 0);
- }
- final Rect endBounds = new Rect(change.getEndAbsBounds());
- startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- startExampleResizeAnimation(leash, startBounds, endBounds);
- }
- if (change.getParent() != null) {
- continue;
- }
-
- if (transition == mPendingEnter
- && mListener.mPrimary.token.equals(change.getContainer())
- || mListener.mSecondary.token.equals(change.getContainer())) {
- startTransaction.setWindowCrop(leash, change.getStartAbsBounds().width(),
- change.getStartAbsBounds().height());
- if (mListener.mPrimary.token.equals(change.getContainer())) {
- // Move layer to top since we want it above the oversized home task during
- // animation even though home task is on top in hierarchy.
- startTransaction.setLayer(leash, info.getChanges().size() + 1);
- }
- }
- boolean isOpening = Transitions.isOpeningType(info.getType());
- if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
- // fade in
- startExampleAnimation(leash, true /* show */);
- } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
- // fade out
- if (transition == mPendingDismiss && mDismissFromSnap) {
- // Dismissing via snap-to-top/bottom means that the dismissed task is already
- // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
- // and don't animate it so it doesn't pop-in when reparented.
- startTransaction.setAlpha(leash, 0.f);
- } else {
- startExampleAnimation(leash, false /* show */);
- }
- }
- }
- if (transition == mPendingEnter) {
- // If entering, check if we should enter into minimized or normal split
- boolean homeIsVisible = false;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() == null
- || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) {
- continue;
- }
- homeIsVisible = change.getMode() == TRANSIT_OPEN
- || change.getMode() == TRANSIT_TO_FRONT
- || change.getMode() == TRANSIT_CHANGE;
- break;
- }
- mSplitScreen.finishEnterSplitTransition(homeIsVisible);
- }
- startTransaction.apply();
- onFinish();
- return true;
- }
-
- @ExternalThread
- void dismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
- boolean dismissOrMaximize, boolean snapped) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- WindowManagerProxy.buildDismissSplit(wct, tiles, layout, dismissOrMaximize);
- mTransitions.getMainExecutor().execute(() -> {
- mDismissFromSnap = snapped;
- mPendingDismiss = mTransitions.startTransition(TRANSIT_SPLIT_DISMISS_SNAP, wct, this);
- });
- }
-
- private void onFinish() {
- if (!mAnimations.isEmpty()) return;
- mFinishTransaction.apply();
- mTransactionPool.release(mFinishTransaction);
- mFinishTransaction = null;
- mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
- mFinishCallback = null;
- if (mAnimatingTransition == mPendingEnter) {
- mPendingEnter = null;
- }
- if (mAnimatingTransition == mPendingDismiss) {
- mSplitScreen.onDismissSplit();
- mPendingDismiss = null;
- }
- mDismissFromSnap = false;
- mAnimatingTransition = null;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/MinimizedDockShadow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/MinimizedDockShadow.java
deleted file mode 100644
index 1e9223cbe3e2..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/MinimizedDockShadow.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.Shader;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.wm.shell.R;
-
-/**
- * Shadow for the minimized dock state on homescreen.
- */
-public class MinimizedDockShadow extends View {
-
- private final Paint mShadowPaint = new Paint();
-
- private int mDockSide = WindowManager.DOCKED_INVALID;
-
- public MinimizedDockShadow(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- void setDockSide(int dockSide) {
- if (dockSide != mDockSide) {
- mDockSide = dockSide;
- updatePaint(getLeft(), getTop(), getRight(), getBottom());
- invalidate();
- }
- }
-
- private void updatePaint(int left, int top, int right, int bottom) {
- int startColor = mContext.getResources().getColor(
- R.color.minimize_dock_shadow_start, null);
- int endColor = mContext.getResources().getColor(
- R.color.minimize_dock_shadow_end, null);
- final int middleColor = Color.argb(
- (Color.alpha(startColor) + Color.alpha(endColor)) / 2, 0, 0, 0);
- final int quarter = Color.argb(
- (int) (Color.alpha(startColor) * 0.25f + Color.alpha(endColor) * 0.75f),
- 0, 0, 0);
- if (mDockSide == WindowManager.DOCKED_TOP) {
- mShadowPaint.setShader(new LinearGradient(
- 0, 0, 0, bottom - top,
- new int[] { startColor, middleColor, quarter, endColor },
- new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP));
- } else if (mDockSide == WindowManager.DOCKED_LEFT) {
- mShadowPaint.setShader(new LinearGradient(
- 0, 0, right - left, 0,
- new int[] { startColor, middleColor, quarter, endColor },
- new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP));
- } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
- mShadowPaint.setShader(new LinearGradient(
- right - left, 0, 0, 0,
- new int[] { startColor, middleColor, quarter, endColor },
- new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP));
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (changed) {
- updatePaint(left, top, right, bottom);
- invalidate();
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- canvas.drawRect(0, 0, getWidth(), getHeight(), mShadowPaint);
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
deleted file mode 100644
index e42e43bbc2be..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.legacysplitscreen;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.Display;
-import android.view.SurfaceControl;
-import android.view.WindowManagerGlobal;
-import android.window.TaskOrganizer;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.transition.Transitions;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Proxy to simplify calls into window manager/activity manager
- */
-class WindowManagerProxy {
-
- private static final String TAG = "WindowManagerProxy";
- private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS};
- private static final int[] CONTROLLED_ACTIVITY_TYPES = {
- ACTIVITY_TYPE_STANDARD,
- ACTIVITY_TYPE_HOME,
- ACTIVITY_TYPE_RECENTS,
- ACTIVITY_TYPE_UNDEFINED
- };
- private static final int[] CONTROLLED_WINDOWING_MODES = {
- WINDOWING_MODE_FULLSCREEN,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
- WINDOWING_MODE_UNDEFINED
- };
-
- @GuardedBy("mDockedRect")
- private final Rect mDockedRect = new Rect();
-
- private final Rect mTmpRect1 = new Rect();
-
- @GuardedBy("mDockedRect")
- private final Rect mTouchableRegion = new Rect();
-
- private final SyncTransactionQueue mSyncTransactionQueue;
- private final TaskOrganizer mTaskOrganizer;
-
- WindowManagerProxy(SyncTransactionQueue syncQueue, TaskOrganizer taskOrganizer) {
- mSyncTransactionQueue = syncQueue;
- mTaskOrganizer = taskOrganizer;
- }
-
- void dismissOrMaximizeDocked(final LegacySplitScreenTaskListener tiles,
- LegacySplitDisplayLayout layout, final boolean dismissOrMaximize) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- tiles.mSplitScreenController.startDismissSplit(!dismissOrMaximize, true /* snapped */);
- } else {
- applyDismissSplit(tiles, layout, dismissOrMaximize);
- }
- }
-
- public void setResizing(final boolean resizing) {
- try {
- ActivityTaskManager.getService().setSplitScreenResizing(resizing);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling setDockedStackResizing: " + e);
- }
- }
-
- /** Sets a touch region */
- public void setTouchRegion(Rect region) {
- try {
- synchronized (mDockedRect) {
- mTouchableRegion.set(region);
- }
- WindowManagerGlobal.getWindowManagerService().setDockedTaskDividerTouchRegion(
- mTouchableRegion);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to set touchable region: " + e);
- }
- }
-
- void applyResizeSplits(int position, LegacySplitDisplayLayout splitLayout) {
- WindowContainerTransaction t = new WindowContainerTransaction();
- splitLayout.resizeSplits(position, t);
- applySyncTransaction(t);
- }
-
- boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
- WindowContainerToken parent) {
- boolean resizable = false;
- List<ActivityManager.RunningTaskInfo> rootTasks = parent == null
- ? mTaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS)
- : mTaskOrganizer.getChildTasks(parent, HOME_AND_RECENTS);
- for (int i = 0, n = rootTasks.size(); i < n; ++i) {
- final ActivityManager.RunningTaskInfo ti = rootTasks.get(i);
- out.add(ti);
- if (ti.topActivityType == ACTIVITY_TYPE_HOME) {
- resizable = ti.isResizeable;
- }
- }
- return resizable;
- }
-
- /**
- * Assign a fixed override-bounds to home tasks that reflect their geometry while the primary
- * split is minimized. This actually "sticks out" of the secondary split area, but when in
- * minimized mode, the secondary split gets a 'negative' crop to expose it.
- */
- boolean applyHomeTasksMinimized(LegacySplitDisplayLayout layout, WindowContainerToken parent,
- @NonNull WindowContainerTransaction wct) {
- // Resize the home/recents stacks to the larger minimized-state size
- final Rect homeBounds;
- final ArrayList<ActivityManager.RunningTaskInfo> homeStacks = new ArrayList<>();
- boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent);
- if (isHomeResizable) {
- homeBounds = layout.calcResizableMinimizedHomeStackBounds();
- } else {
- // home is not resizable, so lock it to its inherent orientation size.
- homeBounds = new Rect(0, 0, 0, 0);
- for (int i = homeStacks.size() - 1; i >= 0; --i) {
- if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_HOME) {
- final int orient = homeStacks.get(i).configuration.orientation;
- final boolean displayLandscape = layout.mDisplayLayout.isLandscape();
- final boolean isLandscape = orient == ORIENTATION_LANDSCAPE
- || (orient == ORIENTATION_UNDEFINED && displayLandscape);
- homeBounds.right = isLandscape == displayLandscape
- ? layout.mDisplayLayout.width() : layout.mDisplayLayout.height();
- homeBounds.bottom = isLandscape == displayLandscape
- ? layout.mDisplayLayout.height() : layout.mDisplayLayout.width();
- break;
- }
- }
- }
- for (int i = homeStacks.size() - 1; i >= 0; --i) {
- // For non-resizable homes, the minimized size is actually the fullscreen-size. As a
- // result, we don't minimize for recents since it only shows half-size screenshots.
- if (!isHomeResizable) {
- if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_RECENTS) {
- continue;
- }
- wct.setWindowingMode(homeStacks.get(i).token, WINDOWING_MODE_FULLSCREEN);
- }
- wct.setBounds(homeStacks.get(i).token, homeBounds);
- }
- layout.mTiles.mHomeBounds.set(homeBounds);
- return isHomeResizable;
- }
-
- /** @see #buildEnterSplit */
- boolean applyEnterSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout) {
- // Set launchtile first so that any stack created after
- // getAllRootTaskInfos and before reparent (even if unlikely) are placed
- // correctly.
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setLaunchRoot(tiles.mSecondary.token, CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES);
- final boolean isHomeResizable = buildEnterSplit(wct, tiles, layout);
- applySyncTransaction(wct);
- return isHomeResizable;
- }
-
- /**
- * Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split.
- * This assumes there is already something in the primary split since that is usually what
- * triggers a call to this. In the same transaction, this overrides the home task bounds via
- * {@link #applyHomeTasksMinimized}.
- *
- * @return whether the home stack is resizable
- */
- boolean buildEnterSplit(WindowContainerTransaction outWct, LegacySplitScreenTaskListener tiles,
- LegacySplitDisplayLayout layout) {
- List<ActivityManager.RunningTaskInfo> rootTasks =
- mTaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
- if (rootTasks.isEmpty()) {
- return false;
- }
- ActivityManager.RunningTaskInfo topHomeTask = null;
- for (int i = rootTasks.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
- // Check whether the task can be moved to split secondary.
- if (!rootTask.supportsMultiWindow && rootTask.topActivityType != ACTIVITY_TYPE_HOME) {
- continue;
- }
- // Only move split controlling tasks to split secondary.
- final int windowingMode = rootTask.getWindowingMode();
- if (!ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, windowingMode)
- || !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, rootTask.getActivityType())
- // Excludes split screen secondary due to it's the root we're reparenting to.
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
- continue;
- }
- // Since this iterates from bottom to top, update topHomeTask for every fullscreen task
- // so it will be left with the status of the top one.
- topHomeTask = isHomeOrRecentTask(rootTask) ? rootTask : null;
- outWct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
- }
- // Move the secondary split-forward.
- outWct.reorder(tiles.mSecondary.token, true /* onTop */);
- boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */,
- outWct);
- if (topHomeTask != null && !Transitions.ENABLE_SHELL_TRANSITIONS) {
- // Translate/update-crop of secondary out-of-band with sync transaction -- Until BALST
- // is enabled, this temporarily syncs the home surface position with offset until
- // sync transaction finishes.
- outWct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds);
- }
- return isHomeResizable;
- }
-
- static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
- final int atype = ti.getActivityType();
- return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS;
- }
-
- /** @see #buildDismissSplit */
- void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
- boolean dismissOrMaximize) {
- // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
- // plus specific APIs to clean this up.
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Set launch root first so that any task created after getChildContainers and
- // before reparent (pretty unlikely) are put into fullscreen.
- wct.setLaunchRoot(tiles.mSecondary.token, null, null);
- buildDismissSplit(wct, tiles, layout, dismissOrMaximize);
- applySyncTransaction(wct);
- }
-
- /**
- * Reparents all tile members back to their display and resets home task override bounds.
- * @param dismissOrMaximize When {@code true} this resolves the split by closing the primary
- * split (thus resulting in the top of the secondary split becoming
- * fullscreen. {@code false} resolves the other way.
- */
- static void buildDismissSplit(WindowContainerTransaction outWct,
- LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
- boolean dismissOrMaximize) {
- // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
- // plus specific APIs to clean this up.
- final TaskOrganizer taskOrg = tiles.getTaskOrganizer();
- List<ActivityManager.RunningTaskInfo> primaryChildren =
- taskOrg.getChildTasks(tiles.mPrimary.token, null /* activityTypes */);
- List<ActivityManager.RunningTaskInfo> secondaryChildren =
- taskOrg.getChildTasks(tiles.mSecondary.token, null /* activityTypes */);
- // In some cases (eg. non-resizable is launched), system-server will leave split-screen.
- // as a result, the above will not capture any tasks; yet, we need to clean-up the
- // home task bounds.
- List<ActivityManager.RunningTaskInfo> freeHomeAndRecents =
- taskOrg.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS);
- // Filter out the root split tasks
- freeHomeAndRecents.removeIf(p -> p.token.equals(tiles.mSecondary.token)
- || p.token.equals(tiles.mPrimary.token));
-
- if (primaryChildren.isEmpty() && secondaryChildren.isEmpty()
- && freeHomeAndRecents.isEmpty()) {
- return;
- }
- if (dismissOrMaximize) {
- // Dismissing, so move all primary split tasks first
- for (int i = primaryChildren.size() - 1; i >= 0; --i) {
- outWct.reparent(primaryChildren.get(i).token, null /* parent */,
- true /* onTop */);
- }
- boolean homeOnTop = false;
- // Don't need to worry about home tasks because they are already in the "proper"
- // order within the secondary split.
- for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
- outWct.reparent(ti.token, null /* parent */, true /* onTop */);
- if (isHomeOrRecentTask(ti)) {
- outWct.setBounds(ti.token, null);
- outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
- if (i == 0) {
- homeOnTop = true;
- }
- }
- }
- if (homeOnTop && !Transitions.ENABLE_SHELL_TRANSITIONS) {
- // Translate/update-crop of secondary out-of-band with sync transaction -- instead
- // play this in sync with new home-app frame because until BALST is enabled this
- // shows up on screen before the syncTransaction returns.
- // We only have access to the secondary root surface, though, so in order to
- // position things properly, we have to take into account the existing negative
- // offset/crop of the minimized-home task.
- final boolean landscape = layout.mDisplayLayout.isLandscape();
- final int posX = landscape ? layout.mSecondary.left - tiles.mHomeBounds.left
- : layout.mSecondary.left;
- final int posY = landscape ? layout.mSecondary.top
- : layout.mSecondary.top - tiles.mHomeBounds.top;
- final SurfaceControl.Transaction sft = new SurfaceControl.Transaction();
- sft.setPosition(tiles.mSecondarySurface, posX, posY);
- final Rect crop = new Rect(0, 0, layout.mDisplayLayout.width(),
- layout.mDisplayLayout.height());
- crop.offset(-posX, -posY);
- sft.setWindowCrop(tiles.mSecondarySurface, crop);
- outWct.setBoundsChangeTransaction(tiles.mSecondary.token, sft);
- }
- } else {
- // Maximize, so move non-home secondary split first
- for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
- if (isHomeOrRecentTask(secondaryChildren.get(i))) {
- continue;
- }
- outWct.reparent(secondaryChildren.get(i).token, null /* parent */,
- true /* onTop */);
- }
- // Find and place home tasks in-between. This simulates the fact that there was
- // nothing behind the primary split's tasks.
- for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
- if (isHomeOrRecentTask(ti)) {
- outWct.reparent(ti.token, null /* parent */, true /* onTop */);
- // reset bounds and mode too
- outWct.setBounds(ti.token, null);
- outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
- }
- }
- for (int i = primaryChildren.size() - 1; i >= 0; --i) {
- outWct.reparent(primaryChildren.get(i).token, null /* parent */,
- true /* onTop */);
- }
- }
- for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) {
- outWct.setBounds(freeHomeAndRecents.get(i).token, null);
- outWct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED);
- }
- // Reset focusable to true
- outWct.setFocusable(tiles.mPrimary.token, true /* focusable */);
- }
-
- /**
- * Utility to apply a sync transaction serially with other sync transactions.
- *
- * @see SyncTransactionQueue#queue
- */
- void applySyncTransaction(WindowContainerTransaction wct) {
- mSyncTransactionQueue.queue(wct);
- }
-
- /**
- * @see SyncTransactionQueue#queueIfWaiting
- */
- boolean queueSyncTransactionIfWaiting(WindowContainerTransaction wct) {
- return mSyncTransactionQueue.queueIfWaiting(wct);
- }
-
- /**
- * @see SyncTransactionQueue#runInSync
- */
- void runInSync(SyncTransactionQueue.TransactionRunnable runnable) {
- mSyncTransactionQueue.runInSync(runnable);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 179b725ab210..1d8ac2b576e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -39,6 +39,7 @@ import android.provider.Settings;
import android.util.Slog;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
@@ -659,11 +660,11 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
}
/**
- * Handles rotation based on OnDisplayChangingListener callback
+ * Handles display change based on OnDisplayChangingListener callback
*/
@Override
- public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- WindowContainerTransaction wct) {
+ public void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
if (!isInitialized()) {
return;
}
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 e624de661737..bd386b5681d8 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
@@ -297,6 +297,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
displayController.addDisplayWindowListener(this);
}
+ public PipTransitionController getTransitionController() {
+ return mPipTransitionController;
+ }
+
public Rect getCurrentOrAnimatingBounds() {
PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
@@ -458,7 +462,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
// Cancel the existing animator if there is any.
- cancelCurrentAnimator();
+ // TODO(b/232439933): this is disabled temporarily to unblock b/234502692.
+ // cancelCurrentAnimator();
// Set the exiting state first so if there is fixed rotation later, the running animation
// won't be interrupted by alpha animation for existing PiP.
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 36e712459863..28427a808d90 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
@@ -28,7 +28,6 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
@@ -52,6 +51,7 @@ import android.graphics.Rect;
import android.os.IBinder;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
@@ -145,6 +145,11 @@ public class PipTransition extends PipTransitionController {
if (destinationBounds != null) {
mExitDestinationBounds.set(destinationBounds);
}
+ final PipAnimationController.PipTransitionAnimator animator =
+ mPipAnimationController.getCurrentAnimator();
+ if (animator != null && animator.isRunning()) {
+ animator.cancel();
+ }
mExitTransition = mTransitions.startTransition(type, out, this);
}
@@ -217,13 +222,20 @@ public class PipTransition extends PipTransitionController {
}
// Entering PIP.
- if (isEnteringPip(info, mCurrentPipTaskToken)) {
- return startEnterAnimation(info, startTransaction, finishTransaction, finishCallback);
+ if (isEnteringPip(info)) {
+ startEnterAnimation(info, startTransaction, finishTransaction, finishCallback);
+ return true;
}
// For transition that we don't animate, but contains the PIP leash, we need to update the
// PIP surface, otherwise it will be reset after the transition.
if (currentPipTaskChange != null) {
+ // Set the "end" bounds of pip. The default setup uses the start bounds. Since this is
+ // changing the *finish*Transaction, we need to use the end bounds. This will also
+ // make sure that the fade-in animation (below) uses the end bounds as well.
+ if (!currentPipTaskChange.getEndAbsBounds().isEmpty()) {
+ mPipBoundsState.setBounds(currentPipTaskChange.getEndAbsBounds());
+ }
updatePipForUnhandledTransition(currentPipTaskChange, startTransaction,
finishTransaction);
}
@@ -245,16 +257,9 @@ public class PipTransition extends PipTransitionController {
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- if (request.getType() == TRANSIT_PIP) {
+ if (requestHasPipEnter(request)) {
WindowContainerTransaction wct = new WindowContainerTransaction();
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
- mRequestedEnterTransition = transition;
- mRequestedEnterTask = request.getTriggerTask().token;
- wct.setActivityWindowingMode(request.getTriggerTask().token,
- WINDOWING_MODE_UNDEFINED);
- final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
- wct.setBounds(request.getTriggerTask().token, destinationBounds);
- }
+ augmentRequest(transition, request, wct);
return wct;
} else {
return null;
@@ -262,6 +267,22 @@ public class PipTransition extends PipTransitionController {
}
@Override
+ public void augmentRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) {
+ if (!requestHasPipEnter(request)) {
+ throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
+ }
+ if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ mRequestedEnterTransition = transition;
+ mRequestedEnterTask = request.getTriggerTask().token;
+ outWCT.setActivityWindowingMode(request.getTriggerTask().token,
+ WINDOWING_MODE_UNDEFINED);
+ final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+ outWCT.setBounds(request.getTriggerTask().token, destinationBounds);
+ }
+ }
+
+ @Override
public boolean handleRotateDisplay(int startRotation, int endRotation,
WindowContainerTransaction wct) {
if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) {
@@ -315,11 +336,27 @@ public class PipTransition extends PipTransitionController {
// (likely a remote like launcher), so don't fire the finish-callback here -- wait until
// the exit transition is merged.
if ((mExitTransition == null || isAnimatingLocally()) && mFinishCallback != null) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareFinishResizeTransaction(taskInfo, destinationBounds,
- direction, wct);
- if (tx != null) {
- wct.setBoundsChangeTransaction(taskInfo.token, tx);
+ WindowContainerTransaction wct = null;
+ if (isOutPipDirection(direction)) {
+ // Only need to reset surface properties. The server-side operations were already
+ // done at the start.
+ if (tx != null) {
+ mFinishTransaction.merge(tx);
+ }
+ } else {
+ wct = new WindowContainerTransaction();
+ if (isInPipDirection(direction)) {
+ // If we are animating from fullscreen using a bounds animation, then reset the
+ // activity windowing mode, and set the task bounds to the final bounds
+ wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds);
+ wct.setBounds(taskInfo.token, destinationBounds);
+ } else {
+ wct.setBounds(taskInfo.token, null /* bounds */);
+ }
+ if (tx != null) {
+ wct.setBoundsChangeTransaction(taskInfo.token, tx);
+ }
}
final SurfaceControl leash = mPipOrganizer.getSurfaceControl();
final int displayRotation = taskInfo.getConfiguration().windowConfiguration
@@ -559,92 +596,94 @@ public class PipTransition extends PipTransitionController {
}
/** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */
- private static boolean isEnteringPip(@NonNull TransitionInfo info,
- @Nullable WindowContainerToken currentPipTaskToken) {
+ private boolean isEnteringPip(@NonNull TransitionInfo info) {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() != null
- && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED
- && !change.getContainer().equals(currentPipTaskToken)) {
- // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps
- // that enter PiP instantly on opening, mostly from CTS/Flicker tests)
- if (info.getType() == TRANSIT_PIP || info.getType() == TRANSIT_OPEN) {
- return true;
- }
- // This can happen if the request to enter PIP happens when we are collecting for
- // another transition, such as TRANSIT_CHANGE (display rotation).
- if (info.getType() == TRANSIT_CHANGE) {
- return true;
- }
+ if (isEnteringPip(change, info.getType())) return true;
+ }
+ return false;
+ }
- // Please file a bug to handle the unexpected transition type.
- throw new IllegalStateException("Entering PIP with unexpected transition type="
- + transitTypeToString(info.getType()));
+ /** Whether a particular change is a window that is entering pip. */
+ @Override
+ public boolean isEnteringPip(@NonNull TransitionInfo.Change change,
+ @WindowManager.TransitionType int transitType) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED
+ && !change.getContainer().equals(mCurrentPipTaskToken)) {
+ // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps
+ // that enter PiP instantly on opening, mostly from CTS/Flicker tests)
+ if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN) {
+ return true;
+ }
+ // This can happen if the request to enter PIP happens when we are collecting for
+ // another transition, such as TRANSIT_CHANGE (display rotation).
+ if (transitType == TRANSIT_CHANGE) {
+ return true;
}
+
+ // Please file a bug to handle the unexpected transition type.
+ throw new IllegalStateException("Entering PIP with unexpected transition type="
+ + transitTypeToString(transitType));
}
return false;
}
- private boolean startEnterAnimation(@NonNull TransitionInfo info,
+ private void startEnterAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- // Search for an Enter PiP transition (along with a show wallpaper one)
+ // Search for an Enter PiP transition
TransitionInfo.Change enterPip = null;
- TransitionInfo.Change wallpaper = null;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.getTaskInfo() != null
&& change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
enterPip = change;
- } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
- wallpaper = change;
}
}
if (enterPip == null) {
- return false;
- }
- // Keep track of the PIP task.
- mCurrentPipTaskToken = enterPip.getContainer();
- mHasFadeOut = false;
-
- if (mFinishCallback != null) {
- callFinishCallback(null /* wct */);
- mFinishTransaction = null;
- throw new RuntimeException("Previous callback not called, aborting entering PIP.");
+ throw new IllegalStateException("Trying to start PiP animation without a pip"
+ + "participant");
}
- // Show the wallpaper if there is a wallpaper change.
- if (wallpaper != null) {
- startTransaction.show(wallpaper.getLeash());
- startTransaction.setAlpha(wallpaper.getLeash(), 1.f);
- }
// Make sure other open changes are visible as entering PIP. Some may be hidden in
// Transitions#setupStartState because the transition type is OPEN (such as auto-enter).
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if (change == enterPip || change == wallpaper) {
- continue;
- }
+ if (change == enterPip) continue;
if (isOpeningType(change.getMode())) {
final SurfaceControl leash = change.getLeash();
startTransaction.show(leash).setAlpha(leash, 1.f);
}
}
+ startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback);
+ }
+
+ @Override
+ public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange,
+ @NonNull final SurfaceControl.Transaction startTransaction,
+ @NonNull final SurfaceControl.Transaction finishTransaction,
+ @NonNull final Transitions.TransitionFinishCallback finishCallback) {
+ if (mFinishCallback != null) {
+ callFinishCallback(null /* wct */);
+ mFinishTransaction = null;
+ throw new RuntimeException("Previous callback not called, aborting entering PIP.");
+ }
+
+ // Keep track of the PIP task and animation.
+ mCurrentPipTaskToken = pipChange.getContainer();
+ mHasFadeOut = false;
mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
mFinishCallback = finishCallback;
mFinishTransaction = finishTransaction;
- final int endRotation = mInFixedRotation ? mEndFixedRotation : enterPip.getEndRotation();
- return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(),
- startTransaction, finishTransaction, enterPip.getStartRotation(),
- endRotation);
- }
- private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
- final SurfaceControl.Transaction startTransaction,
- final SurfaceControl.Transaction finishTransaction,
- final int startRotation, final int endRotation) {
+ final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo();
+ final SurfaceControl leash = pipChange.getLeash();
+ final int startRotation = pipChange.getStartRotation();
+ final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation();
+
setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
taskInfo.topActivityInfo);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
@@ -657,7 +696,6 @@ public class PipTransition extends PipTransitionController {
computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
destinationBounds, sourceHintRect);
}
- PipAnimationController.PipTransitionAnimator animator;
// Set corner radius for entering pip.
mSurfaceTransactionHelper
.crop(finishTransaction, leash, destinationBounds)
@@ -694,7 +732,7 @@ public class PipTransition extends PipTransitionController {
null /* callback */, false /* withStartDelay */);
}
mPipTransitionState.setInSwipePipToHomeTransition(false);
- return true;
+ return;
}
if (rotationDelta != Surface.ROTATION_0) {
@@ -702,6 +740,12 @@ public class PipTransition extends PipTransitionController {
tmpTransform.postRotate(rotationDelta);
startTransaction.setMatrix(leash, tmpTransform, new float[9]);
}
+ if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ startTransaction.setAlpha(leash, 0f);
+ }
+ startTransaction.apply();
+
+ PipAnimationController.PipTransitionAnimator animator;
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
@@ -712,7 +756,6 @@ public class PipTransition extends PipTransitionController {
animator.setColorContentOverlay(mContext);
}
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
- startTransaction.setAlpha(leash, 0f);
animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
0f, 1f);
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
@@ -720,7 +763,6 @@ public class PipTransition extends PipTransitionController {
throw new RuntimeException("Unrecognized animation type: "
+ mOneShotAnimationType);
}
- startTransaction.apply();
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration);
@@ -731,8 +773,6 @@ public class PipTransition extends PipTransitionController {
animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
}
animator.start();
-
- return true;
}
/** Computes destination bounds in old rotation and updates source hint rect if available. */
@@ -852,27 +892,4 @@ public class PipTransition extends PipTransitionController {
mPipMenuController.movePipMenu(null, null, destinationBounds);
mPipMenuController.updateMenuBounds(destinationBounds);
}
-
- private void prepareFinishResizeTransaction(TaskInfo taskInfo, Rect destinationBounds,
- @PipAnimationController.TransitionDirection int direction,
- WindowContainerTransaction wct) {
- Rect taskBounds = null;
- if (isInPipDirection(direction)) {
- // If we are animating from fullscreen using a bounds animation, then reset the
- // activity windowing mode set by WM, and set the task bounds to the final bounds
- taskBounds = destinationBounds;
- wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
- wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds);
- } else if (isOutPipDirection(direction)) {
- // If we are animating to fullscreen, then we need to reset the override bounds
- // on the task to ensure that the task "matches" the parent's bounds.
- taskBounds = (direction == TRANSITION_DIRECTION_LEAVE_PIP)
- ? null : destinationBounds;
- wct.setWindowingMode(taskInfo.token, getOutPipWindowingMode());
- // Simply reset the activity mode set prior to the animation running.
- wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
- }
-
- wct.setBounds(taskInfo.token, taskBounds);
- }
}
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 54f46e0c9938..d3f69f6762f9 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.WindowManager.TRANSIT_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
@@ -28,10 +29,16 @@ import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.transition.Transitions;
@@ -206,6 +213,30 @@ public abstract class PipTransitionController implements Transitions.TransitionH
return false;
}
+ /** @return whether the transition-request represents a pip-entry. */
+ public boolean requestHasPipEnter(@NonNull TransitionRequestInfo request) {
+ return request.getType() == TRANSIT_PIP;
+ }
+
+ /** Whether a particular change is a window that is entering pip. */
+ public boolean isEnteringPip(@NonNull TransitionInfo.Change change,
+ @WindowManager.TransitionType int transitType) {
+ return false;
+ }
+
+ /** Add PiP-related changes to `outWCT` for the given request. */
+ public void augmentRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) {
+ throw new IllegalStateException("Request isn't entering PiP");
+ }
+
+ /** Play a transition animation for entering PiP on a specific PiP change. */
+ public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange,
+ @NonNull final SurfaceControl.Transaction startTransaction,
+ @NonNull final SurfaceControl.Transaction finishTransaction,
+ @NonNull final Transitions.TransitionFinishCallback finishCallback) {
+ }
+
/**
* Callback interface for PiP transitions (both from and to PiP mode)
*/
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 4b1e5f8c0d7c..c3e6d82df781 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
@@ -110,7 +110,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private PipAppOpsListener mAppOpsListener;
private PipMediaController mMediaController;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
+ private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
private TaskStackListenerImpl mTaskStackListener;
@@ -156,7 +158,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
* Handler for display rotation changes.
*/
private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
- int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
+ displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> {
if (mPipTransitionController.handleRotateDisplay(fromRotation, toRotation, t)) {
return;
}
@@ -286,7 +288,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Nullable
public static Pip create(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -301,7 +304,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
- pipBoundsState, pipMediaController,
+ pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
oneHandedController, mainExecutor)
@@ -312,7 +315,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
PipTaskOrganizer pipTaskOrganizer,
@@ -335,7 +340,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mWindowManagerShellWrapper = windowManagerShellWrapper;
mDisplayController = displayController;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
+ mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
mMainExecutor = mainExecutor;
mMediaController = pipMediaController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
new file mode 100644
index 000000000000..78084fafe197
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
@@ -0,0 +1,97 @@
+/*
+ * 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 android.graphics.Rect;
+import android.util.ArraySet;
+
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+
+import java.util.Set;
+
+/**
+ * Calculates the adjusted position that does not occlude keep clear areas.
+ */
+public class PipKeepClearAlgorithm {
+
+ /**
+ * Adjusts the current position of PiP to avoid occluding keep clear areas. If the user has
+ * moved PiP manually, the unmodified current position will be returned instead.
+ */
+ public Rect adjust(PipBoundsState boundsState, PipBoundsAlgorithm boundsAlgorithm) {
+ if (boundsState.hasUserResizedPip()) {
+ return boundsState.getBounds();
+ }
+ return adjust(boundsAlgorithm.getEntryDestinationBounds(),
+ boundsState.getRestrictedKeepClearAreas(),
+ boundsState.getUnrestrictedKeepClearAreas(), boundsState.getDisplayBounds());
+ }
+
+ /** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */
+ public Rect adjust(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
+ Set<Rect> unrestrictedKeepClearAreas, Rect displayBounds) {
+ if (restrictedKeepClearAreas.isEmpty()) {
+ return defaultBounds;
+ }
+ Set<Rect> keepClearAreas = new ArraySet<>();
+ if (!restrictedKeepClearAreas.isEmpty()) {
+ keepClearAreas.addAll(restrictedKeepClearAreas);
+ }
+ Rect outBounds = new Rect(defaultBounds);
+ for (Rect r : keepClearAreas) {
+ if (Rect.intersects(r, outBounds)) {
+ if (tryOffsetUp(outBounds, r, displayBounds)) continue;
+ if (tryOffsetLeft(outBounds, r, displayBounds)) continue;
+ if (tryOffsetDown(outBounds, r, displayBounds)) continue;
+ if (tryOffsetRight(outBounds, r, displayBounds)) continue;
+ }
+ }
+ return outBounds;
+ }
+
+ private boolean tryOffsetLeft(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
+ return tryOffset(rectToMove, rectToAvoid, displayBounds,
+ rectToAvoid.left - rectToMove.right, 0);
+ }
+
+ private boolean tryOffsetRight(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
+ return tryOffset(rectToMove, rectToAvoid, displayBounds,
+ rectToAvoid.right - rectToMove.left, 0);
+ }
+
+ private boolean tryOffsetUp(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
+ return tryOffset(rectToMove, rectToAvoid, displayBounds,
+ 0, rectToAvoid.top - rectToMove.bottom);
+ }
+
+ private boolean tryOffsetDown(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
+ return tryOffset(rectToMove, rectToAvoid, displayBounds,
+ 0, rectToAvoid.bottom - rectToMove.top);
+ }
+
+ private boolean tryOffset(Rect rectToMove, Rect rectToAvoid, Rect displayBounds,
+ int dx, int dy) {
+ Rect tmp = new Rect(rectToMove);
+ tmp.offset(dx, dy);
+ if (!Rect.intersects(rectToAvoid, tmp) && displayBounds.contains(tmp)) {
+ rectToMove.offsetTo(tmp.left, tmp.top);
+ return true;
+ }
+ return false;
+ }
+}
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 6390c8984dac..1958157fc319 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
@@ -282,7 +282,7 @@ public class PipMenuView extends FrameLayout {
&& mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId);
mFocusedTaskAllowSplitScreen = isSplitScreen
|| (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && taskInfo.supportsSplitScreenMultiWindow
+ && taskInfo.supportsMultiWindow
&& taskInfo.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME);
}
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 ac7b9033b2b9..a2ff97247189 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
@@ -54,6 +54,7 @@ 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;
@@ -250,6 +251,10 @@ public class PipTouchHandler {
});
}
+ public PipTransitionController getTransitionHandler() {
+ return mPipTaskOrganizer.getTransitionController();
+ }
+
private void reloadResources() {
final Resources res = mContext.getResources();
mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index c166178e9bbd..0f7a4daf6d08 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -44,7 +44,7 @@ import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.StagedSplitBounds;
+import com.android.wm.shell.util.SplitBounds;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -69,12 +69,12 @@ public class RecentTasksController implements TaskStackListenerCallback,
// pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1)
private final SparseIntArray mSplitTasks = new SparseIntArray();
/**
- * Maps taskId to {@link StagedSplitBounds} for both taskIDs.
+ * Maps taskId to {@link SplitBounds} for both taskIDs.
* Meaning there will be two taskId integers mapping to the same object.
* If there's any ordering to the pairing than we can probably just get away with only one
* taskID mapping to it, leaving both for consistency with {@link #mSplitTasks} for now.
*/
- private final Map<Integer, StagedSplitBounds> mTaskSplitBoundsMap = new HashMap<>();
+ private final Map<Integer, SplitBounds> mTaskSplitBoundsMap = new HashMap<>();
/**
* Creates {@link RecentTasksController}, returns {@code null} if the feature is not
@@ -110,7 +110,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
/**
* Adds a split pair. This call does not validate the taskIds, only that they are not the same.
*/
- public void addSplitPair(int taskId1, int taskId2, StagedSplitBounds splitBounds) {
+ public void addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds) {
if (taskId1 == taskId2) {
return;
}
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 ae5e075c4d3f..2bfa5db502ce 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
@@ -16,7 +16,6 @@
package com.android.wm.shell.splitscreen;
-import android.annotation.Nullable;
import android.content.Context;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -38,10 +37,9 @@ class MainStage extends StageTaskListener {
MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession, IconProvider iconProvider,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
- super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
- stageTaskUnfoldController);
+ SurfaceSession surfaceSession, IconProvider iconProvider) {
+ super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ iconProvider);
}
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 d55619f5e5ed..f92a0d3901b9 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
@@ -16,7 +16,6 @@
package com.android.wm.shell.splitscreen;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.view.SurfaceSession;
@@ -38,10 +37,9 @@ class SideStage extends StageTaskListener {
SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession, IconProvider iconProvider,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
- super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
- stageTaskUnfoldController);
+ SurfaceSession surfaceSession, IconProvider iconProvider) {
+ super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ iconProvider);
}
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 448773ae9ea2..29b6311e5041 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.graphics.Rect;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -58,6 +59,7 @@ public interface SplitScreen {
interface SplitScreenListener {
default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
+ default void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) {}
default void onSplitVisibilityChanged(boolean visible) {}
}
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 31b510c38457..ee493668994d 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
@@ -83,11 +83,10 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executor;
-import javax.inject.Provider;
-
/**
* Class manages split-screen multitasking mode and implements the main interface
* {@link SplitScreen}.
@@ -108,7 +107,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
static final int EXIT_REASON_ROOT_TASK_VANISHED = 6;
static final int EXIT_REASON_SCREEN_LOCKED = 7;
static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
- static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
+ public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -138,7 +137,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final SplitscreenEventLogger mLogger;
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
- private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
private StageCoordinator mStageCoordinator;
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
@@ -152,8 +150,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
- Optional<RecentTasksController> recentTasks,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ Optional<RecentTasksController> recentTasks) {
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
@@ -164,7 +161,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mDisplayInsetsController = displayInsetsController;
mTransitions = transitions;
mTransactionPool = transactionPool;
- mUnfoldControllerProvider = unfoldControllerProvider;
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
@@ -190,7 +186,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
- mIconProvider, mMainExecutor, mRecentTasksOptional, mUnfoldControllerProvider);
+ mIconProvider, mMainExecutor, mRecentTasksOptional);
}
}
@@ -198,6 +194,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator.isSplitScreenVisible();
}
+ public StageCoordinator getTransitionHandler() {
+ return mStageCoordinator;
+ }
+
@Nullable
public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) {
@@ -222,6 +222,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
new WindowContainerTransaction());
}
+ /**
+ * Update surfaces of the split screen layout based on the current state
+ * @param transaction to write the updates to
+ */
+ public void updateSplitScreenSurfaces(SurfaceControl.Transaction transaction) {
+ mStageCoordinator.updateSurfaces(transaction);
+ }
+
private boolean moveToStage(int taskId, @StageType int stageType,
@SplitPosition int stagePosition, WindowContainerTransaction wct) {
final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
@@ -362,11 +370,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (apps == null || apps.length == 0) {
final ActivityManager.RunningTaskInfo pairedTaskInfo =
getTaskInfo(SplitLayout.reversePosition(position));
- final ComponentName pairedActivity =
- pairedTaskInfo != null ? pairedTaskInfo.baseActivity : null;
- final ComponentName intentActivity =
- intent.getIntent() != null ? intent.getIntent().getComponent() : null;
- if (pairedActivity != null && pairedActivity.equals(intentActivity)) {
+ final ComponentName pairedActivity = pairedTaskInfo != null
+ ? pairedTaskInfo.baseIntent.getComponent() : null;
+ final ComponentName intentActivity = intent.getIntent() != null
+ ? intent.getIntent().getComponent() : null;
+
+ if (Objects.equals(pairedActivity, intentActivity)) {
// Switch split position if dragging the same activity to another side.
setSideStagePosition(SplitLayout.reversePosition(
mStageCoordinator.getSideStagePosition()));
@@ -535,6 +544,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
+ public void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onSplitBoundsChanged(rootBounds, mainBounds,
+ sideBounds);
+ });
+ }
+ }
+
+ @Override
public void onSplitVisibilityChanged(boolean visible) {
for (int i = 0; i < mExecutors.size(); i++) {
final int index = i;
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 cd121ed41fdd..f7057d454df9 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
@@ -21,7 +21,6 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
@@ -58,9 +57,6 @@ import java.util.ArrayList;
class SplitScreenTransitions {
private static final String TAG = "SplitScreenTransitions";
- /** 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;
-
private final TransactionPool mTransactionPool;
private final Transitions mTransitions;
private final Runnable mOnFinish;
@@ -70,8 +66,9 @@ class SplitScreenTransitions {
IBinder mPendingRecent = null;
private IBinder mAnimatingTransition = null;
- private OneShotRemoteHandler mPendingRemoteHandler = null;
+ OneShotRemoteHandler mPendingRemoteHandler = null;
private OneShotRemoteHandler mActiveRemoteHandler = null;
+ private boolean mEnterTransitionMerged;
private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish;
@@ -94,7 +91,8 @@ class SplitScreenTransitions {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback,
- @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
+ @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
+ @NonNull WindowContainerToken topRoot) {
mFinishCallback = finishCallback;
mAnimatingTransition = transition;
if (mPendingRemoteHandler != null) {
@@ -104,12 +102,12 @@ class SplitScreenTransitions {
mPendingRemoteHandler = null;
return;
}
- playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
+ playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot);
}
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
- @NonNull WindowContainerToken sideRoot) {
+ @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
mFinishTransaction = mTransactionPool.acquire();
// Play some place-holder fade animations
@@ -140,7 +138,10 @@ class SplitScreenTransitions {
endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
startExampleResizeAnimation(leash, startBounds, endBounds);
}
- if (change.getParent() != null) {
+ boolean isRootOrSplitSideRoot = change.getParent() == null
+ || topRoot.equals(change.getParent());
+ // For enter or exit, we only want to animate the side roots but not the top-root.
+ if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) {
continue;
}
@@ -187,27 +188,28 @@ class SplitScreenTransitions {
}
/** Starts a transition to dismiss split. */
- IBinder startDismissTransition(@Nullable IBinder transition, WindowContainerTransaction wct,
+ IBinder startDismissTransition(WindowContainerTransaction wct,
Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
@SplitScreenController.ExitReason int reason) {
final int type = reason == EXIT_REASON_DRAG_DIVIDER
? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS;
- if (transition == null) {
- transition = mTransitions.startTransition(type, wct, handler);
- }
+ IBinder transition = mTransitions.startTransition(type, wct, handler);
+ setDismissTransition(transition, dismissTop, reason);
+ return transition;
+ }
+
+ /** Sets a transition to dismiss split. */
+ void setDismissTransition(@NonNull IBinder transition, @SplitScreen.StageType int dismissTop,
+ @SplitScreenController.ExitReason int reason) {
mPendingDismiss = new DismissTransition(transition, reason, dismissTop);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Dismiss due to %s. toTop=%s",
exitReasonToString(reason), stageTypeToString(dismissTop));
- return transition;
}
- IBinder startRecentTransition(@Nullable IBinder transition, WindowContainerTransaction wct,
- Transitions.TransitionHandler handler, @Nullable RemoteTransition remoteTransition) {
- if (transition == null) {
- transition = mTransitions.startTransition(TRANSIT_OPEN, wct, handler);
- }
+ void setRecentTransition(@NonNull IBinder transition,
+ @Nullable RemoteTransition remoteTransition) {
mPendingRecent = transition;
if (remoteTransition != null) {
@@ -219,7 +221,6 @@ class SplitScreenTransitions {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Enter recent panel");
- return transition;
}
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
@@ -229,6 +230,18 @@ class SplitScreenTransitions {
}
}
+ void onTransitionMerged(@NonNull IBinder transition) {
+ // Once a pending enter transition got merged, make sure to append the reset of finishing
+ // operations to the finish transition.
+ if (transition == mPendingEnter) {
+ mFinishTransaction = mTransactionPool.acquire();
+ mStageCoordinator.finishEnterSplitScreen(mFinishTransaction);
+ mPendingEnter = null;
+ mPendingRemoteHandler = null;
+ mEnterTransitionMerged = true;
+ }
+ }
+
void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
if (!mAnimations.isEmpty()) return;
if (mAnimatingTransition == mPendingEnter) {
@@ -238,18 +251,16 @@ class SplitScreenTransitions {
mPendingDismiss = null;
}
if (mAnimatingTransition == mPendingRecent) {
- // If the clean-up wct is null when finishing recent transition, it indicates it's
- // returning to home and thus no need to reorder tasks.
- final boolean returnToHome = wct == null;
- if (returnToHome) {
- wct = new WindowContainerTransaction();
+ if (!mEnterTransitionMerged) {
+ if (wct == null) wct = new WindowContainerTransaction();
+ mStageCoordinator.onRecentTransitionFinished(wct, mFinishTransaction);
}
- mStageCoordinator.onRecentTransitionFinished(returnToHome, wct, mFinishTransaction);
mPendingRecent = null;
}
mPendingRemoteHandler = null;
mActiveRemoteHandler = null;
mAnimatingTransition = null;
+ mEnterTransitionMerged = false;
mOnFinish.run();
if (mFinishTransaction != null) {
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 7ea32a6d8f86..774d6aecf16b 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,7 +28,9 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -47,7 +49,6 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
-import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -83,6 +84,7 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
+import android.window.DisplayAreaInfo;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -109,15 +111,13 @@ import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.StagedSplitBounds;
+import com.android.wm.shell.util.SplitBounds;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
-import javax.inject.Provider;
-
/**
* Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
* {@link SideStage} stages.
@@ -131,20 +131,21 @@ import javax.inject.Provider;
* This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
* {@link #onStageHasChildrenChanged(StageListenerImpl).}
*/
-class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
ShellTaskOrganizer.TaskListener {
private static final String TAG = StageCoordinator.class.getSimpleName();
+ /** 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;
+
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final MainStage mMainStage;
private final StageListenerImpl mMainStageListener = new StageListenerImpl();
- private final StageTaskUnfoldController mMainUnfoldController;
private final SideStage mSideStage;
private final StageListenerImpl mSideStageListener = new StageListenerImpl();
- private final StageTaskUnfoldController mSideUnfoldController;
private final DisplayLayout mDisplayLayout;
@SplitPosition
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -205,8 +206,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, SplitscreenEventLogger logger,
IconProvider iconProvider, ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ Optional<RecentTasksController> recentTasks) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -214,8 +214,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mLogger = logger;
mMainExecutor = mainExecutor;
mRecentTasks = recentTasks;
- mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
- mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
mMainStage = new MainStage(
@@ -225,8 +224,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStageListener,
mSyncQueue,
mSurfaceSession,
- iconProvider,
- mMainUnfoldController);
+ iconProvider);
mSideStage = new SideStage(
mContext,
mTaskOrganizer,
@@ -234,8 +232,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStageListener,
mSyncQueue,
mSurfaceSession,
- iconProvider,
- mSideUnfoldController);
+ iconProvider);
mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
@@ -258,8 +255,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger, ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ Optional<RecentTasksController> recentTasks) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -273,8 +269,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout = splitLayout;
mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
this::onTransitionAnimationComplete, this);
- mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
- mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
mLogger = logger;
mMainExecutor = mainExecutor;
mRecentTasks = recentTasks;
@@ -365,10 +359,15 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
sideOptions = sideOptions != null ? sideOptions : new Bundle();
setSideStagePosition(sidePosition, wct);
+ if (mMainStage.isActive()) {
+ mMainStage.evictAllChildren(wct);
+ mSideStage.evictAllChildren(wct);
+ } else {
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(wct, false /* reparent */);
+ }
mSplitLayout.setDivideRatio(splitRatio);
- // Build a request WCT that will launch both apps such that task 0 is on the main stage
- // while task 1 is on the side stage.
- mMainStage.activate(wct, false /* reparent */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
@@ -626,7 +625,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
onLayoutSizeChanged(mSplitLayout);
} else {
updateWindowBounds(mSplitLayout, wct);
- updateUnfoldBounds();
+ sendOnBoundsChanged();
}
}
}
@@ -641,7 +640,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (ENABLE_SHELL_TRANSITIONS) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
- mSplitTransitions.startDismissTransition(null /* transition */, wct, this,
+ mSplitTransitions.startDismissTransition(wct, this,
mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
} else {
exitSplitScreen(
@@ -674,8 +673,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(dismissTop, wct);
- mSplitTransitions.startDismissTransition(null /* transition */, wct, this,
- dismissTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ mSplitTransitions.startDismissTransition(wct, this, dismissTop,
+ EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
}
}
@@ -738,10 +737,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setDividerVisibility(false, t);
});
- // Hide divider and reset its position.
- mSplitLayout.resetDividerPosition();
- mSplitLayout.release();
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ onTransitionAnimationComplete();
Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason));
// Log the exit
if (childrenToTop != null) {
@@ -859,6 +855,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
listener.onSplitVisibilityChanged(isSplitScreenVisible());
+ if (mSplitLayout != null) {
+ listener.onSplitBoundsChanged(mSplitLayout.getRootBounds(), getMainStageBounds(),
+ getSideStageBounds());
+ }
mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
}
@@ -871,6 +871,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ private void sendOnBoundsChanged() {
+ if (mSplitLayout == null) return;
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onSplitBoundsChanged(mSplitLayout.getRootBounds(),
+ getMainStageBounds(), getSideStageBounds());
+ }
+ }
+
private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
boolean present, boolean visible) {
int stage;
@@ -920,7 +928,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
leftTopTaskId = mainStageTopTaskId;
rightBottomTaskId = sideStageTopTaskId;
}
- StagedSplitBounds splitBounds = new StagedSplitBounds(topLeftBounds, bottomRightBounds,
+ SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds,
leftTopTaskId, rightBottomTaskId);
if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
// Update the pair for the top tasks
@@ -934,12 +942,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final SplitScreen.SplitScreenListener l = mListeners.get(i);
l.onSplitVisibilityChanged(mDividerVisible);
}
-
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
- mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
- updateUnfoldBounds();
- }
+ sendOnBoundsChanged();
}
@Override
@@ -960,11 +963,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.init();
- mSideUnfoldController.init();
- }
-
onRootTaskAppeared();
}
@@ -978,13 +976,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRootTaskInfo = taskInfo;
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
- && mMainStage.isActive()) {
- // TODO(b/204925795): With Shell transition, We are handling split bounds rotation at
- // onRotateDisplay. But still need to handle unfold case.
- if (ENABLE_SHELL_TRANSITIONS) {
- updateUnfoldBounds();
- return;
- }
+ && mMainStage.isActive()
+ && !ENABLE_SHELL_TRANSITIONS) {
// Clear the divider remote animating flag as the divider will be re-rendered to apply
// the new rotation config.
mIsDividerRemoteAnimating = false;
@@ -1024,8 +1017,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
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,
- true /* moveTogether */);
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
mTaskOrganizer.applyTransaction(wct);
}
@@ -1202,8 +1194,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(dismissTop, wct);
- mSplitTransitions.startDismissTransition(
- null /* transition */, wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER);
+ if (mRootTaskInfo != null) {
+ wct.setDoNotPip(mRootTaskInfo.token);
+ }
+ mSplitTransitions.startDismissTransition(wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER);
}
@Override
@@ -1233,7 +1227,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void onLayoutSizeChanged(SplitLayout layout) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
- updateUnfoldBounds();
+ sendOnBoundsChanged();
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
setResizingSplits(false /* resizing */);
@@ -1244,15 +1238,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
- private void updateUnfoldBounds() {
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.onLayoutChanged(getMainStageBounds(), getMainStagePosition(),
- isLandscape());
- mSideUnfoldController.onLayoutChanged(getSideStageBounds(), getSideStagePosition(),
- isLandscape());
- }
- }
-
private boolean isLandscape() {
return mSplitLayout.isLandscape();
}
@@ -1321,7 +1306,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (displayId != DEFAULT_DISPLAY) {
return;
}
- mDisplayController.addDisplayChangingController(this::onRotateDisplay);
+ mDisplayController.addDisplayChangingController(this::onDisplayChange);
}
@Override
@@ -1332,16 +1317,24 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId));
}
- private void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- WindowContainerTransaction wct) {
+ void updateSurfaces(SurfaceControl.Transaction transaction) {
+ updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false);
+ mSplitLayout.update(transaction);
+ }
+
+ private void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
if (!mMainStage.isActive()) return;
// Only do this when shell transition
if (!ENABLE_SHELL_TRANSITIONS) return;
mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
mSplitLayout.rotateTo(toRotation, mDisplayLayout.stableInsets());
+ if (newDisplayAreaInfo != null) {
+ mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration);
+ }
updateWindowBounds(mSplitLayout, wct);
- updateUnfoldBounds();
+ sendOnBoundsChanged();
}
private void onFoldedStateChanged(boolean folded) {
@@ -1392,7 +1385,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable TransitionRequestInfo request) {
final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
if (triggerTask == null) {
- if (mMainStage.isActive()) {
+ if (isSplitActive()) {
+ // Check if the display is rotating.
final TransitionRequestInfo.DisplayChange displayChange =
request.getDisplayChange();
if (request.getType() == TRANSIT_CHANGE && displayChange != null
@@ -1419,7 +1413,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
}
- if (mMainStage.isActive()) {
+ if (isSplitActive()) {
// Try to handle everything while in split-screen, so return a WCT even if it's empty.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split"
+ "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
@@ -1434,7 +1428,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE
: STAGE_TYPE_MAIN;
prepareExitSplitScreen(dismissTop, out);
- mSplitTransitions.startDismissTransition(transition, out, this, dismissTop,
+ mSplitTransitions.setDismissTransition(transition, dismissTop,
EXIT_REASON_APP_FINISHED);
}
} else if (isOpening && inFullscreen) {
@@ -1444,12 +1438,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
} else if (activityType == ACTIVITY_TYPE_HOME
|| activityType == ACTIVITY_TYPE_RECENTS) {
// Enter overview panel, so start recent transition.
- mSplitTransitions.startRecentTransition(transition, out, this,
+ mSplitTransitions.setRecentTransition(transition,
request.getRemoteTransition());
- } else {
- // Occluded by the other fullscreen task, so dismiss both.
+ } else if (mSplitTransitions.mPendingRecent == null) {
+ // If split-task is not controlled by recents animation
+ // and occluded by the other fullscreen task, dismiss both.
prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
- mSplitTransitions.startDismissTransition(transition, out, this,
+ mSplitTransitions.setDismissTransition(transition,
STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN);
}
}
@@ -1464,6 +1459,33 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return out;
}
+ /**
+ * This is used for mixed scenarios. For such scenarios, just make sure to include exiting
+ * split or entering split when appropriate.
+ */
+ public void addEnterOrExitIfNeeded(@Nullable TransitionRequestInfo request,
+ @NonNull WindowContainerTransaction outWCT) {
+ final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+ if (triggerTask != null && triggerTask.displayId != mDisplayId) {
+ // Skip handling task on the other display.
+ return;
+ }
+ final @WindowManager.TransitionType int type = request.getType();
+ if (isSplitActive() && !isOpeningType(type)
+ && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " One of the splits became "
+ + "empty during a mixed transition (one not handled by split),"
+ + " so make sure split-screen state is cleaned-up. "
+ + "mainStageCount=%d sideStageCount=%d", mMainStage.getChildCount(),
+ mSideStage.getChildCount());
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT);
+ }
+ }
+
+ public boolean isSplitActive() {
+ return mMainStage.isActive();
+ }
+
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
@@ -1473,15 +1495,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onTransitionMerged(@NonNull IBinder transition) {
- // Once the pending enter transition got merged, make sure to bring divider bar visible and
- // clear the pending transition from cache to prevent mess-up the following state.
- if (transition == mSplitTransitions.mPendingEnter) {
- final SurfaceControl.Transaction t = mTransactionPool.acquire();
- finishEnterSplitScreen(t);
- mSplitTransitions.mPendingEnter = null;
- t.apply();
- mTransactionPool.release(t);
- }
+ mSplitTransitions.onTransitionMerged(transition);
}
@Override
@@ -1552,11 +1566,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!shouldAnimate) return false;
mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
- finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
+ mRootTaskInfo.token);
return true;
}
- void onTransitionAnimationComplete() {
+ /** Called to clean-up state and do house-keeping after the animation is done. */
+ public void onTransitionAnimationComplete() {
// If still playing, let it finish.
if (!mMainStage.isActive()) {
// Update divider state after animation so that it is still around and positioned
@@ -1620,8 +1636,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
- private boolean startPendingDismissAnimation(
- @NonNull SplitScreenTransitions.DismissTransition dismissTransition,
+ /** Synchronize split-screen state with transition and make appropriate preparations. */
+ public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
// Make some noise if things aren't totally expected. These states shouldn't effect
@@ -1654,7 +1670,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
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(dismissTransition.mReason) && mShouldUpdateRecents) {
+ if (shouldBreakPairedTaskInRecents(dismissReason) && mShouldUpdateRecents) {
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo != null
@@ -1671,30 +1687,37 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Wait until after animation to update divider
// Reset crops so they don't interfere with subsequent launches
- t.setWindowCrop(mMainStage.mRootLeash, null);
- t.setWindowCrop(mSideStage.mRootLeash, null);
+ t.setCrop(mMainStage.mRootLeash, null);
+ t.setCrop(mSideStage.mRootLeash, null);
+
+ if (toStage == STAGE_TYPE_UNDEFINED) {
+ logExit(dismissReason);
+ } else {
+ logExitToStage(dismissReason, toStage == STAGE_TYPE_MAIN);
+ }
+ // Hide divider and dim layer on transition finished.
+ setDividerVisibility(false, finishT);
+ finishT.hide(mMainStage.mDimLayer);
+ finishT.hide(mSideStage.mDimLayer);
+ }
+
+ private boolean startPendingDismissAnimation(
+ @NonNull SplitScreenTransitions.DismissTransition dismissTransition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction finishT) {
+ prepareDismissAnimation(dismissTransition.mDismissTop, dismissTransition.mReason, info,
+ t, finishT);
if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
- logExit(dismissTransition.mReason);
// TODO: Have a proper remote for this. Until then, though, reset state and use the
// normal animation stuff (which falls back to the normal launcher remote).
+ t.hide(mSplitLayout.getDividerLeash());
mSplitLayout.release(t);
mSplitTransitions.mPendingDismiss = null;
return false;
- } else {
- logExitToStage(dismissTransition.mReason,
- dismissTransition.mDismissTop == STAGE_TYPE_MAIN);
}
addDividerBarToTransition(info, t, false /* show */);
- // We're dismissing split by moving the other one to fullscreen.
- // Since we don't have any animations for this yet, just use the internal example
- // animations.
-
- // Hide divider and dim layer on transition finished.
- setDividerVisibility(false, finishT);
- finishT.hide(mMainStage.mDimLayer);
- finishT.hide(mSideStage.mDimLayer);
return true;
}
@@ -1704,26 +1727,26 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
- void onRecentTransitionFinished(boolean returnToHome, WindowContainerTransaction wct,
+ void onRecentTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction finishT) {
- // Exclude the case that the split screen has been dismissed already.
- if (!mMainStage.isActive()) {
- // The latest split dismissing transition might be a no-op transition and thus won't
- // callback startAnimation, update split visibility here to cover this kind of no-op
- // transition case.
- setSplitsVisible(false);
- return;
+ // Check if the recent transition is finished by returning to the current split so we can
+ // restore the divider bar.
+ for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
+ final WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
+ final IBinder container = op.getContainer();
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+ && (mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container))) {
+ setDividerVisibility(true, finishT);
+ return;
+ }
}
- if (returnToHome) {
- // When returning to home from recent apps, the splitting tasks are already hidden, so
- // append the reset of dismissing operations into the clean-up wct.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
- setSplitsVisible(false);
- logExit(EXIT_REASON_RETURN_HOME);
- } else {
- setDividerVisibility(true, finishT);
- }
+ // Dismiss the split screen is it's not returning to split.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ setSplitsVisible(false);
+ setDividerVisibility(false, finishT);
+ logExit(EXIT_REASON_UNKNOWN);
}
private void addDividerBarToTransition(@NonNull TransitionInfo info,
@@ -1870,8 +1893,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(stageType, wct);
- mSplitTransitions.startDismissTransition(null /* transition */, wct,
- StageCoordinator.this, stageType,
+ mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
}
}
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 949bf5f55808..23eec96a5d8f 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
@@ -31,6 +31,7 @@ import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.IBinder;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
@@ -47,6 +48,7 @@ import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import java.io.PrintWriter;
+import java.util.function.Predicate;
/**
* Base class that handle common task org. related for split-screen stages.
@@ -95,18 +97,14 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
// TODO(b/204308910): Extracts SplitDecorManager related code to common package.
private SplitDecorManager mSplitDecorManager;
- private final StageTaskUnfoldController mStageTaskUnfoldController;
-
StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession, IconProvider iconProvider,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ SurfaceSession surfaceSession, IconProvider iconProvider) {
mContext = context;
mCallbacks = callbacks;
mSyncQueue = syncQueue;
mSurfaceSession = surfaceSession;
mIconProvider = iconProvider;
- mStageTaskUnfoldController = stageTaskUnfoldController;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
}
@@ -119,63 +117,53 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
boolean containsToken(WindowContainerToken token) {
- if (token.equals(mRootTaskInfo.token)) {
- return true;
- }
-
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- if (token.equals(mChildrenTaskInfo.valueAt(i).token)) {
- return true;
- }
- }
+ return contains(t -> t.token.equals(token));
+ }
- return false;
+ boolean containsContainer(IBinder binder) {
+ return contains(t -> t.token.asBinder() == binder);
}
/**
* Returns the top visible child task's id.
*/
int getTopVisibleChildTaskId() {
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
- if (info.isVisible) {
- return info.taskId;
- }
- }
- return INVALID_TASK_ID;
+ final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.isVisible);
+ return taskInfo != null ? taskInfo.taskId : INVALID_TASK_ID;
}
/**
* Returns the top activity uid for the top child task.
*/
int getTopChildTaskUid() {
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
- if (info.topActivityInfo == null) {
- continue;
- }
- return info.topActivityInfo.applicationInfo.uid;
- }
- return 0;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ getChildTaskInfo(t -> t.topActivityInfo != null);
+ return taskInfo != null ? taskInfo.topActivityInfo.applicationInfo.uid : 0;
}
/** @return {@code true} if this listener contains the currently focused task. */
boolean isFocused() {
- if (mRootTaskInfo == null) {
- return false;
- }
+ return contains(t -> t.isFocused);
+ }
- if (mRootTaskInfo.isFocused) {
+ private boolean contains(Predicate<ActivityManager.RunningTaskInfo> predicate) {
+ if (mRootTaskInfo != null && predicate.test(mRootTaskInfo)) {
return true;
}
+ return getChildTaskInfo(predicate) != null;
+ }
+
+ @Nullable
+ private ActivityManager.RunningTaskInfo getChildTaskInfo(
+ Predicate<ActivityManager.RunningTaskInfo> predicate) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- if (mChildrenTaskInfo.valueAt(i).isFocused) {
- return true;
+ final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+ if (predicate.test(taskInfo)) {
+ return taskInfo;
}
}
-
- return false;
+ return null;
}
@Override
@@ -207,10 +195,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
-
- if (mStageTaskUnfoldController != null) {
- mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
- }
}
@Override
@@ -266,22 +250,18 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mChildrenTaskInfo.remove(taskId);
mChildrenLeashes.remove(taskId);
mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
- mCallbacks.onChildTaskEnterPip(taskId);
- }
if (ENABLE_SHELL_TRANSITIONS) {
// Status is managed/synchronized by the transition lifecycle.
return;
}
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ mCallbacks.onChildTaskEnterPip(taskId);
+ }
sendStatusChanged();
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
-
- if (mStageTaskUnfoldController != null) {
- mStageTaskUnfoldController.onTaskVanished(taskInfo);
- }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
deleted file mode 100644
index 45f6d3c8b154..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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.stagesplit;
-
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.window.RemoteTransition;
-
-import com.android.wm.shell.stagesplit.ISplitScreenListener;
-
-/**
- * Interface that is exposed to remote callers to manipulate the splitscreen feature.
- */
-interface ISplitScreen {
-
- /**
- * Registers a split screen listener.
- */
- oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;
-
- /**
- * Unregisters a split screen listener.
- */
- oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
-
- /**
- * Hides the side-stage if it is currently visible.
- */
- oneway void setSideStageVisibility(boolean visible) = 3;
-
- /**
- * Removes a task from the side stage.
- */
- oneway void removeFromSideStage(int taskId) = 4;
-
- /**
- * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID
- * to indicate leaving no top task after leaving split-screen.
- */
- oneway void exitSplitScreen(int toTopTaskId) = 5;
-
- /**
- * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
- */
- oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;
-
- /**
- * Starts a task in a stage.
- */
- oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;
-
- /**
- * Starts a shortcut in a stage.
- */
- oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
- in Bundle options, in UserHandle user) = 8;
-
- /**
- * Starts an activity in a stage.
- */
- oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
- int position, in Bundle options) = 9;
-
- /**
- * Starts tasks simultaneously in one transition.
- */
- oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
- in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10;
-
- /**
- * Version of startTasks using legacy transition system.
- */
- oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
- int sideTaskId, in Bundle sideOptions, int sidePosition,
- in RemoteAnimationAdapter adapter) = 11;
-
- /**
- * Blocking call that notifies and gets additional split-screen targets when entering
- * recents (for example: the dividerBar).
- * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled).
- * @param appTargets apps that will be re-parented to display area
- */
- RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
- in RemoteAnimationTarget[] appTargets) = 12;
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
deleted file mode 100644
index 83855be91e04..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
+++ /dev/null
@@ -1,104 +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.stagesplit;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.view.SurfaceSession;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-/**
- * Main stage for split-screen mode. When split-screen is active all standard activity types launch
- * on the main stage, except for task that are explicitly pinned to the {@link SideStage}.
- * @see StageCoordinator
- */
-class MainStage extends StageTaskListener {
- private static final String TAG = MainStage.class.getSimpleName();
-
- private boolean mIsActive = false;
-
- MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
- StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
- super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
- stageTaskUnfoldController);
- }
-
- boolean isActive() {
- return mIsActive;
- }
-
- void activate(Rect rootBounds, WindowContainerTransaction wct) {
- if (mIsActive) return;
-
- final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setBounds(rootToken, rootBounds)
- .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
- .setLaunchRoot(
- rootToken,
- CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES)
- .reparentTasks(
- null /* currentParent */,
- rootToken,
- CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES,
- true /* onTop */)
- // Moving the root task to top after the child tasks were re-parented , or the root
- // task cannot be visible and focused.
- .reorder(rootToken, true /* onTop */);
-
- mIsActive = true;
- }
-
- void deactivate(WindowContainerTransaction wct) {
- deactivate(wct, false /* toTop */);
- }
-
- void deactivate(WindowContainerTransaction wct, boolean toTop) {
- if (!mIsActive) return;
- mIsActive = false;
-
- if (mRootTaskInfo == null) return;
- final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setLaunchRoot(
- rootToken,
- null,
- null)
- .reparentTasks(
- rootToken,
- null /* newParent */,
- CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
- CONTROLLED_ACTIVITY_TYPES,
- toTop)
- // We want this re-order to the bottom regardless since we are re-parenting
- // all its tasks.
- .reorder(rootToken, false /* onTop */);
- }
-
- void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) {
- wct.setBounds(mRootTaskInfo.token, bounds)
- .setWindowingMode(mRootTaskInfo.token, windowingMode);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS
deleted file mode 100644
index 264e88f32bff..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# WM shell sub-modules stagesplit owner
-chenghsiuchang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
deleted file mode 100644
index 8fbad52c630f..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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.stagesplit;
-
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Binder;
-import android.view.IWindow;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.LayoutInflater;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.WindowlessWindowManager;
-import android.widget.FrameLayout;
-
-import com.android.wm.shell.R;
-
-/**
- * Handles drawing outline of the bounds of provided root surface. The outline will be drown with
- * the consideration of display insets like status bar, navigation bar and display cutout.
- */
-class OutlineManager extends WindowlessWindowManager {
- private static final String WINDOW_NAME = "SplitOutlineLayer";
- private final Context mContext;
- private final Rect mRootBounds = new Rect();
- private final Rect mTempRect = new Rect();
- private final Rect mLastOutlineBounds = new Rect();
- private final InsetsState mInsetsState = new InsetsState();
- private final int mExpandedTaskBarHeight;
- private OutlineView mOutlineView;
- private SurfaceControlViewHost mViewHost;
- private SurfaceControl mHostLeash;
- private SurfaceControl mLeash;
-
- OutlineManager(Context context, Configuration configuration) {
- super(configuration, null /* rootSurface */, null /* hostInputToken */);
- mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
- null /* options */);
- mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.taskbar_frame_height);
- }
-
- @Override
- protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
- b.setParent(mHostLeash);
- }
-
- void inflate(SurfaceControl rootLeash, Rect rootBounds) {
- if (mLeash != null || mViewHost != null) return;
-
- mHostLeash = rootLeash;
- mRootBounds.set(rootBounds);
- mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
-
- final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
- .inflate(R.layout.split_outline, null);
- mOutlineView = rootLayout.findViewById(R.id.split_outline);
-
- final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
- FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
- lp.width = mRootBounds.width();
- lp.height = mRootBounds.height();
- lp.token = new Binder();
- lp.setTitle(WINDOW_NAME);
- lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
- // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
- // TRUSTED_OVERLAY for windowless window without input channel.
- mViewHost.setView(rootLayout, lp);
- mLeash = getSurfaceControl(mViewHost.getWindowToken());
-
- drawOutline();
- }
-
- void release() {
- if (mViewHost != null) {
- mViewHost.release();
- mViewHost = null;
- }
- mRootBounds.setEmpty();
- mLastOutlineBounds.setEmpty();
- mOutlineView = null;
- mHostLeash = null;
- mLeash = null;
- }
-
- @Nullable
- SurfaceControl getOutlineLeash() {
- return mLeash;
- }
-
- void setVisibility(boolean visible) {
- if (mOutlineView != null) {
- mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- }
- }
-
- void setRootBounds(Rect rootBounds) {
- if (mViewHost == null || mViewHost.getView() == null) {
- return;
- }
-
- if (!mRootBounds.equals(rootBounds)) {
- WindowManager.LayoutParams lp =
- (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
- lp.width = rootBounds.width();
- lp.height = rootBounds.height();
- mViewHost.relayout(lp);
- mRootBounds.set(rootBounds);
- drawOutline();
- }
- }
-
- void onInsetsChanged(InsetsState insetsState) {
- if (!mInsetsState.equals(insetsState)) {
- mInsetsState.set(insetsState);
- drawOutline();
- }
- }
-
- private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) {
- outBounds.set(rootBounds);
- final InsetsSource taskBarInsetsSource =
- insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
- // Only insets the divider bar with task bar when it's expanded so that the rounded corners
- // will be drawn against task bar.
- if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
- outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds));
- }
-
- // Offset the coordinate from screen based to surface based.
- outBounds.offset(-rootBounds.left, -rootBounds.top);
- }
-
- void drawOutline() {
- if (mOutlineView == null) {
- return;
- }
-
- computeOutlineBounds(mRootBounds, mInsetsState, mTempRect);
- if (mTempRect.equals(mLastOutlineBounds)) {
- return;
- }
-
- ViewGroup.MarginLayoutParams lp =
- (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams();
- lp.leftMargin = mTempRect.left;
- lp.topMargin = mTempRect.top;
- lp.width = mTempRect.width();
- lp.height = mTempRect.height();
- mOutlineView.setLayoutParams(lp);
- mLastOutlineBounds.set(mTempRect);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
deleted file mode 100644
index 92b1381fc808..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.stagesplit;
-
-import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
-import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
-import static android.view.RoundedCorner.POSITION_TOP_LEFT;
-import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.util.AttributeSet;
-import android.view.RoundedCorner;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.R;
-
-/** View for drawing split outline. */
-public class OutlineView extends View {
- private final Paint mPaint = new Paint();
- private final Path mPath = new Path();
- private final float[] mRadii = new float[8];
-
- public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeWidth(
- getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
- mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
- }
-
- @Override
- protected void onAttachedToWindow() {
- // TODO(b/200850654): match the screen corners with the actual display decor.
- mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
- mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
- mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
- mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
- }
-
- private int getCornerRadius(@RoundedCorner.Position int position) {
- final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
- return roundedCorner == null ? 0 : roundedCorner.getRadius();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (changed) {
- mPath.reset();
- mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW);
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- canvas.drawPath(mPath, mPaint);
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
deleted file mode 100644
index 55c4f3aea19a..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
+++ /dev/null
@@ -1,144 +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.stagesplit;
-
-import android.annotation.CallSuper;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.graphics.Rect;
-import android.view.InsetsSourceControl;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-/**
- * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
- * here. All other task are launch in the {@link MainStage}.
- *
- * @see StageCoordinator
- */
-class SideStage extends StageTaskListener implements
- DisplayInsetsController.OnInsetsChangedListener {
- private static final String TAG = SideStage.class.getSimpleName();
- private final Context mContext;
- private OutlineManager mOutlineManager;
-
- SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
- StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
- super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
- stageTaskUnfoldController);
- mContext = context;
- }
-
- void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
- WindowContainerTransaction wct) {
- final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setBounds(rootToken, rootBounds)
- .reparent(task.token, rootToken, true /* onTop*/)
- // Moving the root task to top after the child tasks were reparented , or the root
- // task cannot be visible and focused.
- .reorder(rootToken, true /* onTop */);
- }
-
- boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
- // No matter if the root task is empty or not, moving the root to bottom because it no
- // longer preserves visible child task.
- wct.reorder(mRootTaskInfo.token, false /* onTop */);
- if (mChildrenTaskInfo.size() == 0) return false;
- wct.reparentTasks(
- mRootTaskInfo.token,
- null /* newParent */,
- CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
- CONTROLLED_ACTIVITY_TYPES,
- toTop);
- return true;
- }
-
- boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
- final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
- if (task == null) return false;
- wct.reparent(task.token, newParent, false /* onTop */);
- return true;
- }
-
- @Nullable
- public SurfaceControl getOutlineLeash() {
- return mOutlineManager.getOutlineLeash();
- }
-
- @Override
- @CallSuper
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- super.onTaskAppeared(taskInfo, leash);
- if (isRootTask(taskInfo)) {
- mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
- enableOutline(true);
- }
- }
-
- @Override
- @CallSuper
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- super.onTaskInfoChanged(taskInfo);
- if (isRootTask(taskInfo)) {
- mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
- }
- }
-
- private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
- return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
- }
-
- void enableOutline(boolean enable) {
- if (mOutlineManager == null) {
- return;
- }
-
- if (enable) {
- if (mRootTaskInfo != null) {
- mOutlineManager.inflate(mRootLeash,
- mRootTaskInfo.configuration.windowConfiguration.getBounds());
- }
- } else {
- mOutlineManager.release();
- }
- }
-
- void setOutlineVisibility(boolean visible) {
- mOutlineManager.setVisibility(visible);
- }
-
- @Override
- public void insetsChanged(InsetsState insetsState) {
- mOutlineManager.onInsetsChanged(insetsState);
- }
-
- @Override
- public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) {
- insetsChanged(insetsState);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
deleted file mode 100644
index c5d231262cd2..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
+++ /dev/null
@@ -1,99 +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.stagesplit;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-
-import java.util.concurrent.Executor;
-
-/**
- * Interface to engage split-screen feature.
- * TODO: Figure out which of these are actually needed outside of the Shell
- */
-@ExternalThread
-public interface SplitScreen {
- /**
- * Stage type isn't specified normally meaning to use what ever the default is.
- * E.g. exit split-screen and launch the app in fullscreen.
- */
- int STAGE_TYPE_UNDEFINED = -1;
- /**
- * The main stage type.
- * @see MainStage
- */
- int STAGE_TYPE_MAIN = 0;
-
- /**
- * The side stage type.
- * @see SideStage
- */
- int STAGE_TYPE_SIDE = 1;
-
- @IntDef(prefix = { "STAGE_TYPE_" }, value = {
- STAGE_TYPE_UNDEFINED,
- STAGE_TYPE_MAIN,
- STAGE_TYPE_SIDE
- })
- @interface StageType {}
-
- /** Callback interface for listening to changes in a split-screen stage. */
- interface SplitScreenListener {
- default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
- default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
- default void onSplitVisibilityChanged(boolean visible) {}
- }
-
- /** Registers listener that gets split screen callback. */
- void registerSplitScreenListener(@NonNull SplitScreenListener listener,
- @NonNull Executor executor);
-
- /** Unregisters listener that gets split screen callback. */
- void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
-
- /**
- * Returns a binder that can be passed to an external process to manipulate SplitScreen.
- */
- default ISplitScreen createExternalInterface() {
- return null;
- }
-
- /**
- * Called when the keyguard occluded state changes.
- * @param occluded Indicates if the keyguard is now occluded.
- */
- void onKeyguardOccludedChanged(boolean occluded);
-
- /**
- * Called when the visibility of the keyguard changes.
- * @param showing Indicates if the keyguard is now visible.
- */
- void onKeyguardVisibilityChanged(boolean showing);
-
- /** Get a string representation of a stage type */
- static String stageTypeToString(@StageType int stage) {
- switch (stage) {
- case STAGE_TYPE_UNDEFINED: return "UNDEFINED";
- case STAGE_TYPE_MAIN: return "MAIN";
- case STAGE_TYPE_SIDE: return "SIDE";
- default: return "UNKNOWN(" + stage + ")";
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
deleted file mode 100644
index 07174051a344..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
+++ /dev/null
@@ -1,595 +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.stagesplit;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-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.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
-
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.view.WindowManager;
-import android.window.RemoteTransition;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.logging.InstanceId;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.RemoteCallable;
-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.annotations.ExternalThread;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.draganddrop.DragAndDropPolicy;
-import com.android.wm.shell.transition.LegacyTransitions;
-import com.android.wm.shell.transition.Transitions;
-
-import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
-import javax.inject.Provider;
-
-/**
- * Class manages split-screen multitasking mode and implements the main interface
- * {@link SplitScreen}.
- * @see StageCoordinator
- */
-// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
-public class SplitScreenController implements DragAndDropPolicy.Starter,
- RemoteCallable<SplitScreenController> {
- private static final String TAG = SplitScreenController.class.getSimpleName();
-
- private final ShellTaskOrganizer mTaskOrganizer;
- private final SyncTransactionQueue mSyncQueue;
- private final Context mContext;
- private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
- private final ShellExecutor mMainExecutor;
- private final SplitScreenImpl mImpl = new SplitScreenImpl();
- private final DisplayImeController mDisplayImeController;
- private final DisplayInsetsController mDisplayInsetsController;
- private final Transitions mTransitions;
- private final TransactionPool mTransactionPool;
- private final SplitscreenEventLogger mLogger;
- private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
-
- private StageCoordinator mStageCoordinator;
-
- public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue, Context context,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer,
- ShellExecutor mainExecutor, DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController,
- Transitions transitions, TransactionPool transactionPool,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
- mTaskOrganizer = shellTaskOrganizer;
- mSyncQueue = syncQueue;
- mContext = context;
- mRootTDAOrganizer = rootTDAOrganizer;
- mMainExecutor = mainExecutor;
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mTransitions = transitions;
- mTransactionPool = transactionPool;
- mUnfoldControllerProvider = unfoldControllerProvider;
- mLogger = new SplitscreenEventLogger();
- }
-
- public SplitScreen asSplitScreen() {
- return mImpl;
- }
-
- @Override
- public Context getContext() {
- return mContext;
- }
-
- @Override
- public ShellExecutor getRemoteCallExecutor() {
- return mMainExecutor;
- }
-
- public void onOrganizerRegistered() {
- if (mStageCoordinator == null) {
- // TODO: Multi-display
- mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
- mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
- mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
- mUnfoldControllerProvider);
- }
- }
-
- public boolean isSplitScreenVisible() {
- return mStageCoordinator.isSplitScreenVisible();
- }
-
- public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
- final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
- if (task == null) {
- throw new IllegalArgumentException("Unknown taskId" + taskId);
- }
- return moveToSideStage(task, sideStagePosition);
- }
-
- public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
- @SplitPosition int sideStagePosition) {
- return mStageCoordinator.moveToSideStage(task, sideStagePosition);
- }
-
- public boolean removeFromSideStage(int taskId) {
- return mStageCoordinator.removeFromSideStage(taskId);
- }
-
- public void setSideStageOutline(boolean enable) {
- mStageCoordinator.setSideStageOutline(enable);
- }
-
- public void setSideStagePosition(@SplitPosition int sideStagePosition) {
- mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
- }
-
- public void setSideStageVisibility(boolean visible) {
- mStageCoordinator.setSideStageVisibility(visible);
- }
-
- public void enterSplitScreen(int taskId, boolean leftOrTop) {
- moveToSideStage(taskId,
- leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
- }
-
- public void exitSplitScreen(int toTopTaskId, int exitReason) {
- mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
- }
-
- public void onKeyguardOccludedChanged(boolean occluded) {
- mStageCoordinator.onKeyguardOccludedChanged(occluded);
- }
-
- public void onKeyguardVisibilityChanged(boolean showing) {
- mStageCoordinator.onKeyguardVisibilityChanged(showing);
- }
-
- public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
- }
-
- public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
- mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
- }
-
- public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
- mStageCoordinator.registerSplitScreenListener(listener);
- }
-
- public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
- mStageCoordinator.unregisterSplitScreenListener(listener);
- }
-
- public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
- null /* wct */);
-
- try {
- ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to launch task", e);
- }
- }
-
- public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
- @Nullable Bundle options, UserHandle user) {
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
- null /* wct */);
-
- try {
- LauncherApps launcherApps =
- mContext.getSystemService(LauncherApps.class);
- launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
- options, user);
- } catch (ActivityNotFoundException e) {
- Slog.e(TAG, "Failed to launch shortcut", e);
- }
- }
-
- public void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
- @Nullable Bundle options) {
- if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
- startIntentLegacy(intent, fillInIntent, position, options);
- return;
- }
- mStageCoordinator.startIntent(intent, fillInIntent, STAGE_TYPE_UNDEFINED, position, options,
- null /* remote */);
- }
-
- private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options) {
- LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback,
- SurfaceControl.Transaction t) {
- mStageCoordinator.updateSurfaceBounds(null /* layout */, t,
- false /* applyResizingOffset */);
-
- if (apps != null) {
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
- }
- }
- }
-
- t.apply();
- if (finishedCallback != null) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error finishing legacy transition: ", e);
- }
- }
- }
- };
- WindowContainerTransaction wct = new WindowContainerTransaction();
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
- wct.sendPendingIntent(intent, fillInIntent, options);
- mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
- }
-
- RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
- if (!isSplitScreenVisible()) return null;
- final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
- .setContainerLayer()
- .setName("RecentsAnimationSplitTasks")
- .setHidden(false)
- .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
- mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
- SurfaceControl sc = builder.build();
- SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
-
- // Ensure that we order these in the parent in the right z-order as their previous order
- Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
- int layer = 1;
- for (RemoteAnimationTarget appTarget : apps) {
- transaction.reparent(appTarget.leash, sc);
- transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
- appTarget.screenSpaceBounds.top);
- transaction.setLayer(appTarget.leash, layer++);
- }
- transaction.apply();
- transaction.close();
- return new RemoteAnimationTarget[]{
- mStageCoordinator.getDividerBarLegacyTarget(),
- mStageCoordinator.getOutlineLegacyTarget()};
- }
-
- /**
- * Sets drag info to be logged when splitscreen is entered.
- */
- public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
- mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
- }
-
- public void dump(@NonNull PrintWriter pw, String prefix) {
- pw.println(prefix + TAG);
- if (mStageCoordinator != null) {
- mStageCoordinator.dump(pw, prefix);
- }
- }
-
- /**
- * The interface for calls from outside the Shell, within the host process.
- */
- @ExternalThread
- private class SplitScreenImpl implements SplitScreen {
- private ISplitScreenImpl mISplitScreen;
- private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
- private final SplitScreenListener mListener = new SplitScreenListener() {
- @Override
- public void onStagePositionChanged(int stage, int position) {
- for (int i = 0; i < mExecutors.size(); i++) {
- final int index = i;
- mExecutors.valueAt(index).execute(() -> {
- mExecutors.keyAt(index).onStagePositionChanged(stage, position);
- });
- }
- }
-
- @Override
- public void onTaskStageChanged(int taskId, int stage, boolean visible) {
- for (int i = 0; i < mExecutors.size(); i++) {
- final int index = i;
- mExecutors.valueAt(index).execute(() -> {
- mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
- });
- }
- }
-
- @Override
- public void onSplitVisibilityChanged(boolean visible) {
- for (int i = 0; i < mExecutors.size(); i++) {
- final int index = i;
- mExecutors.valueAt(index).execute(() -> {
- mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
- });
- }
- }
- };
-
- @Override
- public ISplitScreen createExternalInterface() {
- if (mISplitScreen != null) {
- mISplitScreen.invalidate();
- }
- mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
- return mISplitScreen;
- }
-
- @Override
- public void onKeyguardOccludedChanged(boolean occluded) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onKeyguardOccludedChanged(occluded);
- });
- }
-
- @Override
- public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
- if (mExecutors.containsKey(listener)) return;
-
- mMainExecutor.execute(() -> {
- if (mExecutors.size() == 0) {
- SplitScreenController.this.registerSplitScreenListener(mListener);
- }
-
- mExecutors.put(listener, executor);
- });
-
- executor.execute(() -> {
- mStageCoordinator.sendStatusToListener(listener);
- });
- }
-
- @Override
- public void unregisterSplitScreenListener(SplitScreenListener listener) {
- mMainExecutor.execute(() -> {
- mExecutors.remove(listener);
-
- if (mExecutors.size() == 0) {
- SplitScreenController.this.unregisterSplitScreenListener(mListener);
- }
- });
- }
-
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onKeyguardVisibilityChanged(showing);
- });
- }
- }
-
- /**
- * The interface for calls from outside the host process.
- */
- @BinderThread
- private static class ISplitScreenImpl extends ISplitScreen.Stub {
- private SplitScreenController mController;
- private ISplitScreenListener mListener;
- private final SplitScreen.SplitScreenListener mSplitScreenListener =
- new SplitScreen.SplitScreenListener() {
- @Override
- public void onStagePositionChanged(int stage, int position) {
- try {
- if (mListener != null) {
- mListener.onStagePositionChanged(stage, position);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "onStagePositionChanged", e);
- }
- }
-
- @Override
- public void onTaskStageChanged(int taskId, int stage, boolean visible) {
- try {
- if (mListener != null) {
- mListener.onTaskStageChanged(taskId, stage, visible);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "onTaskStageChanged", e);
- }
- }
- };
- private final IBinder.DeathRecipient mListenerDeathRecipient =
- new IBinder.DeathRecipient() {
- @Override
- @BinderThread
- public void binderDied() {
- final SplitScreenController controller = mController;
- controller.getRemoteCallExecutor().execute(() -> {
- mListener = null;
- controller.unregisterSplitScreenListener(mSplitScreenListener);
- });
- }
- };
-
- public ISplitScreenImpl(SplitScreenController controller) {
- mController = controller;
- }
-
- /**
- * Invalidates this instance, preventing future calls from updating the controller.
- */
- void invalidate() {
- mController = null;
- }
-
- @Override
- public void registerSplitScreenListener(ISplitScreenListener listener) {
- executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
- (controller) -> {
- if (mListener != null) {
- mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
- 0 /* flags */);
- }
- if (listener != null) {
- try {
- listener.asBinder().linkToDeath(mListenerDeathRecipient,
- 0 /* flags */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to link to death");
- return;
- }
- }
- mListener = listener;
- controller.registerSplitScreenListener(mSplitScreenListener);
- });
- }
-
- @Override
- public void unregisterSplitScreenListener(ISplitScreenListener listener) {
- executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
- (controller) -> {
- if (mListener != null) {
- mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
- 0 /* flags */);
- }
- mListener = null;
- controller.unregisterSplitScreenListener(mSplitScreenListener);
- });
- }
-
- @Override
- public void exitSplitScreen(int toTopTaskId) {
- executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
- (controller) -> {
- controller.exitSplitScreen(toTopTaskId,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT);
- });
- }
-
- @Override
- public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
- (controller) -> {
- controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
- });
- }
-
- @Override
- public void setSideStageVisibility(boolean visible) {
- executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
- (controller) -> {
- controller.setSideStageVisibility(visible);
- });
- }
-
- @Override
- public void removeFromSideStage(int taskId) {
- executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
- (controller) -> {
- controller.removeFromSideStage(taskId);
- });
- }
-
- @Override
- public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
- executeRemoteCallWithTaskPermission(mController, "startTask",
- (controller) -> {
- controller.startTask(taskId, position, options);
- });
- }
-
- @Override
- public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- RemoteAnimationAdapter adapter) {
- executeRemoteCallWithTaskPermission(mController, "startTasks",
- (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
- mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
- adapter));
- }
-
- @Override
- public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition,
- @Nullable RemoteTransition remoteTransition) {
- executeRemoteCallWithTaskPermission(mController, "startTasks",
- (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
- sideTaskId, sideOptions, sidePosition, remoteTransition));
- }
-
- @Override
- public void startShortcut(String packageName, String shortcutId, int stage, int position,
- @Nullable Bundle options, UserHandle user) {
- executeRemoteCallWithTaskPermission(mController, "startShortcut",
- (controller) -> {
- controller.startShortcut(packageName, shortcutId, position,
- options, user);
- });
- }
-
- @Override
- public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
- @Nullable Bundle options) {
- executeRemoteCallWithTaskPermission(mController, "startIntent",
- (controller) -> {
- controller.startIntent(intent, fillInIntent, position, options);
- });
- }
-
- @Override
- public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
- RemoteAnimationTarget[] apps) {
- final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
- executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
- (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps),
- true /* blocking */);
- return out[0];
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
deleted file mode 100644
index 018365420177..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * 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.stagesplit;
-
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
-
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
-import static com.android.wm.shell.transition.Transitions.isOpeningType;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.transition.OneShotRemoteHandler;
-import com.android.wm.shell.transition.Transitions;
-
-import java.util.ArrayList;
-
-/** Manages transition animations for split-screen. */
-class SplitScreenTransitions {
- private static final String TAG = "SplitScreenTransitions";
-
- /** 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;
-
- private final TransactionPool mTransactionPool;
- private final Transitions mTransitions;
- private final Runnable mOnFinish;
-
- IBinder mPendingDismiss = null;
- IBinder mPendingEnter = null;
-
- private IBinder mAnimatingTransition = null;
- private OneShotRemoteHandler mRemoteHandler = null;
-
- private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> {
- if (wct != null || wctCB != null) {
- throw new UnsupportedOperationException("finish transactions not supported yet.");
- }
- onFinish();
- };
-
- /** Keeps track of currently running animations */
- private final ArrayList<Animator> mAnimations = new ArrayList<>();
-
- private Transitions.TransitionFinishCallback mFinishCallback = null;
- private SurfaceControl.Transaction mFinishTransaction;
-
- SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
- @NonNull Runnable onFinishCallback) {
- mTransactionPool = pool;
- mTransitions = transitions;
- mOnFinish = onFinishCallback;
- }
-
- void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback,
- @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
- mFinishCallback = finishCallback;
- mAnimatingTransition = transition;
- if (mRemoteHandler != null) {
- mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
- mRemoteFinishCB);
- mRemoteHandler = null;
- return;
- }
- playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
- }
-
- private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
- @NonNull WindowContainerToken sideRoot) {
- mFinishTransaction = mTransactionPool.acquire();
-
- // Play some place-holder fade animations
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final SurfaceControl leash = change.getLeash();
- final int mode = info.getChanges().get(i).getMode();
-
- if (mode == TRANSIT_CHANGE) {
- if (change.getParent() != null) {
- // This is probably reparented, so we want the parent to be immediately visible
- final TransitionInfo.Change parentChange = info.getChange(change.getParent());
- t.show(parentChange.getLeash());
- t.setAlpha(parentChange.getLeash(), 1.f);
- // and then animate this layer outside the parent (since, for example, this is
- // the home task animating from fullscreen to part-screen).
- t.reparent(leash, info.getRootLeash());
- t.setLayer(leash, info.getChanges().size() - i);
- // build the finish reparent/reposition
- mFinishTransaction.reparent(leash, parentChange.getLeash());
- mFinishTransaction.setPosition(leash,
- change.getEndRelOffset().x, change.getEndRelOffset().y);
- }
- // TODO(shell-transitions): screenshot here
- final Rect startBounds = new Rect(change.getStartAbsBounds());
- final Rect endBounds = new Rect(change.getEndAbsBounds());
- startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- startExampleResizeAnimation(leash, startBounds, endBounds);
- }
- if (change.getParent() != null) {
- continue;
- }
-
- if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
- || sideRoot.equals(change.getContainer()))) {
- t.setWindowCrop(leash, change.getStartAbsBounds().width(),
- change.getStartAbsBounds().height());
- }
- boolean isOpening = isOpeningType(info.getType());
- if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
- // fade in
- startExampleAnimation(leash, true /* show */);
- } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
- // fade out
- if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
- // Dismissing via snap-to-top/bottom means that the dismissed task is already
- // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
- // and don't animate it so it doesn't pop-in when reparented.
- t.setAlpha(leash, 0.f);
- } else {
- startExampleAnimation(leash, false /* show */);
- }
- }
- }
- t.apply();
- onFinish();
- }
-
- /** Starts a transition to enter split with a remote transition animator. */
- IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
- @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
- @NonNull Transitions.TransitionHandler handler) {
- if (remoteTransition != null) {
- // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
- mRemoteHandler = new OneShotRemoteHandler(
- mTransitions.getMainExecutor(), remoteTransition);
- }
- final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- mPendingEnter = transition;
- if (mRemoteHandler != null) {
- mRemoteHandler.setTransition(transition);
- }
- return transition;
- }
-
- /** Starts a transition for dismissing split after dragging the divider to a screen edge */
- IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct,
- @NonNull Transitions.TransitionHandler handler) {
- final IBinder transition = mTransitions.startTransition(
- TRANSIT_SPLIT_DISMISS_SNAP, wct, handler);
- mPendingDismiss = transition;
- return transition;
- }
-
- void onFinish() {
- if (!mAnimations.isEmpty()) return;
- mOnFinish.run();
- if (mFinishTransaction != null) {
- mFinishTransaction.apply();
- mTransactionPool.release(mFinishTransaction);
- mFinishTransaction = null;
- }
- mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
- mFinishCallback = null;
- if (mAnimatingTransition == mPendingEnter) {
- mPendingEnter = null;
- }
- if (mAnimatingTransition == mPendingDismiss) {
- mPendingDismiss = null;
- }
- mAnimatingTransition = null;
- }
-
- // TODO(shell-transitions): real animations
- private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
- final float end = show ? 1.f : 0.f;
- final float start = 1.f - end;
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- final ValueAnimator va = ValueAnimator.ofFloat(start, end);
- va.setDuration(500);
- va.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
- transaction.apply();
- });
- final Runnable finisher = () -> {
- transaction.setAlpha(leash, end);
- transaction.apply();
- mTransactionPool.release(transaction);
- mTransitions.getMainExecutor().execute(() -> {
- mAnimations.remove(va);
- onFinish();
- });
- };
- va.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) { }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) { }
- });
- mAnimations.add(va);
- mTransitions.getAnimExecutor().execute(va::start);
- }
-
- // TODO(shell-transitions): real animations
- private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
- @NonNull Rect startBounds, @NonNull Rect endBounds) {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
- va.setDuration(500);
- va.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- transaction.setWindowCrop(leash,
- (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
- (int) (startBounds.height() * (1.f - fraction)
- + endBounds.height() * fraction));
- transaction.setPosition(leash,
- startBounds.left * (1.f - fraction) + endBounds.left * fraction,
- startBounds.top * (1.f - fraction) + endBounds.top * fraction);
- transaction.apply();
- });
- final Runnable finisher = () -> {
- transaction.setWindowCrop(leash, 0, 0);
- transaction.setPosition(leash, endBounds.left, endBounds.top);
- transaction.apply();
- mTransactionPool.release(transaction);
- mTransitions.getMainExecutor().execute(() -> {
- mAnimations.remove(va);
- onFinish();
- });
- };
- va.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- finisher.run();
- }
- });
- mAnimations.add(va);
- mTransitions.getAnimExecutor().execute(va::start);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
deleted file mode 100644
index e1850396a5c0..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * 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.stagesplit;
-
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.InstanceIdSequence;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-
-/**
- * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent
- */
-public class SplitscreenEventLogger {
-
- // Used to generate instance ids for this drag if one is not provided
- private final InstanceIdSequence mIdSequence;
-
- // The instance id for the current splitscreen session (from start to end)
- private InstanceId mLoggerSessionId;
-
- // Drag info
- private @SplitPosition int mDragEnterPosition;
- private InstanceId mDragEnterSessionId;
-
- // For deduping async events
- private int mLastMainStagePosition = -1;
- private int mLastMainStageUid = -1;
- private int mLastSideStagePosition = -1;
- private int mLastSideStageUid = -1;
- private float mLastSplitRatio = -1f;
-
- public SplitscreenEventLogger() {
- mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
- }
-
- /**
- * Return whether a splitscreen session has started.
- */
- public boolean hasStartedSession() {
- return mLoggerSessionId != null;
- }
-
- /**
- * May be called before logEnter() to indicate that the session was started from a drag.
- */
- public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
- mDragEnterPosition = position;
- mDragEnterSessionId = dragSessionId;
- }
-
- /**
- * Logs when the user enters splitscreen.
- */
- public void logEnter(float splitRatio,
- @SplitPosition int mainStagePosition, int mainStageUid,
- @SplitPosition int sideStagePosition, int sideStageUid,
- boolean isLandscape) {
- mLoggerSessionId = mIdSequence.newInstanceId();
- int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
- ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
- : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
- updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
- mainStageUid);
- updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
- sideStageUid);
- updateSplitRatioState(splitRatio);
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER,
- enterReason,
- 0 /* exitReason */,
- splitRatio,
- mLastMainStagePosition,
- mLastMainStageUid,
- mLastSideStagePosition,
- mLastSideStageUid,
- mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
- mLoggerSessionId.getId());
- }
-
- /**
- * Logs when the user exits splitscreen. Only one of the main or side stages should be
- * specified to indicate which position was focused as a part of exiting (both can be unset).
- */
- public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid,
- @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
- if (mLoggerSessionId == null) {
- // Ignore changes until we've started logging the session
- return;
- }
- if ((mainStagePosition != SPLIT_POSITION_UNDEFINED
- && sideStagePosition != SPLIT_POSITION_UNDEFINED)
- || (mainStageUid != 0 && sideStageUid != 0)) {
- throw new IllegalArgumentException("Only main or side stage should be set");
- }
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT,
- 0 /* enterReason */,
- exitReason,
- 0f /* splitRatio */,
- getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
- mainStageUid,
- getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
- sideStageUid,
- 0 /* dragInstanceId */,
- mLoggerSessionId.getId());
-
- // Reset states
- mLoggerSessionId = null;
- mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
- mDragEnterSessionId = null;
- mLastMainStagePosition = -1;
- mLastMainStageUid = -1;
- mLastSideStagePosition = -1;
- mLastSideStageUid = -1;
- }
-
- /**
- * Logs when an app in the main stage changes.
- */
- public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid,
- boolean isLandscape) {
- if (mLoggerSessionId == null) {
- // Ignore changes until we've started logging the session
- return;
- }
- if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition,
- isLandscape), mainStageUid)) {
- // Ignore if there are no user perceived changes
- return;
- }
-
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
- 0 /* enterReason */,
- 0 /* exitReason */,
- 0f /* splitRatio */,
- mLastMainStagePosition,
- mLastMainStageUid,
- 0 /* sideStagePosition */,
- 0 /* sideStageUid */,
- 0 /* dragInstanceId */,
- mLoggerSessionId.getId());
- }
-
- /**
- * Logs when an app in the side stage changes.
- */
- public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid,
- boolean isLandscape) {
- if (mLoggerSessionId == null) {
- // Ignore changes until we've started logging the session
- return;
- }
- if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition,
- isLandscape), sideStageUid)) {
- // Ignore if there are no user perceived changes
- return;
- }
-
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
- 0 /* enterReason */,
- 0 /* exitReason */,
- 0f /* splitRatio */,
- 0 /* mainStagePosition */,
- 0 /* mainStageUid */,
- mLastSideStagePosition,
- mLastSideStageUid,
- 0 /* dragInstanceId */,
- mLoggerSessionId.getId());
- }
-
- /**
- * Logs when the splitscreen ratio changes.
- */
- public void logResize(float splitRatio) {
- if (mLoggerSessionId == null) {
- // Ignore changes until we've started logging the session
- return;
- }
- if (splitRatio <= 0f || splitRatio >= 1f) {
- // Don't bother reporting resizes that end up dismissing the split, that will be logged
- // via the exit event
- return;
- }
- if (!updateSplitRatioState(splitRatio)) {
- // Ignore if there are no user perceived changes
- return;
- }
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE,
- 0 /* enterReason */,
- 0 /* exitReason */,
- mLastSplitRatio,
- 0 /* mainStagePosition */, 0 /* mainStageUid */,
- 0 /* sideStagePosition */, 0 /* sideStageUid */,
- 0 /* dragInstanceId */,
- mLoggerSessionId.getId());
- }
-
- /**
- * Logs when the apps in splitscreen are swapped.
- */
- public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid,
- @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
- if (mLoggerSessionId == null) {
- // Ignore changes until we've started logging the session
- return;
- }
-
- updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
- mainStageUid);
- updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
- sideStageUid);
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP,
- 0 /* enterReason */,
- 0 /* exitReason */,
- 0f /* splitRatio */,
- mLastMainStagePosition,
- mLastMainStageUid,
- mLastSideStagePosition,
- mLastSideStageUid,
- 0 /* dragInstanceId */,
- mLoggerSessionId.getId());
- }
-
- private boolean updateMainStageState(int mainStagePosition, int mainStageUid) {
- boolean changed = (mLastMainStagePosition != mainStagePosition)
- || (mLastMainStageUid != mainStageUid);
- if (!changed) {
- return false;
- }
-
- mLastMainStagePosition = mainStagePosition;
- mLastMainStageUid = mainStageUid;
- return true;
- }
-
- private boolean updateSideStageState(int sideStagePosition, int sideStageUid) {
- boolean changed = (mLastSideStagePosition != sideStagePosition)
- || (mLastSideStageUid != sideStageUid);
- if (!changed) {
- return false;
- }
-
- mLastSideStagePosition = sideStagePosition;
- mLastSideStageUid = sideStageUid;
- return true;
- }
-
- private boolean updateSplitRatioState(float splitRatio) {
- boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0;
- if (!changed) {
- return false;
- }
-
- mLastSplitRatio = splitRatio;
- return true;
- }
-
- public int getDragEnterReasonFromSplitPosition(@SplitPosition int position,
- boolean isLandscape) {
- if (isLandscape) {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT;
- } else {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM;
- }
- }
-
- private int getMainStagePositionFromSplitPosition(@SplitPosition int position,
- boolean isLandscape) {
- if (position == SPLIT_POSITION_UNDEFINED) {
- return 0;
- }
- if (isLandscape) {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT;
- } else {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM;
- }
- }
-
- private int getSideStagePositionFromSplitPosition(@SplitPosition int position,
- boolean isLandscape) {
- if (position == SPLIT_POSITION_UNDEFINED) {
- return 0;
- }
- if (isLandscape) {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT;
- } else {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM;
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
deleted file mode 100644
index ac25c7510931..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
+++ /dev/null
@@ -1,1333 +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.stagesplit;
-
-import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
-
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
-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.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_MAIN;
-import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_SIDE;
-import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_UNDEFINED;
-import static com.android.wm.shell.stagesplit.SplitScreen.stageTypeToString;
-import static com.android.wm.shell.stagesplit.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
-import static com.android.wm.shell.transition.Transitions.isClosingType;
-import static com.android.wm.shell.transition.Transitions.isOpeningType;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.app.WindowConfiguration;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.hardware.devicestate.DeviceStateManager;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Slog;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.view.WindowManager;
-import android.window.DisplayAreaInfo;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.InstanceId;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
-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.common.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.common.split.SplitWindowManager;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.transition.Transitions;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-import javax.inject.Provider;
-
-/**
- * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
- * {@link SideStage} stages.
- * Some high-level rules:
- * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at
- * least one child task.
- * - The {@link MainStage} should only have children if the coordinator is active.
- * - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
- * and {@link SideStage} are visible.
- * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible.
- * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
- * {@link #onStageHasChildrenChanged(StageListenerImpl).}
- */
-class StageCoordinator implements SplitLayout.SplitLayoutHandler,
- RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {
-
- private static final String TAG = StageCoordinator.class.getSimpleName();
-
- /** internal value for mDismissTop that represents no dismiss */
- private static final int NO_DISMISS = -2;
-
- private final SurfaceSession mSurfaceSession = new SurfaceSession();
-
- private final MainStage mMainStage;
- private final StageListenerImpl mMainStageListener = new StageListenerImpl();
- private final StageTaskUnfoldController mMainUnfoldController;
- private final SideStage mSideStage;
- private final StageListenerImpl mSideStageListener = new StageListenerImpl();
- private final StageTaskUnfoldController mSideUnfoldController;
- @SplitPosition
- private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
-
- private final int mDisplayId;
- private SplitLayout mSplitLayout;
- private boolean mDividerVisible;
- private final SyncTransactionQueue mSyncQueue;
- private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
- private final ShellTaskOrganizer mTaskOrganizer;
- private DisplayAreaInfo mDisplayAreaInfo;
- private final Context mContext;
- private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
- private final DisplayImeController mDisplayImeController;
- private final DisplayInsetsController mDisplayInsetsController;
- private final SplitScreenTransitions mSplitTransitions;
- private final SplitscreenEventLogger mLogger;
- private boolean mExitSplitScreenOnHide;
- private boolean mKeyguardOccluded;
-
- // TODO(b/187041611): remove this flag after totally deprecated legacy split
- /** Whether the device is supporting legacy split or not. */
- private boolean mUseLegacySplit;
-
- @SplitScreen.StageType private int mDismissTop = NO_DISMISS;
-
- /** The target stage to dismiss to when unlock after folded. */
- @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-
- private final Runnable mOnTransitionAnimationComplete = () -> {
- // If still playing, let it finish.
- if (!isSplitScreenVisible()) {
- // Update divider state after animation so that it is still around and positioned
- // properly for the animation itself.
- setDividerVisibility(false);
- mSplitLayout.resetDividerPosition();
- }
- mDismissTop = NO_DISMISS;
- };
-
- private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
- new SplitWindowManager.ParentContainerCallbacks() {
- @Override
- public void attachToParentSurface(SurfaceControl.Builder b) {
- mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
- }
-
- @Override
- public void onLeashReady(SurfaceControl leash) {
- mSyncQueue.runInSync(t -> applyDividerVisibility(t));
- }
- };
-
- StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
- DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController, Transitions transitions,
- TransactionPool transactionPool, SplitscreenEventLogger logger,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
- mContext = context;
- mDisplayId = displayId;
- mSyncQueue = syncQueue;
- mRootTDAOrganizer = rootTDAOrganizer;
- mTaskOrganizer = taskOrganizer;
- mLogger = logger;
- mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
- mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
-
- mMainStage = new MainStage(
- mTaskOrganizer,
- mDisplayId,
- mMainStageListener,
- mSyncQueue,
- mSurfaceSession,
- mMainUnfoldController);
- mSideStage = new SideStage(
- mContext,
- mTaskOrganizer,
- mDisplayId,
- mSideStageListener,
- mSyncQueue,
- mSurfaceSession,
- mSideUnfoldController);
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
- mRootTDAOrganizer.registerListener(displayId, this);
- final DeviceStateManager deviceStateManager =
- mContext.getSystemService(DeviceStateManager.class);
- deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
- new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
- mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
- mOnTransitionAnimationComplete);
- transitions.addHandler(this);
- }
-
- @VisibleForTesting
- StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
- MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
- Transitions transitions, TransactionPool transactionPool,
- SplitscreenEventLogger logger,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
- mContext = context;
- mDisplayId = displayId;
- mSyncQueue = syncQueue;
- mRootTDAOrganizer = rootTDAOrganizer;
- mTaskOrganizer = taskOrganizer;
- mMainStage = mainStage;
- mSideStage = sideStage;
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mRootTDAOrganizer.registerListener(displayId, this);
- mSplitLayout = splitLayout;
- mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
- mOnTransitionAnimationComplete);
- mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
- mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
- mLogger = logger;
- transitions.addHandler(this);
- }
-
- @VisibleForTesting
- SplitScreenTransitions getSplitTransitions() {
- return mSplitTransitions;
- }
-
- boolean isSplitScreenVisible() {
- return mSideStageListener.mVisible && mMainStageListener.mVisible;
- }
-
- boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
- @SplitPosition int sideStagePosition) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- setSideStagePosition(sideStagePosition, wct);
- mMainStage.activate(getMainStageBounds(), wct);
- mSideStage.addTask(task, getSideStageBounds(), wct);
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(
- t -> updateSurfaceBounds(null /* layout */, t, false /* applyResizingOffset */));
- return true;
- }
-
- boolean removeFromSideStage(int taskId) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
-
- /**
- * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the
- * {@link SideStage} no longer has children.
- */
- final boolean result = mSideStage.removeTask(taskId,
- mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null,
- wct);
- mTaskOrganizer.applyTransaction(wct);
- return result;
- }
-
- void setSideStageOutline(boolean enable) {
- mSideStage.enableOutline(enable);
- }
-
- /** Starts 2 tasks in one transition. */
- void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
- @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- @Nullable RemoteTransition remoteTransition) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mainOptions = mainOptions != null ? mainOptions : new Bundle();
- sideOptions = sideOptions != null ? sideOptions : new Bundle();
- setSideStagePosition(sidePosition, wct);
-
- // Build a request WCT that will launch both apps such that task 0 is on the main stage
- // while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct);
- mSideStage.setBounds(getSideStageBounds(), wct);
-
- // Make sure the launch options will put tasks in the corresponding split roots
- addActivityOptions(mainOptions, mMainStage);
- addActivityOptions(sideOptions, mSideStage);
-
- // Add task launch requests
- wct.startTask(mainTaskId, mainOptions);
- wct.startTask(sideTaskId, sideOptions);
-
- mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
- }
-
- /** Starts 2 tasks in one legacy transition. */
- void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- RemoteAnimationAdapter adapter) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Need to add another wrapper here in shell so that we can inject the divider bar
- // and also manage the process elevation via setRunningRemote
- IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- RemoteAnimationTarget[] augmentedNonApps =
- new RemoteAnimationTarget[nonApps.length + 1];
- for (int i = 0; i < nonApps.length; ++i) {
- augmentedNonApps[i] = nonApps[i];
- }
- augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
- try {
- ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
- adapter.getCallingApplication());
- adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps,
- finishedCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- }
-
- @Override
- public void onAnimationCancelled() {
- try {
- adapter.getRunner().onAnimationCancelled();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- }
- };
- RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
- wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
-
- if (mainOptions == null) {
- mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
- } else {
- ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
- mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
- }
-
- sideOptions = sideOptions != null ? sideOptions : new Bundle();
- setSideStagePosition(sidePosition, wct);
-
- // Build a request WCT that will launch both apps such that task 0 is on the main stage
- // while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct);
- mSideStage.setBounds(getSideStageBounds(), wct);
-
- // Make sure the launch options will put tasks in the corresponding split roots
- addActivityOptions(mainOptions, mMainStage);
- addActivityOptions(sideOptions, mSideStage);
-
- // Add task launch requests
- wct.startTask(mainTaskId, mainOptions);
- wct.startTask(sideTaskId, sideOptions);
-
- // Using legacy transitions, so we can't use blast sync since it conflicts.
- mTaskOrganizer.applyTransaction(wct);
- }
-
- public void startIntent(PendingIntent intent, Intent fillInIntent,
- @SplitScreen.StageType int stage, @SplitPosition int position,
- @androidx.annotation.Nullable Bundle options,
- @Nullable RemoteTransition remoteTransition) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- options = resolveStartStage(stage, position, options, wct);
- wct.sendPendingIntent(intent, fillInIntent, options);
- mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
- }
-
- Bundle resolveStartStage(@SplitScreen.StageType int stage,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
- @androidx.annotation.Nullable WindowContainerTransaction wct) {
- switch (stage) {
- case STAGE_TYPE_UNDEFINED: {
- // Use the stage of the specified position is valid.
- if (position != SPLIT_POSITION_UNDEFINED) {
- if (position == getSideStagePosition()) {
- options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
- } else {
- options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct);
- }
- } else {
- // Exit split-screen and launch fullscreen since stage wasn't specified.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
- }
- break;
- }
- case STAGE_TYPE_SIDE: {
- if (position != SPLIT_POSITION_UNDEFINED) {
- setSideStagePosition(position, wct);
- } else {
- position = getSideStagePosition();
- }
- if (options == null) {
- options = new Bundle();
- }
- updateActivityOptions(options, position);
- break;
- }
- case STAGE_TYPE_MAIN: {
- if (position != SPLIT_POSITION_UNDEFINED) {
- // Set the side stage opposite of what we want to the main stage.
- final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
- setSideStagePosition(sideStagePosition, wct);
- } else {
- position = getMainStagePosition();
- }
- if (options == null) {
- options = new Bundle();
- }
- updateActivityOptions(options, position);
- break;
- }
- default:
- throw new IllegalArgumentException("Unknown stage=" + stage);
- }
-
- return options;
- }
-
- @SplitPosition
- int getSideStagePosition() {
- return mSideStagePosition;
- }
-
- @SplitPosition
- int getMainStagePosition() {
- return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
- }
-
- void setSideStagePosition(@SplitPosition int sideStagePosition,
- @Nullable WindowContainerTransaction wct) {
- setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
- }
-
- private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
- @Nullable WindowContainerTransaction wct) {
- if (mSideStagePosition == sideStagePosition) return;
- mSideStagePosition = sideStagePosition;
- sendOnStagePositionChanged();
-
- if (mSideStageListener.mVisible && updateBounds) {
- if (wct == null) {
- // onLayoutSizeChanged builds/applies a wct with the contents of updateWindowBounds.
- onLayoutSizeChanged(mSplitLayout);
- } else {
- updateWindowBounds(mSplitLayout, wct);
- updateUnfoldBounds();
- }
- }
- }
-
- void setSideStageVisibility(boolean visible) {
- if (mSideStageListener.mVisible == visible) return;
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mSideStage.setVisibility(visible, wct);
- mTaskOrganizer.applyTransaction(wct);
- }
-
- void onKeyguardOccludedChanged(boolean occluded) {
- // Do not exit split directly, because it needs to wait for task info update to determine
- // which task should remain on top after split dismissed.
- mKeyguardOccluded = occluded;
- }
-
- void onKeyguardVisibilityChanged(boolean showing) {
- if (!showing && mMainStage.isActive()
- && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
- exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
- SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED);
- }
- }
-
- void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- mExitSplitScreenOnHide = exitSplitScreenOnHide;
- }
-
- void exitSplitScreen(int toTopTaskId, int exitReason) {
- StageTaskListener childrenToTop = null;
- if (mMainStage.containsTask(toTopTaskId)) {
- childrenToTop = mMainStage;
- } else if (mSideStage.containsTask(toTopTaskId)) {
- childrenToTop = mSideStage;
- }
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (childrenToTop != null) {
- childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct);
- }
- applyExitSplitScreen(childrenToTop, wct, exitReason);
- }
-
- private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- applyExitSplitScreen(childrenToTop, wct, exitReason);
- }
-
- private void applyExitSplitScreen(
- StageTaskListener childrenToTop,
- WindowContainerTransaction wct, int exitReason) {
- mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
- mMainStage.deactivate(wct, childrenToTop == mMainStage);
- mTaskOrganizer.applyTransaction(wct);
- mSyncQueue.runInSync(t -> t
- .setWindowCrop(mMainStage.mRootLeash, null)
- .setWindowCrop(mSideStage.mRootLeash, null));
- // Hide divider and reset its position.
- setDividerVisibility(false);
- mSplitLayout.resetDividerPosition();
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
- if (childrenToTop != null) {
- logExitToStage(exitReason, childrenToTop == mMainStage);
- } else {
- logExit(exitReason);
- }
- }
-
- /**
- * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
- * an existing WindowContainerTransaction (rather than applying immediately). This is intended
- * to be used when exiting split might be bundled with other window operations.
- */
- void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
- @NonNull WindowContainerTransaction wct) {
- mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
- mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
- }
-
- void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
- outTopOrLeftBounds.set(mSplitLayout.getBounds1());
- outBottomOrRightBounds.set(mSplitLayout.getBounds2());
- }
-
- private void addActivityOptions(Bundle opts, StageTaskListener stage) {
- opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
- }
-
- void updateActivityOptions(Bundle opts, @SplitPosition int position) {
- addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage);
- }
-
- void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
- if (mListeners.contains(listener)) return;
- mListeners.add(listener);
- sendStatusToListener(listener);
- }
-
- void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
- mListeners.remove(listener);
- }
-
- void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
- listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
- listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
- listener.onSplitVisibilityChanged(isSplitScreenVisible());
- mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
- mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
- }
-
- private void sendOnStagePositionChanged() {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- final SplitScreen.SplitScreenListener l = mListeners.get(i);
- l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
- l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
- }
- }
-
- private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
- boolean present, boolean visible) {
- int stage;
- if (present) {
- stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
- } else {
- // No longer on any stage
- stage = STAGE_TYPE_UNDEFINED;
- }
- if (stage == STAGE_TYPE_MAIN) {
- mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
- } else {
- mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
- }
-
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
- }
- }
-
- private void sendSplitVisibilityChanged() {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- final SplitScreen.SplitScreenListener l = mListeners.get(i);
- l.onSplitVisibilityChanged(mDividerVisible);
- }
-
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
- mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
- }
- }
-
- private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
- if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
- mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Make the stages adjacent to each other so they occlude what's behind them.
- wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
- true /* moveTogether */);
-
- // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy
- // split to prevent new split behavior confusing users.
- if (!mUseLegacySplit) {
- wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- }
-
- mTaskOrganizer.applyTransaction(wct);
- }
- }
-
- private void onStageRootTaskVanished(StageListenerImpl stageListener) {
- if (stageListener == mMainStageListener || stageListener == mSideStageListener) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Deactivate the main stage if it no longer has a root task.
- mMainStage.deactivate(wct);
-
- if (!mUseLegacySplit) {
- wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- }
-
- mTaskOrganizer.applyTransaction(wct);
- }
- }
-
- private void setDividerVisibility(boolean visible) {
- if (mDividerVisible == visible) return;
- mDividerVisible = visible;
- if (visible) {
- mSplitLayout.init();
- updateUnfoldBounds();
- } else {
- mSplitLayout.release();
- }
- sendSplitVisibilityChanged();
- }
-
- private void onStageVisibilityChanged(StageListenerImpl stageListener) {
- final boolean sideStageVisible = mSideStageListener.mVisible;
- final boolean mainStageVisible = mMainStageListener.mVisible;
- final boolean bothStageVisible = sideStageVisible && mainStageVisible;
- final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
- final boolean sameVisibility = sideStageVisible == mainStageVisible;
- // Only add or remove divider when both visible or both invisible to avoid sometimes we only
- // got one stage visibility changed for a moment and it will cause flicker.
- if (sameVisibility) {
- setDividerVisibility(bothStageVisible);
- }
-
- if (bothStageInvisible) {
- if (mExitSplitScreenOnHide
- // Don't dismiss staged split when both stages are not visible due to sleeping display,
- // like the cases keyguard showing or screen off.
- || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
- exitSplitScreen(null /* childrenToTop */,
- SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
- }
- } else if (mKeyguardOccluded) {
- // At least one of the stages is visible while keyguard occluded. 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.
- final StageTaskListener toTop =
- mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
- exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP);
- }
-
- mSyncQueue.runInSync(t -> {
- // Same above, we only set root tasks and divider leash visibility when both stage
- // change to visible or invisible to avoid flicker.
- if (sameVisibility) {
- t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
- .setVisibility(mMainStage.mRootLeash, bothStageVisible);
- applyDividerVisibility(t);
- applyOutlineVisibility(t);
- }
- });
- }
-
- private void applyDividerVisibility(SurfaceControl.Transaction t) {
- final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
- if (dividerLeash == null) {
- return;
- }
-
- if (mDividerVisible) {
- t.show(dividerLeash)
- .setLayer(dividerLeash, Integer.MAX_VALUE)
- .setPosition(dividerLeash,
- mSplitLayout.getDividerBounds().left,
- mSplitLayout.getDividerBounds().top);
- } else {
- t.hide(dividerLeash);
- }
- }
-
- private void applyOutlineVisibility(SurfaceControl.Transaction t) {
- final SurfaceControl outlineLeash = mSideStage.getOutlineLeash();
- if (outlineLeash == null) {
- return;
- }
-
- if (mDividerVisible) {
- t.show(outlineLeash).setLayer(outlineLeash, Integer.MAX_VALUE);
- } else {
- t.hide(outlineLeash);
- }
- }
-
- private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
- final boolean hasChildren = stageListener.mHasChildren;
- final boolean isSideStage = stageListener == mSideStageListener;
- if (!hasChildren) {
- if (isSideStage && mMainStageListener.mVisible) {
- // Exit to main stage if side stage no longer has children.
- exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
- } else if (!isSideStage && mSideStageListener.mVisible) {
- // Exit to side stage if main stage no longer has children.
- exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
- }
- } else if (isSideStage) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Make sure the main stage is active.
- mMainStage.activate(getMainStageBounds(), wct);
- mSideStage.setBounds(getSideStageBounds(), wct);
- mTaskOrganizer.applyTransaction(wct);
- }
- if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
- && mSideStageListener.mHasChildren) {
- mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
- getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
- }
- }
-
- @VisibleForTesting
- IBinder onSnappedToDismissTransition(boolean mainStageToTop) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct);
- return mSplitTransitions.startSnapToDismiss(wct, this);
- }
-
- @Override
- public void onSnappedToDismiss(boolean bottomOrRight) {
- final boolean mainStageToTop =
- bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
- : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
- if (ENABLE_SHELL_TRANSITIONS) {
- onSnappedToDismissTransition(mainStageToTop);
- return;
- }
- exitSplitScreen(mainStageToTop ? mMainStage : mSideStage,
- SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER);
- }
-
- @Override
- public void onDoubleTappedDivider() {
- setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
- mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
- }
-
- @Override
- public void onLayoutPositionChanging(SplitLayout layout) {
- mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, true /* applyResizingOffset */));
- }
-
- @Override
- public void onLayoutSizeChanging(SplitLayout layout) {
- mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, true /* applyResizingOffset */));
- mSideStage.setOutlineVisibility(false);
- }
-
- @Override
- public void onLayoutSizeChanged(SplitLayout layout) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- updateWindowBounds(layout, wct);
- updateUnfoldBounds();
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, false /* applyResizingOffset */));
- mSideStage.setOutlineVisibility(true);
- mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
- }
-
- private void updateUnfoldBounds() {
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.onLayoutChanged(getMainStageBounds());
- mSideUnfoldController.onLayoutChanged(getSideStageBounds());
- }
- }
-
- /**
- * Populates `wct` with operations that match the split windows to the current layout.
- * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
- */
- private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
- final StageTaskListener topLeftStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
- final StageTaskListener bottomRightStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
- layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
- }
-
- void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t,
- boolean applyResizingOffset) {
- final StageTaskListener topLeftStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
- final StageTaskListener bottomRightStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
- (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
- bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer,
- applyResizingOffset);
- }
-
- @Override
- public int getSplitItemPosition(WindowContainerToken token) {
- if (token == null) {
- return SPLIT_POSITION_UNDEFINED;
- }
-
- if (token.equals(mMainStage.mRootTaskInfo.getToken())) {
- return getMainStagePosition();
- } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) {
- return getSideStagePosition();
- }
-
- return SPLIT_POSITION_UNDEFINED;
- }
-
- @Override
- public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
- final StageTaskListener topLeftStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
- final StageTaskListener bottomRightStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
- bottomRightStage.mRootTaskInfo);
- mTaskOrganizer.applyTransaction(wct);
- }
-
- @Override
- public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
- mDisplayAreaInfo = displayAreaInfo;
- if (mSplitLayout == null) {
- mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
- mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
- mDisplayImeController, mTaskOrganizer, SplitLayout.PARALLAX_DISMISSING);
- mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
-
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.init();
- mSideUnfoldController.init();
- }
- }
- }
-
- @Override
- public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
- throw new IllegalStateException("Well that was unexpected...");
- }
-
- @Override
- public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
- mDisplayAreaInfo = displayAreaInfo;
- if (mSplitLayout != null
- && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
- && mMainStage.isActive()) {
- onLayoutSizeChanged(mSplitLayout);
- }
- }
-
- private void onFoldedStateChanged(boolean folded) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
- if (!folded) return;
-
- if (mMainStage.isFocused()) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
- } else if (mSideStage.isFocused()) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
- }
- }
-
- private Rect getSideStageBounds() {
- return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
- ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
- }
-
- private Rect getMainStageBounds() {
- return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
- ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
- }
-
- /**
- * Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain
- * this task (yet) so this can also be used to identify which stage to put a task into.
- */
- private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) {
- // TODO(b/184679596): Find a way to either include task-org information in the transition,
- // or synchronize task-org callbacks so we can use stage.containsTask
- if (mMainStage.mRootTaskInfo != null
- && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) {
- return mMainStage;
- } else if (mSideStage.mRootTaskInfo != null
- && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
- return mSideStage;
- }
- return null;
- }
-
- @SplitScreen.StageType
- private int getStageType(StageTaskListener stage) {
- return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
- }
-
- @Override
- public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
- @Nullable TransitionRequestInfo request) {
- final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
- if (triggerTask == null) {
- // still want to monitor everything while in split-screen, so return non-null.
- return isSplitScreenVisible() ? new WindowContainerTransaction() : null;
- }
-
- WindowContainerTransaction out = null;
- final @WindowManager.TransitionType int type = request.getType();
- if (isSplitScreenVisible()) {
- // try to handle everything while in split-screen, so return a WCT even if it's empty.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split"
- + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
- + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
- mMainStage.getChildCount(), mSideStage.getChildCount());
- out = new WindowContainerTransaction();
- final StageTaskListener stage = getStageOfTask(triggerTask);
- if (stage != null) {
- // dismiss split if the last task in one of the stages is going away
- if (isClosingType(type) && stage.getChildCount() == 1) {
- // The top should be the opposite side that is closing:
- mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN
- ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
- }
- } else {
- if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) {
- // Going home so dismiss both.
- mDismissTop = STAGE_TYPE_UNDEFINED;
- }
- }
- if (mDismissTop != NO_DISMISS) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Dismiss from request. toTop=%s",
- stageTypeToString(mDismissTop));
- prepareExitSplitScreen(mDismissTop, out);
- mSplitTransitions.mPendingDismiss = transition;
- }
- } else {
- // Not in split mode, so look for an open into a split stage just so we can whine and
- // complain about how this isn't a supported operation.
- if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) {
- if (getStageOfTask(triggerTask) != null) {
- throw new IllegalStateException("Entering split implicitly with only one task"
- + " isn't supported.");
- }
- }
- }
- return out;
- }
-
- @Override
- public boolean startAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (transition != mSplitTransitions.mPendingDismiss
- && transition != mSplitTransitions.mPendingEnter) {
- // Not entering or exiting, so just do some house-keeping and validation.
-
- // If we're not in split-mode, just abort so something else can handle it.
- if (!isSplitScreenVisible()) return false;
-
- for (int iC = 0; iC < info.getChanges().size(); ++iC) {
- final TransitionInfo.Change change = info.getChanges().get(iC);
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo == null || !taskInfo.hasParentTask()) continue;
- final StageTaskListener stage = getStageOfTask(taskInfo);
- if (stage == null) continue;
- if (isOpeningType(change.getMode())) {
- if (!stage.containsTask(taskInfo.taskId)) {
- Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
- + " with " + taskInfo.taskId + " before startAnimation().");
- }
- } else if (isClosingType(change.getMode())) {
- if (stage.containsTask(taskInfo.taskId)) {
- Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
- + " with " + taskInfo.taskId + " before startAnimation().");
- }
- }
- }
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- // TODO(shell-transitions): Implement a fallback behavior for now.
- throw new IllegalStateException("Somehow removed the last task in a stage"
- + " outside of a proper transition");
- // This can happen in some pathological cases. For example:
- // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
- // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
- // In this case, the result *should* be that we leave split.
- // TODO(b/184679596): Find a way to either include task-org information in
- // the transition, or synchronize task-org callbacks.
- }
-
- // Use normal animations.
- return false;
- }
-
- boolean shouldAnimate = true;
- if (mSplitTransitions.mPendingEnter == transition) {
- shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
- } else if (mSplitTransitions.mPendingDismiss == transition) {
- shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
- }
- if (!shouldAnimate) return false;
-
- mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
- finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
- return true;
- }
-
- private boolean startPendingEnterAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
- if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) {
- // First, verify that we actually have opened 2 apps in split.
- TransitionInfo.Change mainChild = null;
- TransitionInfo.Change sideChild = null;
- for (int iC = 0; iC < info.getChanges().size(); ++iC) {
- final TransitionInfo.Change change = info.getChanges().get(iC);
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo == null || !taskInfo.hasParentTask()) continue;
- final @SplitScreen.StageType int stageType = getStageType(getStageOfTask(taskInfo));
- if (stageType == STAGE_TYPE_MAIN) {
- mainChild = change;
- } else if (stageType == STAGE_TYPE_SIDE) {
- sideChild = change;
- }
- }
- if (mainChild == null || sideChild == null) {
- throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
- + " 2 tasks in transition. Possibly one of them failed to launch");
- // TODO: fallback logic. Probably start a new transition to exit split before
- // applying anything here. Ideally consolidate with transition-merging.
- }
-
- // Update local states (before animating).
- setDividerVisibility(true);
- setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
- null /* wct */);
- setSplitsVisible(true);
-
- addDividerBarToTransition(info, t, true /* show */);
-
- // Make some noise if things aren't totally expected. These states shouldn't effect
- // transitions locally, but remotes (like Launcher) may get confused if they were
- // depending on listener callbacks. This can happen because task-organizer callbacks
- // aren't serialized with transition callbacks.
- // TODO(b/184679596): Find a way to either include task-org information in
- // the transition, or synchronize task-org callbacks.
- if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
- Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
- + " to have been called with " + mainChild.getTaskInfo().taskId
- + " before startAnimation().");
- }
- if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
- Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
- + " to have been called with " + sideChild.getTaskInfo().taskId
- + " before startAnimation().");
- }
- return true;
- } else {
- // TODO: other entry method animations
- throw new RuntimeException("Unsupported split-entry");
- }
- }
-
- private boolean startPendingDismissAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
- // Make some noise if things aren't totally expected. These states shouldn't effect
- // transitions locally, but remotes (like Launcher) may get confused if they were
- // depending on listener callbacks. This can happen because task-organizer callbacks
- // aren't serialized with transition callbacks.
- // TODO(b/184679596): Find a way to either include task-org information in
- // the transition, or synchronize task-org callbacks.
- if (mMainStage.getChildCount() != 0) {
- final StringBuilder tasksLeft = new StringBuilder();
- for (int i = 0; i < mMainStage.getChildCount(); ++i) {
- tasksLeft.append(i != 0 ? ", " : "");
- tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
- }
- Log.w(TAG, "Expected onTaskVanished on " + mMainStage
- + " to have been called with [" + tasksLeft.toString()
- + "] before startAnimation().");
- }
- if (mSideStage.getChildCount() != 0) {
- final StringBuilder tasksLeft = new StringBuilder();
- for (int i = 0; i < mSideStage.getChildCount(); ++i) {
- tasksLeft.append(i != 0 ? ", " : "");
- tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
- }
- Log.w(TAG, "Expected onTaskVanished on " + mSideStage
- + " to have been called with [" + tasksLeft.toString()
- + "] before startAnimation().");
- }
-
- // Update local states.
- setSplitsVisible(false);
- // Wait until after animation to update divider
-
- if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
- // Reset crops so they don't interfere with subsequent launches
- t.setWindowCrop(mMainStage.mRootLeash, null);
- t.setWindowCrop(mSideStage.mRootLeash, null);
- }
-
- if (mDismissTop == STAGE_TYPE_UNDEFINED) {
- // Going home (dismissing both splits)
-
- // TODO: Have a proper remote for this. Until then, though, reset state and use the
- // normal animation stuff (which falls back to the normal launcher remote).
- t.hide(mSplitLayout.getDividerLeash());
- setDividerVisibility(false);
- mSplitTransitions.mPendingDismiss = null;
- return false;
- }
-
- addDividerBarToTransition(info, t, false /* show */);
- // We're dismissing split by moving the other one to fullscreen.
- // Since we don't have any animations for this yet, just use the internal example
- // animations.
- return true;
- }
-
- private void addDividerBarToTransition(@NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, boolean show) {
- final SurfaceControl leash = mSplitLayout.getDividerLeash();
- final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
- final Rect bounds = mSplitLayout.getDividerBounds();
- barChange.setStartAbsBounds(bounds);
- barChange.setEndAbsBounds(bounds);
- barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
- barChange.setFlags(FLAG_IS_DIVIDER_BAR);
- // Technically this should be order-0, but this is running after layer assignment
- // and it's a special case, so just add to end.
- info.addChange(barChange);
- // Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
- if (show) {
- t.setAlpha(leash, 1.f);
- t.setLayer(leash, Integer.MAX_VALUE);
- t.setPosition(leash, bounds.left, bounds.top);
- t.show(leash);
- }
- }
-
- RemoteAnimationTarget getDividerBarLegacyTarget() {
- final Rect bounds = mSplitLayout.getDividerBounds();
- return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
- mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */,
- null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
- new android.graphics.Point(0, 0) /* position */, bounds, bounds,
- new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
- null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
- }
-
- RemoteAnimationTarget getOutlineLegacyTarget() {
- final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds();
- // Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to
- // distinguish as a split auxiliary target in Launcher.
- return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
- mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */,
- null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
- new android.graphics.Point(0, 0) /* position */, bounds, bounds,
- new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
- null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
- pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
- pw.println(innerPrefix + "MainStage");
- pw.println(childPrefix + "isActive=" + mMainStage.isActive());
- mMainStageListener.dump(pw, childPrefix);
- pw.println(innerPrefix + "SideStage");
- mSideStageListener.dump(pw, childPrefix);
- pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout);
- }
-
- /**
- * Directly set the visibility of both splits. This assumes hasChildren matches visibility.
- * This is intended for batch use, so it assumes other state management logic is already
- * handled.
- */
- private void setSplitsVisible(boolean visible) {
- mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
- mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
- }
-
- /**
- * Sets drag info to be logged when splitscreen is next entered.
- */
- public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
- mLogger.enterRequestedByDrag(position, dragSessionId);
- }
-
- /**
- * Logs the exit of splitscreen.
- */
- private void logExit(int exitReason) {
- mLogger.logExit(exitReason,
- SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
- SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
- mSplitLayout.isLandscape());
- }
-
- /**
- * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
- * executed.
- */
- private void logExitToStage(int exitReason, boolean toMainStage) {
- mLogger.logExit(exitReason,
- toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED,
- toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
- !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
- !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
- mSplitLayout.isLandscape());
- }
-
- class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
- boolean mHasRootTask = false;
- boolean mVisible = false;
- boolean mHasChildren = false;
-
- @Override
- public void onRootTaskAppeared() {
- mHasRootTask = true;
- StageCoordinator.this.onStageRootTaskAppeared(this);
- }
-
- @Override
- public void onStatusChanged(boolean visible, boolean hasChildren) {
- if (!mHasRootTask) return;
-
- if (mHasChildren != hasChildren) {
- mHasChildren = hasChildren;
- StageCoordinator.this.onStageHasChildrenChanged(this);
- }
- if (mVisible != visible) {
- mVisible = visible;
- StageCoordinator.this.onStageVisibilityChanged(this);
- }
- }
-
- @Override
- public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) {
- StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
- }
-
- @Override
- public void onRootTaskVanished() {
- reset();
- StageCoordinator.this.onStageRootTaskVanished(this);
- }
-
- @Override
- public void onNoLongerSupportMultiWindow() {
- if (mMainStage.isActive()) {
- StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
- SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW);
- }
- }
-
- private void reset() {
- mHasRootTask = false;
- mVisible = false;
- mHasChildren = false;
- }
-
- public void dump(@NonNull PrintWriter pw, String prefix) {
- pw.println(prefix + "mHasRootTask=" + mHasRootTask);
- pw.println(prefix + "mVisible=" + mVisible);
- pw.println(prefix + "mHasChildren=" + mHasChildren);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
deleted file mode 100644
index 7b679580fa87..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
+++ /dev/null
@@ -1,298 +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.stagesplit;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
-
-import android.annotation.CallSuper;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.SparseArray;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.NonNull;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.SurfaceUtils;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import java.io.PrintWriter;
-
-/**
- * Base class that handle common task org. related for split-screen stages.
- * Note that this class and its sub-class do not directly perform hierarchy operations.
- * They only serve to hold a collection of tasks and provide APIs like
- * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
- * to perform operations in-sync with other containers.
- *
- * @see StageCoordinator
- */
-class StageTaskListener implements ShellTaskOrganizer.TaskListener {
- private static final String TAG = StageTaskListener.class.getSimpleName();
-
- protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
- protected static final int[] CONTROLLED_WINDOWING_MODES =
- {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
- protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
- {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
-
- /** Callback interface for listening to changes in a split-screen stage. */
- public interface StageListenerCallbacks {
- void onRootTaskAppeared();
-
- void onStatusChanged(boolean visible, boolean hasChildren);
-
- void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
-
- void onRootTaskVanished();
- void onNoLongerSupportMultiWindow();
- }
-
- private final StageListenerCallbacks mCallbacks;
- private final SurfaceSession mSurfaceSession;
- protected final SyncTransactionQueue mSyncQueue;
-
- protected ActivityManager.RunningTaskInfo mRootTaskInfo;
- protected SurfaceControl mRootLeash;
- protected SurfaceControl mDimLayer;
- protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
- private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
-
- private final StageTaskUnfoldController mStageTaskUnfoldController;
-
- StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
- StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
- mCallbacks = callbacks;
- mSyncQueue = syncQueue;
- mSurfaceSession = surfaceSession;
- mStageTaskUnfoldController = stageTaskUnfoldController;
- taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
- }
-
- int getChildCount() {
- return mChildrenTaskInfo.size();
- }
-
- boolean containsTask(int taskId) {
- return mChildrenTaskInfo.contains(taskId);
- }
-
- /**
- * Returns the top activity uid for the top child task.
- */
- int getTopChildTaskUid() {
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
- if (info.topActivityInfo == null) {
- continue;
- }
- return info.topActivityInfo.applicationInfo.uid;
- }
- return 0;
- }
-
- /** @return {@code true} if this listener contains the currently focused task. */
- boolean isFocused() {
- if (mRootTaskInfo == null) {
- return false;
- }
-
- if (mRootTaskInfo.isFocused) {
- return true;
- }
-
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- if (mChildrenTaskInfo.valueAt(i).isFocused) {
- return true;
- }
- }
-
- return false;
- }
-
- @Override
- @CallSuper
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
- mRootLeash = leash;
- mRootTaskInfo = taskInfo;
- mCallbacks.onRootTaskAppeared();
- sendStatusChanged();
- mSyncQueue.runInSync(t -> {
- t.hide(mRootLeash);
- mDimLayer =
- SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
- });
- } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
- final int taskId = taskInfo.taskId;
- mChildrenLeashes.put(taskId, leash);
- mChildrenTaskInfo.put(taskId, taskInfo);
- updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
- mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
- if (ENABLE_SHELL_TRANSITIONS) {
- // Status is managed/synchronized by the transition lifecycle.
- return;
- }
- sendStatusChanged();
- } else {
- throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
- + "\n mRootTaskInfo: " + mRootTaskInfo);
- }
-
- if (mStageTaskUnfoldController != null) {
- mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
- }
- }
-
- @Override
- @CallSuper
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- if (!taskInfo.supportsMultiWindow) {
- // Leave split screen if the task no longer supports multi window.
- mCallbacks.onNoLongerSupportMultiWindow();
- return;
- }
- if (mRootTaskInfo.taskId == taskInfo.taskId) {
- mRootTaskInfo = taskInfo;
- } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
- mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
- mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
- taskInfo.isVisible);
- if (!ENABLE_SHELL_TRANSITIONS) {
- updateChildTaskSurface(
- taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
- }
- } else {
- throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
- + "\n mRootTaskInfo: " + mRootTaskInfo);
- }
- if (ENABLE_SHELL_TRANSITIONS) {
- // Status is managed/synchronized by the transition lifecycle.
- return;
- }
- sendStatusChanged();
- }
-
- @Override
- @CallSuper
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- final int taskId = taskInfo.taskId;
- if (mRootTaskInfo.taskId == taskId) {
- mCallbacks.onRootTaskVanished();
- mSyncQueue.runInSync(t -> t.remove(mDimLayer));
- mRootTaskInfo = null;
- } else if (mChildrenTaskInfo.contains(taskId)) {
- mChildrenTaskInfo.remove(taskId);
- mChildrenLeashes.remove(taskId);
- mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
- if (ENABLE_SHELL_TRANSITIONS) {
- // Status is managed/synchronized by the transition lifecycle.
- return;
- }
- sendStatusChanged();
- } else {
- throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
- + "\n mRootTaskInfo: " + mRootTaskInfo);
- }
-
- if (mStageTaskUnfoldController != null) {
- mStageTaskUnfoldController.onTaskVanished(taskInfo);
- }
- }
-
- @Override
- public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
- b.setParent(findTaskSurface(taskId));
- }
-
- @Override
- public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
- SurfaceControl.Transaction t) {
- t.reparent(sc, findTaskSurface(taskId));
- }
-
- private SurfaceControl findTaskSurface(int taskId) {
- if (mRootTaskInfo.taskId == taskId) {
- return mRootLeash;
- } else if (mChildrenLeashes.contains(taskId)) {
- return mChildrenLeashes.get(taskId);
- } else {
- throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
- }
- }
-
- void setBounds(Rect bounds, WindowContainerTransaction wct) {
- wct.setBounds(mRootTaskInfo.token, bounds);
- }
-
- void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
- if (!containsTask(taskId)) {
- return;
- }
- wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
- }
-
- void setVisibility(boolean visible, WindowContainerTransaction wct) {
- wct.reorder(mRootTaskInfo.token, visible /* onTop */);
- }
-
- void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
- @SplitScreen.StageType int stage) {
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- int taskId = mChildrenTaskInfo.keyAt(i);
- listener.onTaskStageChanged(taskId, stage,
- mChildrenTaskInfo.get(taskId).isVisible);
- }
- }
-
- private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl leash, boolean firstAppeared) {
- final Point taskPositionInParent = taskInfo.positionInParent;
- mSyncQueue.runInSync(t -> {
- t.setWindowCrop(leash, null);
- t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
- if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
- t.setAlpha(leash, 1f);
- t.setMatrix(leash, 1, 0, 0, 1);
- t.show(leash);
- }
- });
- }
-
- private void sendStatusChanged() {
- mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
- }
-
- @Override
- @CallSuper
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
deleted file mode 100644
index 62b9da6d4715..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * 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.stagesplit;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.animation.RectEvaluator;
-import android.animation.TypeEvaluator;
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.SparseArray;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
-
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
-import com.android.wm.shell.unfold.UnfoldBackgroundController;
-
-import java.util.concurrent.Executor;
-
-/**
- * Controls transformations of the split screen task surfaces in response
- * to the unfolding/folding action on foldable devices
- */
-public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
-
- private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
- private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
-
- private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
- private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
- private final DisplayInsetsController mDisplayInsetsController;
- private final UnfoldBackgroundController mBackgroundController;
- private final Executor mExecutor;
- private final int mExpandedTaskBarHeight;
- private final float mWindowCornerRadiusPx;
- private final Rect mStageBounds = new Rect();
- private final TransactionPool mTransactionPool;
-
- private InsetsSource mTaskbarInsetsSource;
- private boolean mBothStagesVisible;
-
- public StageTaskUnfoldController(@NonNull Context context,
- @NonNull TransactionPool transactionPool,
- @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
- @NonNull DisplayInsetsController displayInsetsController,
- @NonNull UnfoldBackgroundController backgroundController,
- @NonNull Executor executor) {
- mUnfoldProgressProvider = unfoldProgressProvider;
- mTransactionPool = transactionPool;
- mExecutor = executor;
- mBackgroundController = backgroundController;
- mDisplayInsetsController = displayInsetsController;
- mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.taskbar_frame_height);
- }
-
- /**
- * Initializes the controller, starts listening for the external events
- */
- public void init() {
- mUnfoldProgressProvider.addListener(mExecutor, this);
- mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
- }
-
- @Override
- public void insetsChanged(InsetsState insetsState) {
- mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update();
- }
- }
-
- /**
- * Called when split screen task appeared
- * @param taskInfo info for the appeared task
- * @param leash surface leash for the appeared task
- */
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- AnimationContext context = new AnimationContext(leash);
- mAnimationContextByTaskId.put(taskInfo.taskId, context);
- }
-
- /**
- * Called when a split screen task vanished
- * @param taskInfo info for the vanished task
- */
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
- if (context != null) {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- resetSurface(transaction, context);
- transaction.apply();
- mTransactionPool.release(transaction);
- }
- mAnimationContextByTaskId.remove(taskInfo.taskId);
- }
-
- @Override
- public void onStateChangeProgress(float progress) {
- if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
-
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- mBackgroundController.ensureBackground(transaction);
-
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- AnimationContext context = mAnimationContextByTaskId.valueAt(i);
-
- context.mCurrentCropRect.set(RECT_EVALUATOR
- .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
-
- transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
- .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
- }
-
- transaction.apply();
-
- mTransactionPool.release(transaction);
- }
-
- @Override
- public void onStateChangeFinished() {
- resetTransformations();
- }
-
- /**
- * Called when split screen visibility changes
- * @param bothStagesVisible true if both stages of the split screen are visible
- */
- public void onSplitVisibilityChanged(boolean bothStagesVisible) {
- mBothStagesVisible = bothStagesVisible;
- if (!bothStagesVisible) {
- resetTransformations();
- }
- }
-
- /**
- * Called when split screen stage bounds changed
- * @param bounds new bounds for this stage
- */
- public void onLayoutChanged(Rect bounds) {
- mStageBounds.set(bounds);
-
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update();
- }
- }
-
- private void resetTransformations() {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
-
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- resetSurface(transaction, context);
- }
- mBackgroundController.removeBackground(transaction);
- transaction.apply();
-
- mTransactionPool.release(transaction);
- }
-
- private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
- transaction
- .setWindowCrop(context.mLeash, null)
- .setCornerRadius(context.mLeash, 0.0F);
- }
-
- private class AnimationContext {
- final SurfaceControl mLeash;
- final Rect mStartCropRect = new Rect();
- final Rect mEndCropRect = new Rect();
- final Rect mCurrentCropRect = new Rect();
-
- private AnimationContext(SurfaceControl leash) {
- this.mLeash = leash;
- update();
- }
-
- private void update() {
- mStartCropRect.set(mStageBounds);
-
- if (mTaskbarInsetsSource != null) {
- // Only insets the cropping window with taskbar when taskbar is expanded
- if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
- mStartCropRect.inset(mTaskbarInsetsSource
- .calculateVisibleInsets(mStartCropRect));
- }
- }
-
- // Offset to surface coordinates as layout bounds are in screen coordinates
- mStartCropRect.offsetTo(0, 0);
-
- mEndCropRect.set(mStartCropRect);
-
- int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
- int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
- mStartCropRect.inset(margin, margin, margin, margin);
- }
- }
-}
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 54d62edf2570..a0e176c7ea68 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
@@ -261,7 +261,8 @@ public class StartingSurfaceDrawer {
WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
params.setFitInsetsSides(0);
params.setFitInsetsTypes(0);
- params.format = PixelFormat.TRANSLUCENT;
+ params.format = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
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
new file mode 100644
index 000000000000..1ffe26df729f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.splitscreen.StageCoordinator.FLAG_IS_DIVIDER_BAR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+import java.util.ArrayList;
+
+/**
+ * A handler for dealing with transitions involving multiple other handlers. For example: an
+ * activity in split-screen going into PiP.
+ */
+public class DefaultMixedHandler implements Transitions.TransitionHandler {
+
+ private final Transitions mPlayer;
+ private final PipTransitionController mPipHandler;
+ private final StageCoordinator mSplitHandler;
+
+ private static class MixedTransition {
+ static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
+
+ final int mType;
+ final IBinder mTransition;
+
+ Transitions.TransitionFinishCallback mFinishCallback = null;
+
+ /**
+ * Mixed transitions are made up of multiple "parts". This keeps track of how many
+ * parts are currently animating.
+ */
+ int mInFlightSubAnimations = 0;
+
+ MixedTransition(int type, IBinder transition) {
+ mType = type;
+ mTransition = transition;
+ }
+ }
+ private final ArrayList<MixedTransition> mActiveTransitions = new ArrayList<>();
+
+ public DefaultMixedHandler(@NonNull Transitions player,
+ @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
+ mPlayer = player;
+ mPipHandler = pipHandler;
+ mSplitHandler = splitHandler;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitActive()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while "
+ + "Split-Screen is active, so treat it as Mixed.");
+ if (request.getRemoteTransition() != null) {
+ throw new IllegalStateException("Unexpected remote transition in"
+ + "pip-enter-from-split request");
+ }
+ mActiveTransitions.add(new MixedTransition(MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT,
+ transition));
+
+ WindowContainerTransaction out = new WindowContainerTransaction();
+ mPipHandler.augmentRequest(transition, request, out);
+ mSplitHandler.addEnterOrExitIfNeeded(request, out);
+ return out;
+ }
+ return null;
+ }
+
+ private TransitionInfo subCopy(@NonNull TransitionInfo info,
+ @WindowManager.TransitionType int newType) {
+ final TransitionInfo out = new TransitionInfo(newType, info.getFlags());
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ out.getChanges().add(info.getChanges().get(i));
+ }
+ out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y);
+ out.setAnimationOptions(info.getAnimationOptions());
+ return out;
+ }
+
+ private boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
+ return change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME;
+ }
+
+ private boolean isWallpaper(@NonNull TransitionInfo.Change change) {
+ return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ MixedTransition mixed = null;
+ for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
+ if (mActiveTransitions.get(i).mTransition != transition) continue;
+ mixed = mActiveTransitions.remove(i);
+ break;
+ }
+ if (mixed == null) return false;
+
+ if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+ return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
+ finishCallback);
+ } else {
+ throw new IllegalStateException("Starting mixed animation without a known mixed type? "
+ + mixed.mType);
+ }
+ }
+
+ private boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ + "entering PIP while Split-Screen is active.");
+ TransitionInfo.Change pipChange = null;
+ TransitionInfo.Change wallpaper = null;
+ final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK);
+ boolean homeIsOpening = false;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ // going backwards, so remove-by-index is fine.
+ everythingElse.getChanges().remove(i);
+ } else if (isHomeOpening(change)) {
+ homeIsOpening = true;
+ } else if (isWallpaper(change)) {
+ wallpaper = change;
+ }
+ }
+ if (pipChange == null) {
+ // um, something probably went wrong.
+ return false;
+ }
+ final boolean isGoingHome = homeIsOpening;
+ mixed.mFinishCallback = finishCallback;
+ Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ --mixed.mInFlightSubAnimations;
+ if (mixed.mInFlightSubAnimations > 0) return;
+ if (isGoingHome) {
+ mSplitHandler.onTransitionAnimationComplete();
+ }
+ mixed.mFinishCallback.onTransitionFinished(wct, wctCB);
+ };
+ if (isGoingHome) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
+ + "since entering-PiP caused us to leave split and return home.");
+ // We need to split the transition into 2 parts: the pip part (animated by pip)
+ // and the dismiss-part (animated by launcher).
+ mixed.mInFlightSubAnimations = 2;
+ // immediately make the wallpaper visible (so that we don't see it pop-in during
+ // the time it takes to start recents animation (which is remote).
+ if (wallpaper != null) {
+ startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
+ }
+ // make a new startTransaction because pip's startEnterAnimation "consumes" it so
+ // we need a separate one to send over to launcher.
+ SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+ // Let split update internal state for dismiss.
+ mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED,
+ EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
+ finishTransaction);
+
+ // We are trying to accommodate launcher's close animation which can't handle the
+ // divider-bar, so if split-handler is closing the divider-bar, just hide it and remove
+ // from transition info.
+ for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
+ if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) != 0) {
+ everythingElse.getChanges().remove(i);
+ break;
+ }
+ }
+
+ mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
+ finishCB);
+ // Dispatch the rest of the transition normally. This will most-likely be taken by
+ // recents or default handler.
+ mPlayer.dispatchTransition(mixed.mTransition, everythingElse, otherStartT,
+ finishTransaction, finishCB, this);
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just "
+ + "forward animation to Pip-Handler.");
+ // This happens if the pip-ing activity is in a multi-activity task (and thus a
+ // new pip task is spawned). In this case, we don't actually exit split so we can
+ // just let pip transition handle the animation verbatim.
+ mixed.mInFlightSubAnimations = 1;
+ mPipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction,
+ finishCB);
+ }
+ return true;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ }
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder transition) {
+ MixedTransition mixed = null;
+ for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
+ if (mActiveTransitions.get(i).mTransition != transition) continue;
+ mixed = mActiveTransitions.remove(i);
+ break;
+ }
+ if (mixed == null) return;
+ if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+ mPipHandler.onTransitionMerged(transition);
+ }
+ }
+}
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 9154226b7b22..c3eaa8ee1da0 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
@@ -24,6 +24,7 @@ import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -42,6 +43,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
@@ -470,8 +472,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
final Rect clipRect = Transitions.isClosingType(change.getMode())
- ? mRotator.getEndBoundsInStartRotation(change)
- : change.getEndAbsBounds();
+ ? new Rect(mRotator.getEndBoundsInStartRotation(change))
+ : new Rect(change.getEndAbsBounds());
+ clipRect.offsetTo(0, 0);
if (delayedEdgeExtension) {
// If the edge extension needs to happen after the startTransition has been
@@ -684,6 +687,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final Rect endBounds = Transitions.isClosingType(changeMode)
? mRotator.getEndBoundsInStartRotation(change)
: change.getEndAbsBounds();
+ final boolean isDream =
+ isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
if (info.isKeyguardGoingAway()) {
a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
@@ -726,7 +731,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
} else {
int animAttr = 0;
boolean translucent = false;
- if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+ if (isDream) {
+ if (type == TRANSIT_OPEN) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
+ : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
+ } else if (type == TRANSIT_CLOSE) {
+ animAttr = enter
+ ? 0
+ : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
+ }
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
animAttr = enter
? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
: R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
@@ -790,6 +805,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
}
}
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr,
+ transitTypeToString(type),
+ enter);
}
if (a != null) {
@@ -954,7 +974,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private static void applyTransformation(long time, SurfaceControl.Transaction t,
SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix,
- Point position, float cornerRadius, @Nullable Rect clipRect) {
+ Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
anim.getTransformation(time, transformation);
if (position != null) {
transformation.getMatrix().postTranslate(position.x, position.y);
@@ -962,6 +982,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
t.setMatrix(leash, transformation.getMatrix(), matrix);
t.setAlpha(leash, transformation.getAlpha());
+ final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect);
Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
// Clip out any overflowing edge extension
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 435d67087f34..de0f47fa0a6b 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
@@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -287,12 +288,14 @@ public class Transitions implements RemoteCallable<Transitions> {
finishT.setAlpha(leash, 1.f);
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
- // Wallpaper is a bit of an anomaly: it's visibility is tied to other WindowStates.
- // As a result, we actually can't hide it's WindowToken because there may not be a
- // transition associated with it becoming visible again. Fortunately, since it is
- // always z-ordered to the back, we don't have to worry about it flickering to the
- // front during reparenting, so the hide here isn't necessary for it.
- if ((change.getFlags() & FLAG_IS_WALLPAPER) == 0) {
+ // Wallpaper/IME are anomalies: their visibility is tied to other WindowStates.
+ // As a result, we actually can't hide their WindowTokens because there may not be a
+ // transition associated with them becoming visible again. Fortunately, since
+ // wallpapers are always z-ordered to the back, we don't have to worry about it
+ // flickering to the front during reparenting. Similarly, the IME is reparented to
+ // the associated app, so its visibility is coupled. So, an explicit hide is not
+ // needed visually anyways.
+ if ((change.getFlags() & (FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD)) == 0) {
finishT.hide(leash);
}
}
@@ -309,13 +312,14 @@ public class Transitions implements RemoteCallable<Transitions> {
if (info.getRootLeash().isValid()) {
t.show(info.getRootLeash());
}
+ final int numChanges = info.getChanges().size();
// Put animating stuff above this line and put static stuff below it.
- int zSplitLine = info.getChanges().size();
+ final int zSplitLine = numChanges + 1;
// changes should be ordered top-to-bottom in z
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ for (int i = numChanges - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final SurfaceControl leash = change.getLeash();
- final int mode = info.getChanges().get(i).getMode();
+ final int mode = change.getMode();
// Don't reparent anything that isn't independent within its parents
if (!TransitionInfo.isIndependent(change, info)) {
@@ -329,26 +333,31 @@ public class Transitions implements RemoteCallable<Transitions> {
t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
change.getStartAbsBounds().top - info.getRootOffset().y);
}
+ final int layer;
// Put all the OPEN/SHOW on top
- if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
+ if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ // Wallpaper is always at the bottom.
+ layer = 0;
+ } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
if (isOpening) {
// put on top
- t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
+ layer = zSplitLine + numChanges - i;
} else {
// put on bottom
- t.setLayer(leash, zSplitLine - i);
+ layer = zSplitLine - i;
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
if (isOpening) {
// put on bottom and leave visible
- t.setLayer(leash, zSplitLine - i);
+ layer = zSplitLine - i;
} else {
// put on top
- t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
+ layer = zSplitLine + numChanges - i;
}
} else { // CHANGE or other
- t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
+ layer = zSplitLine + numChanges - i;
}
+ t.setLayer(leash, layer);
}
}
@@ -377,6 +386,7 @@ public class Transitions implements RemoteCallable<Transitions> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
transitionToken, info);
t.apply();
+ finishT.apply();
onAbort(transitionToken);
return;
}
@@ -400,6 +410,7 @@ public class Transitions implements RemoteCallable<Transitions> {
}
if (nonTaskChange && transferStartingWindow) {
t.apply();
+ finishT.apply();
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
onAbort(transitionToken);
@@ -435,33 +446,42 @@ public class Transitions implements RemoteCallable<Transitions> {
playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb));
}
- boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) {
- return handler.startAnimation(active.mToken, active.mInfo, active.mStartT, active.mFinishT,
- (wct, cb) -> onFinish(active.mToken, wct, cb));
- }
-
- void playTransition(@NonNull ActiveTransition active) {
+ private void playTransition(@NonNull ActiveTransition active) {
setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);
// If a handler already chose to run this animation, try delegating to it first.
if (active.mHandler != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
active.mHandler);
- if (startAnimation(active, active.mHandler)) {
+ boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo,
+ active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb));
+ if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
return;
}
}
- // Otherwise give every other handler a chance (in order)
+ // Otherwise give every other handler a chance
+ active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
+ active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb), active.mHandler);
+ }
+
+ /**
+ * Gives every handler (in order) a chance to animate until one consumes the transition.
+ * @return the handler which consumed the transition.
+ */
+ TransitionHandler dispatchTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
+ @NonNull TransitionFinishCallback finishCB, @Nullable TransitionHandler skip) {
for (int i = mHandlers.size() - 1; i >= 0; --i) {
- if (mHandlers.get(i) == active.mHandler) continue;
+ if (mHandlers.get(i) == skip) continue;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s",
mHandlers.get(i));
- if (startAnimation(active, mHandlers.get(i))) {
+ boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT,
+ finishCB);
+ if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
mHandlers.get(i));
- active.mHandler = mHandlers.get(i);
- return;
+ return mHandlers.get(i);
}
}
throw new IllegalStateException(
@@ -615,8 +635,9 @@ public class Transitions implements RemoteCallable<Transitions> {
if (wct == null) {
wct = new WindowContainerTransaction();
}
- mDisplayController.getChangeController().dispatchOnRotateDisplay(wct,
- change.getDisplayId(), change.getStartRotation(), change.getEndRotation());
+ mDisplayController.getChangeController().dispatchOnDisplayChange(wct,
+ change.getDisplayId(), change.getStartRotation(), change.getEndRotation(),
+ null /* newDisplayAreaInfo */);
}
}
active.mToken = mOrganizer.startTransition(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
new file mode 100644
index 000000000000..530d47416665
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -0,0 +1,219 @@
+/*
+ * 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.unfold;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import dagger.Lazy;
+
+/**
+ * Manages fold/unfold animations of tasks on foldable devices.
+ * When folding or unfolding a foldable device we play animations that
+ * transform task cropping/scaling/rounded corners.
+ *
+ * This controller manages:
+ * 1) Folding/unfolding when Shell transitions disabled
+ * 2) Folding when Shell transitions enabled, unfolding is managed by
+ * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler}
+ */
+public class UnfoldAnimationController implements UnfoldListener {
+
+ private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
+ private final Executor mExecutor;
+ private final TransactionPool mTransactionPool;
+ private final List<UnfoldTaskAnimator> mAnimators;
+ private final Lazy<Optional<UnfoldTransitionHandler>> mUnfoldTransitionHandler;
+
+ private final SparseArray<SurfaceControl> mTaskSurfaces = new SparseArray<>();
+ private final SparseArray<UnfoldTaskAnimator> mAnimatorsByTaskId = new SparseArray<>();
+
+ public UnfoldAnimationController(@NonNull TransactionPool transactionPool,
+ @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
+ @NonNull List<UnfoldTaskAnimator> animators,
+ @NonNull Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
+ @NonNull Executor executor) {
+ mUnfoldProgressProvider = unfoldProgressProvider;
+ mUnfoldTransitionHandler = unfoldTransitionHandler;
+ mTransactionPool = transactionPool;
+ mExecutor = executor;
+ mAnimators = animators;
+ }
+
+ /**
+ * Initializes the controller, starts listening for the external events
+ */
+ public void init() {
+ mUnfoldProgressProvider.addListener(mExecutor, this);
+
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ animator.init();
+ animator.start();
+ }
+ }
+
+ /**
+ * Called when a task appeared
+ * @param taskInfo info for the appeared task
+ * @param leash surface leash for the appeared task
+ */
+ public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ mTaskSurfaces.put(taskInfo.taskId, leash);
+
+ // Find the first matching animator
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ if (animator.isApplicableTask(taskInfo)) {
+ mAnimatorsByTaskId.put(taskInfo.taskId, animator);
+ animator.onTaskAppeared(taskInfo, leash);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Called when task info changed
+ * @param taskInfo info for the changed task
+ */
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ final UnfoldTaskAnimator animator = mAnimatorsByTaskId.get(taskInfo.taskId);
+ final boolean isCurrentlyApplicable = animator != null;
+
+ if (isCurrentlyApplicable) {
+ final boolean isApplicable = animator.isApplicableTask(taskInfo);
+ if (isApplicable) {
+ // Still applicable, send update
+ animator.onTaskChanged(taskInfo);
+ } else {
+ // Became inapplicable
+ resetTask(animator, taskInfo);
+ animator.onTaskVanished(taskInfo);
+ mAnimatorsByTaskId.remove(taskInfo.taskId);
+ }
+ } else {
+ // Find the first matching animator
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator currentAnimator = mAnimators.get(i);
+ if (currentAnimator.isApplicableTask(taskInfo)) {
+ // Became applicable
+ mAnimatorsByTaskId.put(taskInfo.taskId, currentAnimator);
+
+ SurfaceControl leash = mTaskSurfaces.get(taskInfo.taskId);
+ currentAnimator.onTaskAppeared(taskInfo, leash);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Called when a task vanished
+ * @param taskInfo info for the vanished task
+ */
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ mTaskSurfaces.remove(taskInfo.taskId);
+
+ final UnfoldTaskAnimator animator = mAnimatorsByTaskId.get(taskInfo.taskId);
+ final boolean isCurrentlyApplicable = animator != null;
+
+ if (isCurrentlyApplicable) {
+ resetTask(animator, taskInfo);
+ animator.onTaskVanished(taskInfo);
+ mAnimatorsByTaskId.remove(taskInfo.taskId);
+ }
+ }
+
+ @Override
+ public void onStateChangeStarted() {
+ if (mUnfoldTransitionHandler.get().get().willHandleTransition()) {
+ return;
+ }
+
+ SurfaceControl.Transaction transaction = null;
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ if (animator.hasActiveTasks()) {
+ if (transaction == null) transaction = mTransactionPool.acquire();
+ animator.prepareStartTransaction(transaction);
+ }
+ }
+
+ if (transaction != null) {
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ if (mUnfoldTransitionHandler.get().get().willHandleTransition()) {
+ return;
+ }
+
+ SurfaceControl.Transaction transaction = null;
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ if (animator.hasActiveTasks()) {
+ if (transaction == null) transaction = mTransactionPool.acquire();
+ animator.applyAnimationProgress(progress, transaction);
+ }
+ }
+
+ if (transaction != null) {
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ if (mUnfoldTransitionHandler.get().get().willHandleTransition()) {
+ return;
+ }
+
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ animator.resetAllSurfaces(transaction);
+ animator.prepareFinishTransaction(transaction);
+ }
+
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ private void resetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ animator.resetSurface(taskInfo, transaction);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
index 9faf454261d3..86ca292399cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
@@ -79,7 +79,7 @@ public class UnfoldBackgroundController {
}
private float[] getBackgroundColor(Context context) {
- int colorInt = context.getResources().getColor(R.color.unfold_transition_background);
+ int colorInt = context.getResources().getColor(R.color.taskbar_background);
return new float[]{
(float) red(colorInt) / 255.0F,
(float) green(colorInt) / 255.0F,
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 639603941c18..9bf32faa12bd 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
@@ -16,8 +16,6 @@
package com.android.wm.shell.unfold;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
import android.os.IBinder;
@@ -34,11 +32,19 @@ import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
import com.android.wm.shell.transition.Transitions.TransitionHandler;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
+import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
+import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
+/**
+ * Transition handler that is responsible for animating app surfaces when unfolding of foldable
+ * devices. It does not handle the folding animation, which is done in
+ * {@link UnfoldAnimationController}.
+ */
public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListener {
private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
@@ -51,17 +57,26 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@Nullable
private IBinder mTransition;
- private final List<TransitionInfo.Change> mAnimatedFullscreenTasks = new ArrayList<>();
+ private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>();
public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider,
- TransactionPool transactionPool, Executor executor, Transitions transitions) {
+ FullscreenUnfoldTaskAnimator fullscreenUnfoldAnimator,
+ SplitTaskUnfoldAnimator splitUnfoldTaskAnimator,
+ TransactionPool transactionPool,
+ Executor executor, Transitions transitions) {
mUnfoldProgressProvider = unfoldProgressProvider;
mTransactionPool = transactionPool;
mExecutor = executor;
mTransitions = transitions;
+
+ mAnimators.add(splitUnfoldTaskAnimator);
+ mAnimators.add(fullscreenUnfoldAnimator);
}
public void init() {
+ for (int i = 0; i < mAnimators.size(); i++) {
+ mAnimators.get(i).init();
+ }
mTransitions.addHandler(this);
mUnfoldProgressProvider.addListener(mExecutor, this);
}
@@ -71,49 +86,69 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
-
if (transition != mTransition) return false;
- startTransaction.apply();
-
- mAnimatedFullscreenTasks.clear();
- info.getChanges().forEach(change -> {
- final boolean allowedToAnimate = change.getTaskInfo() != null
- && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME
- && change.getMode() == TRANSIT_CHANGE;
-
- if (allowedToAnimate) {
- mAnimatedFullscreenTasks.add(change);
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ animator.clearTasks();
+
+ info.getChanges().forEach(change -> {
+ if (change.getTaskInfo() != null
+ && change.getMode() == TRANSIT_CHANGE
+ && animator.isApplicableTask(change.getTaskInfo())) {
+ animator.onTaskAppeared(change.getTaskInfo(), change.getLeash());
+ }
+ });
+
+ if (animator.hasActiveTasks()) {
+ animator.prepareStartTransaction(startTransaction);
+ animator.prepareFinishTransaction(finishTransaction);
+ animator.start();
}
- });
+ }
+ startTransaction.apply();
mFinishCallback = finishCallback;
- mTransition = null;
return true;
}
@Override
public void onStateChangeProgress(float progress) {
- mAnimatedFullscreenTasks.forEach(change -> {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ if (mTransition == null) return;
+
+ SurfaceControl.Transaction transaction = null;
- // TODO: this is a placeholder animation, replace with a spec version in the next CLs
- final float testScale = 0.8f + 0.2f * progress;
- transaction.setScale(change.getLeash(), testScale, testScale);
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ if (animator.hasActiveTasks()) {
+ if (transaction == null) {
+ transaction = mTransactionPool.acquire();
+ }
+
+ animator.applyAnimationProgress(progress, transaction);
+ }
+ }
+
+ if (transaction != null) {
transaction.apply();
mTransactionPool.release(transaction);
- });
+ }
}
@Override
public void onStateChangeFinished() {
- if (mFinishCallback != null) {
- mFinishCallback.onTransitionFinished(null, null);
- mFinishCallback = null;
- mAnimatedFullscreenTasks.clear();
+ 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;
}
@Nullable
@@ -127,4 +162,8 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
}
return null;
}
+
+ public boolean willHandleTransition() {
+ return mTransition != null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index aa3868cfca84..eab82f00e962 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package com.android.wm.shell.fullscreen;
+package com.android.wm.shell.unfold.animation;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.util.MathUtils.lerp;
import static android.view.Display.DEFAULT_DISPLAY;
import android.animation.RectEvaluator;
import android.animation.TypeEvaluator;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Matrix;
@@ -32,22 +32,26 @@ import android.util.SparseArray;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
-import java.util.concurrent.Executor;
-
/**
- * Controls full screen app unfold transition: animating cropping window and scaling when
- * folding or unfolding a foldable device.
+ * This helper class contains logic that calculates scaling and cropping parameters
+ * for the folding/unfolding animation. As an input it receives TaskInfo objects and
+ * surfaces leashes and as an output it could fill surface transactions with required
+ * transformations.
+ *
+ * This class is used by
+ * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} and
+ * {@link UnfoldAnimationController}. They use independent
+ * instances of FullscreenUnfoldTaskAnimator.
*/
-public final class FullscreenUnfoldController implements UnfoldListener,
- OnInsetsChangedListener {
+public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
+ DisplayInsetsController.OnInsetsChangedListener {
private static final float[] FLOAT_9 = new float[9];
private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
@@ -57,49 +61,77 @@ public final class FullscreenUnfoldController implements UnfoldListener,
private static final float END_SCALE = 1f;
private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2;
- private final Executor mExecutor;
- private final ShellUnfoldProgressProvider mProgressProvider;
- private final DisplayInsetsController mDisplayInsetsController;
-
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+ private final int mExpandedTaskBarHeight;
+ private final float mWindowCornerRadiusPx;
+ private final DisplayInsetsController mDisplayInsetsController;
private final UnfoldBackgroundController mBackgroundController;
private InsetsSource mTaskbarInsetsSource;
- private final float mWindowCornerRadiusPx;
- private final float mExpandedTaskBarHeight;
-
- private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
-
- public FullscreenUnfoldController(
- @NonNull Context context,
- @NonNull Executor executor,
+ public FullscreenUnfoldTaskAnimator(Context context,
@NonNull UnfoldBackgroundController backgroundController,
- @NonNull ShellUnfoldProgressProvider progressProvider,
- @NonNull DisplayInsetsController displayInsetsController
- ) {
- mExecutor = executor;
- mProgressProvider = progressProvider;
+ DisplayInsetsController displayInsetsController) {
mDisplayInsetsController = displayInsetsController;
- mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mBackgroundController = backgroundController;
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.taskbar_frame_height);
- mBackgroundController = backgroundController;
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
}
- /**
- * Initializes the controller
- */
public void init() {
- mProgressProvider.addListener(mExecutor, this);
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
}
@Override
- public void onStateChangeProgress(float progress) {
- if (mAnimationContextByTaskId.size() == 0) return;
+ public void insetsChanged(InsetsState insetsState) {
+ mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update(mTaskbarInsetsSource, context.mTaskInfo);
+ }
+ }
+
+ public boolean hasActiveTasks() {
+ return mAnimationContextByTaskId.size() > 0;
+ }
+
+ @Override
+ public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {
+ AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
+ taskInfo);
+ mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
+ }
+
+ @Override
+ public void onTaskChanged(TaskInfo taskInfo) {
+ AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (animationContext != null) {
+ animationContext.update(mTaskbarInsetsSource, taskInfo);
+ }
+ }
+
+ @Override
+ public void onTaskVanished(TaskInfo taskInfo) {
+ mAnimationContextByTaskId.remove(taskInfo.taskId);
+ }
- mBackgroundController.ensureBackground(mTransaction);
+ @Override
+ public void clearTasks() {
+ mAnimationContextByTaskId.clear();
+ }
+
+ @Override
+ public void resetSurface(TaskInfo taskInfo, Transaction transaction) {
+ final AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (context != null) {
+ resetSurface(context, transaction);
+ }
+ }
+
+ @Override
+ public void applyAnimationProgress(float progress, Transaction transaction) {
+ if (mAnimationContextByTaskId.size() == 0) return;
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
@@ -111,75 +143,41 @@ public final class FullscreenUnfoldController implements UnfoldListener,
context.mMatrix.setScale(scale, scale, context.mCurrentCropRect.exactCenterX(),
context.mCurrentCropRect.exactCenterY());
- mTransaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
+ transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
.setMatrix(context.mLeash, context.mMatrix, FLOAT_9)
- .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
+ .setCornerRadius(context.mLeash, mWindowCornerRadiusPx)
+ .show(context.mLeash);
}
-
- mTransaction.apply();
}
@Override
- public void onStateChangeFinished() {
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- resetSurface(context);
- }
-
- mBackgroundController.removeBackground(mTransaction);
- mTransaction.apply();
+ public void prepareStartTransaction(Transaction transaction) {
+ mBackgroundController.ensureBackground(transaction);
}
@Override
- public void insetsChanged(InsetsState insetsState) {
- mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update(mTaskbarInsetsSource, context.mTaskInfo);
- }
+ public void prepareFinishTransaction(Transaction transaction) {
+ mBackgroundController.removeBackground(transaction);
}
- /**
- * Called when a new matching task appeared
- */
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
- taskInfo);
- mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
- }
-
- /**
- * Called when matching task changed
- */
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
- if (animationContext != null) {
- animationContext.update(mTaskbarInsetsSource, taskInfo);
- }
+ @Override
+ public boolean isApplicableTask(TaskInfo taskInfo) {
+ return taskInfo != null && taskInfo.isVisible()
+ && taskInfo.realActivity != null // to filter out parents created by organizer
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && taskInfo.getActivityType() != ACTIVITY_TYPE_HOME;
}
- /**
- * Called when matching task vanished
- */
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
- if (animationContext != null) {
- // PiP task has its own cleanup path, ignore surface reset to avoid conflict.
- if (taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED) {
- resetSurface(animationContext);
- }
- mAnimationContextByTaskId.remove(taskInfo.taskId);
- }
-
- if (mAnimationContextByTaskId.size() == 0) {
- mBackgroundController.removeBackground(mTransaction);
+ @Override
+ public void resetAllSurfaces(Transaction transaction) {
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ resetSurface(context, transaction);
}
-
- mTransaction.apply();
}
- private void resetSurface(AnimationContext context) {
- mTransaction
+ private void resetSurface(AnimationContext context, Transaction transaction) {
+ transaction
.setWindowCrop(context.mLeash, null)
.setCornerRadius(context.mLeash, 0.0F)
.setMatrix(context.mLeash, 1.0F, 0.0F, 0.0F, 1.0F)
@@ -197,10 +195,9 @@ public final class FullscreenUnfoldController implements UnfoldListener,
TaskInfo mTaskInfo;
- private AnimationContext(SurfaceControl leash,
- InsetsSource taskBarInsetsSource,
- TaskInfo taskInfo) {
- this.mLeash = leash;
+ private AnimationContext(SurfaceControl leash, InsetsSource taskBarInsetsSource,
+ TaskInfo taskInfo) {
+ mLeash = leash;
update(taskBarInsetsSource, taskInfo);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index 59eecb5db136..6e10ebe94c5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,17 +14,19 @@
* limitations under the License.
*/
-package com.android.wm.shell.splitscreen;
+package com.android.wm.shell.unfold.animation;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.animation.RectEvaluator;
import android.animation.TypeEvaluator;
-import android.annotation.NonNull;
-import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -32,67 +34,130 @@ import android.util.SparseArray;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
-import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
+import java.util.Optional;
import java.util.concurrent.Executor;
+import dagger.Lazy;
+
/**
- * Controls transformations of the split screen task surfaces in response
- * to the unfolding/folding action on foldable devices
+ * This helper class contains logic that calculates scaling and cropping parameters
+ * for the folding/unfolding animation. As an input it receives TaskInfo objects and
+ * surfaces leashes and as an output it could fill surface transactions with required
+ * transformations.
+ *
+ * This class is used by
+ * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} and
+ * {@link UnfoldAnimationController}.
+ * They use independent instances of SplitTaskUnfoldAnimator.
*/
-public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
+public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
+ DisplayInsetsController.OnInsetsChangedListener, SplitScreenListener {
private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
- private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
- private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
- private final DisplayInsetsController mDisplayInsetsController;
- private final UnfoldBackgroundController mBackgroundController;
private final Executor mExecutor;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
private final int mExpandedTaskBarHeight;
private final float mWindowCornerRadiusPx;
- private final Rect mStageBounds = new Rect();
- private final TransactionPool mTransactionPool;
+ private final Lazy<Optional<SplitScreenController>> mSplitScreenController;
+ private final UnfoldBackgroundController mUnfoldBackgroundController;
+
+ private final Rect mMainStageBounds = new Rect();
+ private final Rect mSideStageBounds = new Rect();
+ private final Rect mRootStageBounds = new Rect();
private InsetsSource mTaskbarInsetsSource;
- private boolean mBothStagesVisible;
-
- public StageTaskUnfoldController(@NonNull Context context,
- @NonNull TransactionPool transactionPool,
- @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
- @NonNull DisplayInsetsController displayInsetsController,
- @NonNull UnfoldBackgroundController backgroundController,
- @NonNull Executor executor) {
- mUnfoldProgressProvider = unfoldProgressProvider;
- mTransactionPool = transactionPool;
- mExecutor = executor;
- mBackgroundController = backgroundController;
+
+ @SplitPosition
+ private int mMainStagePosition = SPLIT_POSITION_UNDEFINED;
+ @SplitPosition
+ private int mSideStagePosition = SPLIT_POSITION_UNDEFINED;
+
+ public SplitTaskUnfoldAnimator(Context context, Executor executor,
+ Lazy<Optional<SplitScreenController>> splitScreenController,
+ UnfoldBackgroundController unfoldBackgroundController,
+ DisplayInsetsController displayInsetsController) {
mDisplayInsetsController = displayInsetsController;
- mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mExecutor = executor;
+ mUnfoldBackgroundController = unfoldBackgroundController;
+ mSplitScreenController = splitScreenController;
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.taskbar_frame_height);
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
}
- /**
- * Initializes the controller, starts listening for the external events
- */
+ /** Initializes the animator, this should be called only once */
+ @Override
public void init() {
- mUnfoldProgressProvider.addListener(mExecutor, this);
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
}
+ /**
+ * Starts listening for split-screen changes and gets initial split-screen
+ * layout information through the listener
+ */
+ @Override
+ public void start() {
+ mSplitScreenController.get().get().asSplitScreen()
+ .registerSplitScreenListener(this, mExecutor);
+ }
+
+ /**
+ * Stops listening for the split-screen layout changes
+ */
+ @Override
+ public void stop() {
+ mSplitScreenController.get().get().asSplitScreen()
+ .unregisterSplitScreenListener(this);
+ }
+
@Override
public void insetsChanged(InsetsState insetsState) {
mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ updateContexts();
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ final AnimationContext context = mAnimationContextByTaskId.get(taskId);
+ if (context != null) {
+ context.mStageType = stage;
+ context.update();
+ }
+ }
+
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ if (stage == STAGE_TYPE_MAIN) {
+ mMainStagePosition = position;
+ } else {
+ mSideStagePosition = position;
+ }
+ updateContexts();
+ }
+
+ @Override
+ public void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) {
+ mRootStageBounds.set(rootBounds);
+ mMainStageBounds.set(mainBounds);
+ mSideStageBounds.set(sideBounds);
+ updateContexts();
+ }
+
+ private void updateContexts() {
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
context.update();
@@ -100,44 +165,73 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
}
/**
- * Called when split screen task appeared
- * @param taskInfo info for the appeared task
- * @param leash surface leash for the appeared task
+ * Register a split task in the animator
+ * @param taskInfo info of the task
+ * @param leash the surface of the task
*/
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- // Only handle child task surface here.
- if (!taskInfo.hasParentTask()) return;
-
+ @Override
+ public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {
AnimationContext context = new AnimationContext(leash);
mAnimationContextByTaskId.put(taskInfo.taskId, context);
}
/**
- * Called when a split screen task vanished
- * @param taskInfo info for the vanished task
+ * Unregister the task from the unfold animation
+ * @param taskInfo info of the task
+ */
+ @Override
+ public void onTaskVanished(TaskInfo taskInfo) {
+ mAnimationContextByTaskId.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public boolean isApplicableTask(TaskInfo taskInfo) {
+ return taskInfo.hasParentTask()
+ && taskInfo.isVisible
+ && taskInfo.realActivity != null // to filter out parents created by organizer
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW;
+ }
+
+ /**
+ * Clear all registered tasks
*/
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- if (!taskInfo.hasParentTask()) return;
+ @Override
+ public void clearTasks() {
+ mAnimationContextByTaskId.clear();
+ }
+ /**
+ * Reset transformations of the task that could have been applied by the animator
+ * @param taskInfo task to reset
+ * @param transaction a transaction to write the changes to
+ */
+ @Override
+ public void resetSurface(TaskInfo taskInfo, Transaction transaction) {
AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
if (context != null) {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
resetSurface(transaction, context);
- transaction.apply();
- mTransactionPool.release(transaction);
}
- mAnimationContextByTaskId.remove(taskInfo.taskId);
}
+ /**
+ * Reset all surface transformation that could have been introduced by the animator
+ * @param transaction to write changes to
+ */
@Override
- public void onStateChangeProgress(float progress) {
- if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
-
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- mBackgroundController.ensureBackground(transaction);
+ public void resetAllSurfaces(Transaction transaction) {
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ resetSurface(transaction, context);
+ }
+ }
+ @Override
+ public void applyAnimationProgress(float progress, Transaction transaction) {
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ if (context.mStageType == STAGE_TYPE_UNDEFINED) {
+ continue;
+ }
context.mCurrentCropRect.set(RECT_EVALUATOR
.evaluate(progress, context.mStartCropRect, context.mEndCropRect));
@@ -145,53 +239,25 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
.setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
}
-
- transaction.apply();
-
- mTransactionPool.release(transaction);
}
@Override
- public void onStateChangeFinished() {
- resetTransformations();
+ public void prepareStartTransaction(Transaction transaction) {
+ mUnfoldBackgroundController.ensureBackground(transaction);
+ mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction);
}
- /**
- * Called when split screen visibility changes
- * @param bothStagesVisible true if both stages of the split screen are visible
- */
- public void onSplitVisibilityChanged(boolean bothStagesVisible) {
- mBothStagesVisible = bothStagesVisible;
- if (!bothStagesVisible) {
- resetTransformations();
- }
+ @Override
+ public void prepareFinishTransaction(Transaction transaction) {
+ mUnfoldBackgroundController.removeBackground(transaction);
}
/**
- * Called when split screen stage bounds changed
- * @param bounds new bounds for this stage
+ * @return true if there are tasks to animate
*/
- public void onLayoutChanged(Rect bounds, @SplitPosition int splitPosition,
- boolean isLandscape) {
- mStageBounds.set(bounds);
-
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update(splitPosition, isLandscape);
- }
- }
-
- private void resetTransformations() {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
-
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- resetSurface(transaction, context);
- }
- mBackgroundController.removeBackground(transaction);
- transaction.apply();
-
- mTransactionPool.release(transaction);
+ @Override
+ public boolean hasActiveTasks() {
+ return mAnimationContextByTaskId.size() > 0;
}
private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
@@ -202,26 +268,24 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
private class AnimationContext {
final SurfaceControl mLeash;
+
final Rect mStartCropRect = new Rect();
final Rect mEndCropRect = new Rect();
final Rect mCurrentCropRect = new Rect();
- private @SplitPosition int mSplitPosition = SPLIT_POSITION_UNDEFINED;
- private boolean mIsLandscape = false;
+ @SplitScreen.StageType
+ int mStageType = STAGE_TYPE_UNDEFINED;
private AnimationContext(SurfaceControl leash) {
- this.mLeash = leash;
- update();
- }
-
- private void update(@SplitPosition int splitPosition, boolean isLandscape) {
- this.mSplitPosition = splitPosition;
- this.mIsLandscape = isLandscape;
+ mLeash = leash;
update();
}
private void update() {
- mStartCropRect.set(mStageBounds);
+ final Rect stageBounds = mStageType == STAGE_TYPE_MAIN
+ ? mMainStageBounds : mSideStageBounds;
+
+ mStartCropRect.set(stageBounds);
boolean taskbarExpanded = isTaskbarExpanded();
if (taskbarExpanded) {
@@ -239,7 +303,8 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
// Sides adjacent to split bar or task bar are not be animated.
Insets margins;
- if (mIsLandscape) { // Left and right splits.
+ final boolean isLandscape = mRootStageBounds.width() > mRootStageBounds.height();
+ if (isLandscape) { // Left and right splits.
margins = getLandscapeMargins(margin, taskbarExpanded);
} else { // Top and bottom splits.
margins = getPortraitMargins(margin, taskbarExpanded);
@@ -251,7 +316,9 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
int left = margin;
int right = margin;
int bottom = taskbarExpanded ? 0 : margin; // Taskbar margin.
- if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ final int splitPosition = mStageType == STAGE_TYPE_MAIN
+ ? mMainStagePosition : mSideStagePosition;
+ if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
right = 0; // Divider margin.
} else {
left = 0; // Divider margin.
@@ -262,7 +329,9 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
private Insets getPortraitMargins(int margin, boolean taskbarExpanded) {
int bottom = margin;
int top = margin;
- if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ final int splitPosition = mStageType == STAGE_TYPE_MAIN
+ ? mMainStagePosition : mSideStagePosition;
+ if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
bottom = 0; // Divider margin.
} else { // Bottom split.
top = 0; // Divider margin.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java
new file mode 100644
index 000000000000..e1e366301b46
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java
@@ -0,0 +1,117 @@
+/*
+ * 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.unfold.animation;
+
+import android.app.TaskInfo;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+
+/**
+ * Interface for classes that handle animations of tasks when folding or unfolding
+ * foldable devices.
+ */
+public interface UnfoldTaskAnimator {
+ /**
+ * Initializes the animator, this should be called once in the lifetime of the animator
+ */
+ default void init() {}
+
+ /**
+ * Starts the animator, it might start listening for some events from the system.
+ * Applying animation should be done only when animator is started.
+ * Animator could be started/stopped several times.
+ */
+ default void start() {}
+
+ /**
+ * Stops the animator, it could unsubscribe from system events.
+ */
+ default void stop() {}
+
+ /**
+ * If this method returns true then task updates will be propagated to
+ * the animator using the onTaskAppeared/Changed/Vanished callbacks.
+ * @return true if this task should be animated by this animator
+ */
+ default boolean isApplicableTask(TaskInfo taskInfo) {
+ return false;
+ }
+
+ /**
+ * Called whenever a task applicable to this animator appeared
+ * (isApplicableTask returns true for this task)
+ *
+ * @param taskInfo info of the appeared task
+ * @param leash surface of the task
+ */
+ default void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {}
+
+ /**
+ * Called whenever a task applicable to this animator changed
+ * @param taskInfo info of the changed task
+ */
+ default void onTaskChanged(TaskInfo taskInfo) {}
+
+ /**
+ * Called whenever a task applicable to this animator vanished
+ * @param taskInfo info of the vanished task
+ */
+ default void onTaskVanished(TaskInfo taskInfo) {}
+
+ /**
+ * @return true if there tasks that could be potentially animated
+ */
+ default boolean hasActiveTasks() {
+ return false;
+ }
+
+ /**
+ * Clears all registered tasks in the animator
+ */
+ default void clearTasks() {}
+
+ /**
+ * Apply task surfaces transformations based on the current unfold progress
+ * @param progress unfold transition progress
+ * @param transaction to write changes to
+ */
+ default void applyAnimationProgress(float progress, Transaction transaction) {}
+
+ /**
+ * Apply task surfaces transformations that should be set before starting the animation
+ * @param transaction to write changes to
+ */
+ default void prepareStartTransaction(Transaction transaction) {}
+
+ /**
+ * Apply task surfaces transformations that should be set after finishing the animation
+ * @param transaction to write changes to
+ */
+ default void prepareFinishTransaction(Transaction transaction) {}
+
+ /**
+ * Resets task surface to its initial transformation
+ * @param transaction to write changes to
+ */
+ default void resetSurface(TaskInfo taskInfo, Transaction transaction) {}
+
+ /**
+ * Resets all task surfaces to their initial transformations
+ * @param transaction to write changes to
+ */
+ default void resetAllSurfaces(Transaction transaction) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java
index 46e4299f99fa..4c868305dcdb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,20 +14,16 @@
* limitations under the License.
*/
-package com.android.wm.shell.stagesplit;
+package com.android.wm.shell.unfold.qualifier;
-/**
- * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
- */
-oneway interface ISplitScreenListener {
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
- /**
- * Called when the stage position changes.
- */
- void onStagePositionChanged(int stage, int position);
+import javax.inject.Qualifier;
- /**
- * Called when a task changes stages.
- */
- void onTaskStageChanged(int taskId, int stage, boolean visible);
-} \ No newline at end of file
+/**
+ * Indicates that this class is used for the shell unfold transition
+ */
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UnfoldShellTransition {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java
new file mode 100644
index 000000000000..4d2b3e6f899b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.unfold.qualifier;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * Indicates that this class is used for unfold transition implemented
+ * without using Shell transitions
+ */
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UnfoldTransition {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
index 603d05d78fc0..2cff1714aff6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
@@ -30,7 +30,7 @@ import androidx.annotation.Nullable;
public class GroupedRecentTaskInfo implements Parcelable {
public @NonNull ActivityManager.RecentTaskInfo mTaskInfo1;
public @Nullable ActivityManager.RecentTaskInfo mTaskInfo2;
- public @Nullable StagedSplitBounds mStagedSplitBounds;
+ public @Nullable SplitBounds mSplitBounds;
public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1) {
this(task1, null, null);
@@ -38,24 +38,24 @@ public class GroupedRecentTaskInfo implements Parcelable {
public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1,
@Nullable ActivityManager.RecentTaskInfo task2,
- @Nullable StagedSplitBounds stagedSplitBounds) {
+ @Nullable SplitBounds splitBounds) {
mTaskInfo1 = task1;
mTaskInfo2 = task2;
- mStagedSplitBounds = stagedSplitBounds;
+ mSplitBounds = splitBounds;
}
GroupedRecentTaskInfo(Parcel parcel) {
mTaskInfo1 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR);
mTaskInfo2 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR);
- mStagedSplitBounds = parcel.readTypedObject(StagedSplitBounds.CREATOR);
+ mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR);
}
@Override
public String toString() {
String taskString = "Task1: " + getTaskInfo(mTaskInfo1)
+ ", Task2: " + getTaskInfo(mTaskInfo2);
- if (mStagedSplitBounds != null) {
- taskString += ", SplitBounds: " + mStagedSplitBounds.toString();
+ if (mSplitBounds != null) {
+ taskString += ", SplitBounds: " + mSplitBounds.toString();
}
return taskString;
}
@@ -76,7 +76,7 @@ public class GroupedRecentTaskInfo implements Parcelable {
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeTypedObject(mTaskInfo1, flags);
parcel.writeTypedObject(mTaskInfo2, flags);
- parcel.writeTypedObject(mStagedSplitBounds, flags);
+ parcel.writeTypedObject(mSplitBounds, flags);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
index a0c84cc33ebd..e90389764af3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
@@ -25,7 +25,7 @@ import java.util.Objects;
* Container of various information needed to display split screen
* tasks/leashes/etc in Launcher
*/
-public class StagedSplitBounds implements Parcelable {
+public class SplitBounds implements Parcelable {
public final Rect leftTopBounds;
public final Rect rightBottomBounds;
/** This rect represents the actual gap between the two apps */
@@ -43,7 +43,7 @@ public class StagedSplitBounds implements Parcelable {
public final int leftTopTaskId;
public final int rightBottomTaskId;
- public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds,
+ public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds,
int leftTopTaskId, int rightBottomTaskId) {
this.leftTopBounds = leftTopBounds;
this.rightBottomBounds = rightBottomBounds;
@@ -66,7 +66,7 @@ public class StagedSplitBounds implements Parcelable {
topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
}
- public StagedSplitBounds(Parcel parcel) {
+ public SplitBounds(Parcel parcel) {
leftTopBounds = parcel.readTypedObject(Rect.CREATOR);
rightBottomBounds = parcel.readTypedObject(Rect.CREATOR);
visualDividerBounds = parcel.readTypedObject(Rect.CREATOR);
@@ -96,11 +96,11 @@ public class StagedSplitBounds implements Parcelable {
@Override
public boolean equals(Object obj) {
- if (!(obj instanceof StagedSplitBounds)) {
+ if (!(obj instanceof SplitBounds)) {
return false;
}
// Only need to check the base fields (the other fields are derived from these)
- final StagedSplitBounds other = (StagedSplitBounds) obj;
+ final SplitBounds other = (SplitBounds) obj;
return Objects.equals(leftTopBounds, other.leftTopBounds)
&& Objects.equals(rightBottomBounds, other.rightBottomBounds)
&& leftTopTaskId == other.leftTopTaskId
@@ -120,15 +120,15 @@ public class StagedSplitBounds implements Parcelable {
+ "AppsVertical? " + appsStackedVertically;
}
- public static final Creator<StagedSplitBounds> CREATOR = new Creator<StagedSplitBounds>() {
+ public static final Creator<SplitBounds> CREATOR = new Creator<SplitBounds>() {
@Override
- public StagedSplitBounds createFromParcel(Parcel in) {
- return new StagedSplitBounds(in);
+ public SplitBounds createFromParcel(Parcel in) {
+ return new SplitBounds(in);
}
@Override
- public StagedSplitBounds[] newArray(int size) {
- return new StagedSplitBounds[size];
+ public SplitBounds[] newArray(int size) {
+ return new SplitBounds[size];
}
};
}
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
new file mode 100644
index 000000000000..6d28d73996f0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -0,0 +1,174 @@
+/*
+ * 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.windowdecor;
+
+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 android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
+import android.content.Context;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+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;
+
+/**
+ * View model for the window decoration with a caption and shadows. Works with
+ * {@link CaptionWindowDecoration}.
+ */
+public class CaptionWindowDecorViewModel implements WindowDecorViewModel<CaptionWindowDecoration> {
+ private final ActivityTaskManager mActivityTaskManager;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final Context mContext;
+ private final Handler mMainHandler;
+ private final DisplayController mDisplayController;
+ private final SyncTransactionQueue mSyncQueue;
+
+ public CaptionWindowDecorViewModel(
+ Context context,
+ Handler mainHandler,
+ ShellTaskOrganizer taskOrganizer,
+ DisplayController displayController,
+ SyncTransactionQueue syncQueue) {
+ mContext = context;
+ mMainHandler = mainHandler;
+ mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
+ mTaskOrganizer = taskOrganizer;
+ mDisplayController = displayController;
+ mSyncQueue = syncQueue;
+ }
+
+ @Override
+ public CaptionWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface) {
+ final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ taskSurface,
+ mMainHandler,
+ mSyncQueue);
+ TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
+ CaptionTouchEventListener touchEventListener =
+ new CaptionTouchEventListener(taskInfo, taskPositioner);
+ windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
+ windowDecoration.setDragResizeCallback(taskPositioner);
+ onTaskInfoChanged(taskInfo, windowDecoration);
+ return windowDecoration;
+ }
+
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
+ decoration.relayout(taskInfo);
+
+ int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
+ decoration.setCaptionColor(statusBarColor);
+ }
+
+ private class CaptionTouchEventListener implements
+ View.OnClickListener, View.OnTouchListener {
+
+ private final int mTaskId;
+ private final WindowContainerToken mTaskToken;
+ private final DragResizeCallback mDragResizeCallback;
+
+ private int mDragPointerId = -1;
+
+ private CaptionTouchEventListener(
+ RunningTaskInfo taskInfo, DragResizeCallback dragResizeCallback) {
+ mTaskId = taskInfo.taskId;
+ mTaskToken = taskInfo.token;
+ mDragResizeCallback = dragResizeCallback;
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ if (id == R.id.close_window) {
+ mActivityTaskManager.removeTask(mTaskId);
+ } else if (id == R.id.maximize_window) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
+ ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM;
+ int displayWindowingMode =
+ taskInfo.configuration.windowConfiguration.getDisplayWindowingMode();
+ wct.setWindowingMode(mTaskToken,
+ targetWindowingMode == displayWindowingMode
+ ? WINDOWING_MODE_UNDEFINED : targetWindowingMode);
+ if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ wct.setBounds(mTaskToken, null);
+ }
+ mSyncQueue.queue(wct);
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent e) {
+ if (v.getId() != R.id.caption) {
+ return false;
+ }
+ handleEventForMove(e);
+
+ if (e.getAction() != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ if (taskInfo.isFocused) {
+ return false;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(mTaskToken, true /* onTop */);
+ mSyncQueue.queue(wct);
+ return true;
+ }
+
+ private void handleEventForMove(MotionEvent e) {
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mDragPointerId = e.getPointerId(0);
+ mDragResizeCallback.onDragResizeStart(
+ 0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
+ break;
+ case MotionEvent.ACTION_MOVE: {
+ int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ mDragResizeCallback.onDragResizeMove(
+ e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ break;
+ }
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ mDragResizeCallback.onDragResizeEnd(
+ e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ break;
+ }
+ }
+ }
+ }
+}
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
new file mode 100644
index 000000000000..572516d2f8c9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -0,0 +1,199 @@
+/*
+ * 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.windowdecor;
+
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.VectorDrawable;
+import android.os.Handler;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.window.WindowContainerTransaction;
+
+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;
+
+/**
+ * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
+ * {@link CaptionWindowDecorViewModel}. The caption bar contains maximize and close buttons.
+ *
+ * {@link CaptionWindowDecorViewModel} can change the color of the caption bar based on the foremost
+ * app's request through {@link #setCaptionColor(int)}, in which it changes the foreground color of
+ * caption buttons according to the luminance of the background.
+ *
+ * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
+ */
+public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
+ // The thickness of shadows of a window that has focus in DIP.
+ private static final int DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP = 20;
+ // The thickness of shadows of a window that doesn't have focus in DIP.
+ private static final int DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP = 5;
+
+ // Height of button (32dp) + 2 * margin (5dp each)
+ private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42;
+ private static final int RESIZE_HANDLE_IN_DIP = 30;
+
+ private static final Rect EMPTY_OUTSET = new Rect();
+ private static final Rect RESIZE_HANDLE_OUTSET = new Rect(
+ RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP);
+
+ private final Handler mHandler;
+ private final SyncTransactionQueue mSyncQueue;
+
+ private View.OnClickListener mOnCaptionButtonClickListener;
+ private View.OnTouchListener mOnCaptionTouchListener;
+ private DragResizeCallback mDragResizeCallback;
+
+ private DragResizeInputListener mDragResizeListener;
+
+ private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
+ new WindowDecoration.RelayoutResult<>();
+
+ CaptionWindowDecoration(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Handler handler,
+ SyncTransactionQueue syncQueue) {
+ super(context, displayController, taskOrganizer, taskInfo, taskSurface);
+
+ mHandler = handler;
+ mSyncQueue = syncQueue;
+ }
+
+ void setCaptionListeners(
+ View.OnClickListener onCaptionButtonClickListener,
+ View.OnTouchListener onCaptionTouchListener) {
+ mOnCaptionButtonClickListener = onCaptionButtonClickListener;
+ mOnCaptionTouchListener = onCaptionTouchListener;
+ }
+
+ void setDragResizeCallback(DragResizeCallback dragResizeCallback) {
+ mDragResizeCallback = dragResizeCallback;
+ }
+
+ void relayout(ActivityManager.RunningTaskInfo taskInfo) {
+ final int shadowRadiusDp = taskInfo.isFocused
+ ? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP;
+ final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode()
+ == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+ final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable;
+ final Rect outset = isDragResizeable ? RESIZE_HANDLE_OUTSET : EMPTY_OUTSET;
+
+ WindowDecorLinearLayout oldRootView = mResult.mRootView;
+ final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ relayout(taskInfo, R.layout.caption_window_decoration, oldRootView,
+ DECOR_CAPTION_HEIGHT_IN_DIP, outset, shadowRadiusDp, t, wct, mResult);
+ taskInfo = null; // Clear it just in case we use it accidentally
+
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+
+ mTaskOrganizer.applyTransaction(wct);
+ });
+
+ if (mResult.mRootView == null) {
+ // This means the task is hidden. Nothing is set up in this case including the
+ // decoration surface.
+ return;
+ }
+ if (oldRootView != mResult.mRootView) {
+ setupRootView();
+ }
+
+ if (!isDragResizeable) {
+ closeDragResizeListener();
+ return;
+ }
+
+ if (oldDecorationSurface != mDecorationContainerSurface) {
+ closeDragResizeListener();
+ mDragResizeListener = new DragResizeInputListener(
+ mContext,
+ mHandler,
+ mDisplay.getDisplayId(),
+ mDecorationContainerSurface,
+ mDragResizeCallback);
+ }
+
+ mDragResizeListener.setGeometry(
+ mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP));
+ }
+
+ /**
+ * Sets up listeners when a new root view is created.
+ */
+ private void setupRootView() {
+ View caption = mResult.mRootView.findViewById(R.id.caption);
+
+ caption.setOnTouchListener(mOnCaptionTouchListener);
+ View maximize = caption.findViewById(R.id.maximize_window);
+ maximize.setOnClickListener(mOnCaptionButtonClickListener);
+ View close = caption.findViewById(R.id.close_window);
+ close.setOnClickListener(mOnCaptionButtonClickListener);
+ }
+
+ void setCaptionColor(int captionColor) {
+ if (mResult.mRootView == null) {
+ return;
+ }
+
+ View caption = mResult.mRootView.findViewById(R.id.caption);
+ GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
+ captionDrawable.setColor(captionColor);
+
+ int buttonTintColorRes =
+ Color.valueOf(captionColor).luminance() < 0.5
+ ? R.color.decor_button_light_color
+ : R.color.decor_button_dark_color;
+ ColorStateList buttonTintColor =
+ caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
+ View maximize = caption.findViewById(R.id.maximize_window);
+ VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
+ maximizeBackground.setTintList(buttonTintColor);
+
+ View close = caption.findViewById(R.id.close_window);
+ VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
+ closeBackground.setTintList(buttonTintColor);
+ }
+
+ private void closeDragResizeListener() {
+ if (mDragResizeListener == null) {
+ return;
+ }
+ mDragResizeListener.close();
+ mDragResizeListener = null;
+ }
+
+ @Override
+ public void close() {
+ closeDragResizeListener();
+ super.close();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
new file mode 100644
index 000000000000..ee160a15df19
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+/**
+ * Callback called when receiving drag-resize or drag-move related input events.
+ */
+public interface DragResizeCallback {
+ /**
+ * Called when a drag resize starts.
+ *
+ * @param ctrlType {@link TaskPositioner.CtrlType} indicating the direction of resizing, use
+ * {@code 0} to indicate it's a move
+ * @param x x coordinate in window decoration coordinate system where the drag resize starts
+ * @param y y coordinate in window decoration coordinate system where the drag resize starts
+ */
+ void onDragResizeStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
+
+ /**
+ * Called when the pointer moves during a drag resize.
+ * @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
+ */
+ void onDragResizeMove(float x, float y);
+
+ /**
+ * Called when a drag resize 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
+ */
+ void onDragResizeEnd(float x, float y);
+}
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
new file mode 100644
index 000000000000..c6bbb027c8e6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -0,0 +1,288 @@
+/*
+ * 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.windowdecor;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.input.InputManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.IWindowSession;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+import android.view.PointerIcon;
+import android.view.SurfaceControl;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.view.BaseIWindow;
+
+/**
+ * An input event listener registered to InputDispatcher to receive input events on task edges and
+ * convert them to drag resize requests.
+ */
+class DragResizeInputListener implements AutoCloseable {
+ private static final String TAG = "DragResizeInputListener";
+
+ private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
+ private final Handler mHandler;
+ private final InputManager mInputManager;
+
+ private final int mDisplayId;
+ private final BaseIWindow mFakeWindow;
+ private final IBinder mFocusGrantToken;
+ private final SurfaceControl mDecorationSurface;
+ private final InputChannel mInputChannel;
+ private final TaskResizeInputEventReceiver mInputEventReceiver;
+ private final com.android.wm.shell.windowdecor.DragResizeCallback mCallback;
+
+ private int mWidth;
+ private int mHeight;
+ private int mResizeHandleThickness;
+
+ private int mDragPointerId = -1;
+
+ DragResizeInputListener(
+ Context context,
+ Handler handler,
+ int displayId,
+ SurfaceControl decorationSurface,
+ DragResizeCallback callback) {
+ mInputManager = context.getSystemService(InputManager.class);
+ mHandler = handler;
+ mDisplayId = displayId;
+ 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.
+ mFakeWindow = new BaseIWindow();
+ mFakeWindow.setSession(mWindowSession);
+ mFocusGrantToken = new Binder();
+ mInputChannel = new InputChannel();
+ try {
+ mWindowSession.grantInputChannel(
+ mDisplayId,
+ new SurfaceControl(mDecorationSurface, TAG),
+ mFakeWindow,
+ null /* hostInputToken */,
+ FLAG_NOT_FOCUSABLE,
+ PRIVATE_FLAG_TRUSTED_OVERLAY,
+ TYPE_APPLICATION,
+ mFocusGrantToken,
+ TAG + " of " + decorationSurface.toString(),
+ mInputChannel);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ mInputEventReceiver = new TaskResizeInputEventReceiver(mInputChannel, mHandler.getLooper());
+ mCallback = callback;
+ }
+
+ /**
+ * 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.
+ *
+ * @param width The width of the drag resize handler in pixels, including resize handle
+ * thickness. That is task width + 2 * resize handle thickness.
+ * @param height The height of the drag resize handler in pixels, including resize handle
+ * thickness. That is task height + 2 * resize handle thickness.
+ * @param resizeHandleThickness The thickness of the resize handle in pixels.
+ */
+ void setGeometry(int width, int height, int resizeHandleThickness) {
+ if (mWidth == width && mHeight == height
+ && mResizeHandleThickness == resizeHandleThickness) {
+ return;
+ }
+
+ mWidth = width;
+ mHeight = height;
+ mResizeHandleThickness = resizeHandleThickness;
+
+ Region touchRegion = new Region();
+ final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
+ touchRegion.union(topInputBounds);
+
+ final Rect leftInputBounds = new Rect(0, mResizeHandleThickness,
+ mResizeHandleThickness, mHeight - mResizeHandleThickness);
+ touchRegion.union(leftInputBounds);
+
+ final Rect rightInputBounds = new Rect(
+ mWidth - mResizeHandleThickness, mResizeHandleThickness,
+ mWidth, mHeight - mResizeHandleThickness);
+ touchRegion.union(rightInputBounds);
+
+ final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness,
+ mWidth, mHeight);
+ touchRegion.union(bottomInputBounds);
+
+ try {
+ mWindowSession.updateInputChannel(
+ mInputChannel.getToken(),
+ mDisplayId,
+ new SurfaceControl(
+ mDecorationSurface, "DragResizeInputListener#setTouchRegion"),
+ FLAG_NOT_FOCUSABLE,
+ PRIVATE_FLAG_TRUSTED_OVERLAY,
+ touchRegion);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ // This marks all relevant components have handled the previous resize event and can take
+ // the next one now.
+ mInputEventReceiver.onHandledLastResizeEvent();
+ }
+
+ @Override
+ public void close() {
+ mInputChannel.dispose();
+ try {
+ mWindowSession.remove(mFakeWindow);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ private class TaskResizeInputEventReceiver extends InputEventReceiver {
+ private boolean mWaitingForLastResizeEventHandled;
+
+ private TaskResizeInputEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ private void onHandledLastResizeEvent() {
+ mWaitingForLastResizeEventHandled = false;
+ consumeBatchedInputEvents(-1);
+ }
+
+ @Override
+ public void onBatchedInputEventPending(int source) {
+ // InputEventReceiver keeps continuous move events in a batched event until explicitly
+ // consuming it or an incompatible event shows up (likely an up event in this case). We
+ // continue to keep move events in the next batched event until we receive a geometry
+ // update so that we don't put too much pressure on the framework with excessive number
+ // of input events if it can't handle them fast enough. It's more responsive to always
+ // resize the task to the latest received coordinates.
+ if (!mWaitingForLastResizeEventHandled) {
+ consumeBatchedInputEvents(-1);
+ }
+ }
+
+ @Override
+ public void onInputEvent(InputEvent inputEvent) {
+ finishInputEvent(inputEvent, handleInputEvent(inputEvent));
+ }
+
+ private boolean handleInputEvent(InputEvent inputEvent) {
+ if (!(inputEvent instanceof MotionEvent)) {
+ return false;
+ }
+
+ MotionEvent e = (MotionEvent) inputEvent;
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ mDragPointerId = e.getPointerId(0);
+ mCallback.onDragResizeStart(
+ calculateCtrlType(e.getX(0), e.getY(0)), e.getRawX(0), e.getRawY(0));
+ mWaitingForLastResizeEventHandled = false;
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ int dragPointerIndex = e.findPointerIndex(mDragPointerId);
+ mCallback.onDragResizeMove(
+ e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ mWaitingForLastResizeEventHandled = true;
+ break;
+ }
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ int dragPointerIndex = e.findPointerIndex(mDragPointerId);
+ mCallback.onDragResizeEnd(
+ e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ mWaitingForLastResizeEventHandled = false;
+ mDragPointerId = -1;
+ break;
+ }
+ case MotionEvent.ACTION_HOVER_ENTER:
+ case MotionEvent.ACTION_HOVER_MOVE: {
+ updateCursorType(e.getXCursorPosition(), e.getYCursorPosition());
+ break;
+ }
+ case MotionEvent.ACTION_HOVER_EXIT:
+ mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
+ break;
+ }
+ return true;
+ }
+
+ @TaskPositioner.CtrlType
+ private int calculateCtrlType(float x, float y) {
+ int ctrlType = 0;
+ if (x < mResizeHandleThickness) {
+ ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
+ }
+ if (x > mWidth - mResizeHandleThickness) {
+ ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT;
+ }
+ if (y < mResizeHandleThickness) {
+ ctrlType |= TaskPositioner.CTRL_TYPE_TOP;
+ }
+ if (y > mHeight - mResizeHandleThickness) {
+ ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM;
+ }
+ return ctrlType;
+ }
+
+ private void updateCursorType(float x, float y) {
+ @TaskPositioner.CtrlType int ctrlType = calculateCtrlType(x, y);
+
+ int cursorType = PointerIcon.TYPE_DEFAULT;
+ switch (ctrlType) {
+ case TaskPositioner.CTRL_TYPE_LEFT:
+ case TaskPositioner.CTRL_TYPE_RIGHT:
+ cursorType = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
+ break;
+ case TaskPositioner.CTRL_TYPE_TOP:
+ case TaskPositioner.CTRL_TYPE_BOTTOM:
+ cursorType = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
+ break;
+ case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_TOP:
+ case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_BOTTOM:
+ cursorType = PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
+ break;
+ case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_BOTTOM:
+ case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_TOP:
+ cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
+ break;
+ }
+ mInputManager.setPointerIconType(cursorType);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskFocusStateConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskFocusStateConsumer.java
new file mode 100644
index 000000000000..1c61802bbd5c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskFocusStateConsumer.java
@@ -0,0 +1,21 @@
+/*
+ * 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.windowdecor;
+
+interface TaskFocusStateConsumer {
+ void setTaskFocusState(boolean focused);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
new file mode 100644
index 000000000000..280569b05d87
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import android.annotation.IntDef;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+
+class TaskPositioner implements DragResizeCallback {
+
+ @IntDef({CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
+ @interface CtrlType {}
+
+ static final int CTRL_TYPE_LEFT = 1;
+ static final int CTRL_TYPE_RIGHT = 2;
+ static final int CTRL_TYPE_TOP = 4;
+ static final int CTRL_TYPE_BOTTOM = 8;
+
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final WindowDecoration mWindowDecoration;
+
+ private final Rect mTaskBoundsAtDragStart = new Rect();
+ private final PointF mResizeStartPoint = new PointF();
+ private final Rect mResizeTaskBounds = new Rect();
+
+ private int mCtrlType;
+
+ TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
+ mTaskOrganizer = taskOrganizer;
+ mWindowDecoration = windowDecoration;
+ }
+
+ @Override
+ public void onDragResizeStart(int ctrlType, float x, float y) {
+ mCtrlType = ctrlType;
+
+ mTaskBoundsAtDragStart.set(
+ mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
+ mResizeStartPoint.set(x, y);
+ }
+
+ @Override
+ public void onDragResizeMove(float x, float y) {
+ changeBounds(x, y);
+ }
+
+ @Override
+ public void onDragResizeEnd(float x, float y) {
+ changeBounds(x, y);
+
+ mCtrlType = 0;
+ mTaskBoundsAtDragStart.setEmpty();
+ mResizeStartPoint.set(0, 0);
+ }
+
+ private void changeBounds(float x, float y) {
+ float deltaX = x - mResizeStartPoint.x;
+ mResizeTaskBounds.set(mTaskBoundsAtDragStart);
+ if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
+ mResizeTaskBounds.left += deltaX;
+ }
+ if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
+ mResizeTaskBounds.right += deltaX;
+ }
+ float deltaY = y - mResizeStartPoint.y;
+ if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
+ mResizeTaskBounds.top += deltaY;
+ }
+ if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
+ mResizeTaskBounds.bottom += deltaY;
+ }
+ if (mCtrlType == 0) {
+ mResizeTaskBounds.offset((int) deltaX, (int) deltaY);
+ }
+
+ if (!mResizeTaskBounds.isEmpty()) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java
new file mode 100644
index 000000000000..6d8001a2f92b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java
@@ -0,0 +1,72 @@
+/*
+ * 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.windowdecor;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+
+/**
+ * A {@link LinearLayout} that takes an additional task focused drawable state. The new state is
+ * used to select the correct background color for views in the window decoration.
+ */
+public class WindowDecorLinearLayout extends LinearLayout implements TaskFocusStateConsumer {
+ private static final int[] TASK_FOCUSED_STATE = { R.attr.state_task_focused };
+
+ private boolean mIsTaskFocused;
+
+ public WindowDecorLinearLayout(Context context) {
+ super(context);
+ }
+
+ public WindowDecorLinearLayout(Context context,
+ @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public WindowDecorLinearLayout(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public WindowDecorLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void setTaskFocusState(boolean focused) {
+ mIsTaskFocused = focused;
+
+ refreshDrawableState();
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ if (!mIsTaskFocused) {
+ return super.onCreateDrawableState(extraSpace);
+ }
+
+ final int[] states = super.onCreateDrawableState(extraSpace + 1);
+ mergeDrawableStates(states, TASK_FOCUSED_STATE);
+ return states;
+ }
+}
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
new file mode 100644
index 000000000000..6f9ceff722ac
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import android.app.ActivityManager;
+import android.view.SurfaceControl;
+
+/**
+ * The interface used by some {@link com.android.wm.shell.ShellTaskOrganizer.TaskListener} to help
+ * customize {@link WindowDecoration}. Its implementations are responsible to interpret user's
+ * interactions with UI widgets in window decorations and send corresponding requests to system
+ * servers.
+ *
+ * @param <T> The actual decoration type
+ */
+public interface WindowDecorViewModel<T extends AutoCloseable> {
+
+ /**
+ * Creates a window decoration for the given task.
+ *
+ * @param taskInfo the initial task info of the task
+ * @param taskSurface the surface of the task
+ * @return the window decoration object
+ */
+ T createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface);
+
+ /**
+ * Notifies a task info update on the given task, with the window decoration created previously
+ * for this task by {@link #createWindowDecoration}.
+ *
+ * @param taskInfo the new task info of the task
+ * @param windowDecoration the window decoration created for the task
+ */
+ void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo, T windowDecoration);
+}
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
new file mode 100644
index 000000000000..0c5022832162
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -0,0 +1,270 @@
+/*
+ * 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.windowdecor;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.InsetsState;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+
+/**
+ * Manages a container surface and a windowless window to show window decoration. Responsible to
+ * update window decoration window state and layout parameters on task info changes and so that
+ * window decoration is in correct state and bounds.
+ *
+ * The container surface is a child of the task display area in the same display, so that window
+ * decorations can be drawn out of the task bounds and receive input events from out of the task
+ * bounds to support drag resizing.
+ *
+ * The windowless window that hosts window decoration is positioned in front of all activities, to
+ * allow the foreground activity to draw its own background behind window decorations, such as
+ * the window captions.
+ *
+ * @param <T> The type of the root view
+ */
+public class WindowDecoration<T extends View & TaskFocusStateConsumer> implements AutoCloseable {
+ private static final int[] CAPTION_INSETS_TYPES = { InsetsState.ITYPE_CAPTION_BAR };
+
+ /**
+ * System-wide context. Only used to create context with overridden configurations.
+ */
+ final Context mContext;
+ final DisplayController mDisplayController;
+ final ShellTaskOrganizer mTaskOrganizer;
+
+ RunningTaskInfo mTaskInfo;
+ final SurfaceControl mTaskSurface;
+
+ Display mDisplay;
+ Context mDecorWindowContext;
+ SurfaceControl mDecorationContainerSurface;
+ SurfaceControl mTaskBackgroundSurface;
+
+ private CaptionWindowManager mCaptionWindowManager;
+ private SurfaceControlViewHost mViewHost;
+
+ private final Rect mCaptionInsetsRect = new Rect();
+ private final Rect mTaskSurfaceCrop = new Rect();
+ private final float[] mTmpColor = new float[3];
+
+ WindowDecoration(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface) {
+ mContext = context;
+ mDisplayController = displayController;
+ mTaskOrganizer = taskOrganizer;
+ mTaskInfo = taskInfo;
+ mTaskSurface = taskSurface;
+
+ mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
+ mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration());
+
+ // Put caption under task surface because ViewRootImpl sets the destination frame of
+ // windowless window layers and BLASTBufferQueue#update() doesn't support offset.
+ mCaptionWindowManager =
+ new CaptionWindowManager(mTaskInfo.getConfiguration(), mTaskSurface);
+ }
+
+ void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp,
+ Rect outsetsDp, float shadowRadiusDp, SurfaceControl.Transaction t,
+ WindowContainerTransaction wct, RelayoutResult<T> outResult) {
+ outResult.reset();
+
+ final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
+ if (taskInfo != null) {
+ mTaskInfo = taskInfo;
+ }
+
+ if (!mTaskInfo.isVisible) {
+ close();
+ t.hide(mTaskSurface);
+ return;
+ }
+
+ if (rootView == null && layoutResId == 0) {
+ throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");
+ }
+
+ outResult.mRootView = rootView;
+ rootView = null; // Clear it just in case we use it accidentally
+ final Configuration taskConfig = mTaskInfo.getConfiguration();
+ if (oldTaskConfig.densityDpi != taskConfig.densityDpi
+ || mDisplay.getDisplayId() != mTaskInfo.displayId) {
+ close();
+
+ mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
+ mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
+ if (layoutResId != 0) {
+ outResult.mRootView =
+ (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+ }
+ }
+
+ if (outResult.mRootView == null) {
+ outResult.mRootView =
+ (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+ }
+
+ // DecorationContainerSurface
+ if (mDecorationContainerSurface == null) {
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder();
+ mDecorationContainerSurface = builder
+ .setName("Decor container of Task=" + mTaskInfo.taskId)
+ .setContainerLayer()
+ .setParent(mTaskSurface)
+ .build();
+
+ t.setTrustedOverlay(mDecorationContainerSurface, true);
+ }
+
+ final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
+ outResult.mDensity = taskConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ final int decorContainerOffsetX = -(int) (outsetsDp.left * outResult.mDensity);
+ final int decorContainerOffsetY = -(int) (outsetsDp.top * outResult.mDensity);
+ outResult.mWidth = taskBounds.width()
+ + (int) (outsetsDp.right * outResult.mDensity)
+ - decorContainerOffsetX;
+ outResult.mHeight = taskBounds.height()
+ + (int) (outsetsDp.bottom * outResult.mDensity)
+ - decorContainerOffsetY;
+ t.setPosition(mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
+ .setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
+ .setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1)
+ .show(mDecorationContainerSurface);
+
+ // TaskBackgroundSurface
+ if (mTaskBackgroundSurface == null) {
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder();
+ mTaskBackgroundSurface = builder
+ .setName("Background of Task=" + mTaskInfo.taskId)
+ .setEffectLayer()
+ .setParent(mTaskSurface)
+ .build();
+ }
+
+ float shadowRadius = outResult.mDensity * shadowRadiusDp;
+ int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
+ mTmpColor[0] = Color.red(backgroundColorInt);
+ mTmpColor[1] = Color.green(backgroundColorInt);
+ mTmpColor[2] = Color.blue(backgroundColorInt);
+ t.setCrop(mTaskBackgroundSurface, taskBounds)
+ .setShadowRadius(mTaskBackgroundSurface, shadowRadius)
+ .setColor(mTaskBackgroundSurface, mTmpColor);
+
+ // Caption view
+ mCaptionWindowManager.setConfiguration(taskConfig);
+ final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity);
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(taskBounds.width(), captionHeight,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
+ lp.setTrustedOverlay();
+ if (mViewHost == null) {
+ mViewHost = new SurfaceControlViewHost(mDecorWindowContext, mDisplay,
+ mCaptionWindowManager, true);
+ mViewHost.setView(outResult.mRootView, lp);
+ } else {
+ mViewHost.relayout(lp);
+ }
+
+ if (ViewRootImpl.CAPTION_ON_SHELL) {
+ outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
+
+ // Caption insets
+ mCaptionInsetsRect.set(taskBounds);
+ mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight;
+ wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES);
+ } else {
+ outResult.mRootView.setVisibility(View.GONE);
+ }
+
+ // Task surface itself
+ Point taskPosition = mTaskInfo.positionInParent;
+ mTaskSurfaceCrop.set(
+ decorContainerOffsetX,
+ decorContainerOffsetY,
+ outResult.mWidth + decorContainerOffsetX,
+ outResult.mHeight + decorContainerOffsetY);
+ t.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
+ .setCrop(mTaskSurface, mTaskSurfaceCrop)
+ .show(mTaskSurface);
+ }
+
+ @Override
+ public void close() {
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+
+ if (mDecorationContainerSurface != null) {
+ mDecorationContainerSurface.release();
+ mDecorationContainerSurface = null;
+ }
+
+ if (mTaskBackgroundSurface != null) {
+ mTaskBackgroundSurface.release();
+ mTaskBackgroundSurface = null;
+ }
+ }
+
+ static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
+ int mWidth;
+ int mHeight;
+ float mDensity;
+ T mRootView;
+
+ void reset() {
+ mWidth = 0;
+ mHeight = 0;
+ mDensity = 0;
+ mRootView = null;
+ }
+ }
+
+ private static class CaptionWindowManager extends WindowlessWindowManager {
+ CaptionWindowManager(Configuration config, SurfaceControl rootSurface) {
+ super(config, rootSurface, null /* hostInputToken */);
+ }
+
+ @Override
+ public void setConfiguration(Configuration configuration) {
+ super.setConfiguration(configuration);
+ }
+ }
+}
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/CommonAssertions.kt
index cb478c84c2b7..cba396a82a87 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/CommonAssertions.kt
@@ -43,6 +43,84 @@ fun FlickerTestParameter.appPairsDividerBecomesVisible() {
}
}
+fun FlickerTestParameter.splitScreenDividerBecomesVisible() {
+ layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+}
+
+fun FlickerTestParameter.layerBecomesVisible(
+ component: FlickerComponentName
+) {
+ assertLayers {
+ this.isInvisible(component)
+ .then()
+ .isVisible(component)
+ }
+}
+
+fun FlickerTestParameter.layerIsVisibleAtEnd(
+ component: FlickerComponentName
+) {
+ assertLayersEnd {
+ this.isVisible(component)
+ }
+}
+
+fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
+ rotation: Int,
+ component: FlickerComponentName,
+ splitLeftTop: Boolean
+) {
+ assertLayers {
+ val dividerRegion = this.last().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
+ this.isInvisible(component)
+ .then()
+ .invoke("splitAppLayerBoundsBecomesVisible") {
+ it.visibleRegion(component).overlaps(
+ if (splitLeftTop) {
+ getSplitLeftTopRegion(dividerRegion, rotation)
+ } else {
+ getSplitRightBottomRegion(dividerRegion, rotation)
+ }
+ )
+ }
+ }
+}
+
+fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
+ rotation: Int,
+ component: FlickerComponentName,
+ splitLeftTop: Boolean
+) {
+ assertLayersEnd {
+ val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
+ visibleRegion(component).overlaps(
+ if (splitLeftTop) {
+ getSplitLeftTopRegion(dividerRegion, rotation)
+ } else {
+ getSplitRightBottomRegion(dividerRegion, rotation)
+ }
+ )
+ }
+}
+
+fun FlickerTestParameter.appWindowBecomesVisible(
+ component: FlickerComponentName
+) {
+ assertWm {
+ this.isAppWindowInvisible(component)
+ .then()
+ .isAppWindowVisible(component)
+ }
+}
+
+fun FlickerTestParameter.appWindowIsVisibleAtEnd(
+ component: FlickerComponentName
+) {
+ assertWmEnd {
+ this.isAppWindowVisible(component)
+ }
+}
+
fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() {
assertLayersEnd {
this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
@@ -118,21 +196,53 @@ fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd(
fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Region.from(0, 0, displayBounds.bounds.right,
- dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset)
+ Region.from(
+ 0, 0, displayBounds.bounds.right,
+ dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
+ )
} else {
- Region.from(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
- displayBounds.bounds.bottom)
+ Region.from(
+ 0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
+ displayBounds.bounds.bottom
+ )
}
}
fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Region.from(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.bounds.right, displayBounds.bounds.bottom)
+ Region.from(
+ 0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
+ displayBounds.bounds.right, displayBounds.bounds.bottom
+ )
} else {
- Region.from(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
- displayBounds.bounds.right, displayBounds.bounds.bottom)
+ Region.from(
+ dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
+ displayBounds.bounds.right, displayBounds.bounds.bottom
+ )
}
-} \ No newline at end of file
+}
+
+fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ return if (displayBounds.width > displayBounds.height) {
+ Region.from(0, 0, dividerRegion.bounds.left, displayBounds.bounds.bottom)
+ } else {
+ Region.from(0, 0, displayBounds.bounds.right, dividerRegion.bounds.top)
+ }
+}
+
+fun getSplitRightBottomRegion(dividerRegion: Region, rotation: Int): Region {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ return if (displayBounds.width > displayBounds.height) {
+ Region.from(
+ dividerRegion.bounds.right, 0, displayBounds.bounds.right,
+ displayBounds.bounds.bottom
+ )
+ } else {
+ Region.from(
+ 0, dividerRegion.bounds.bottom, displayBounds.bounds.right,
+ displayBounds.bounds.bottom
+ )
+ }
+}
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/CommonConstants.kt
index 40891f36a5da..f56eb6e783aa 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/CommonConstants.kt
@@ -21,4 +21,5 @@ import com.android.server.wm.traces.common.FlickerComponentName
const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
val APP_PAIR_SPLIT_DIVIDER_COMPONENT = FlickerComponentName("", "AppPairSplitDivider#")
-val DOCKED_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider#") \ No newline at end of file
+val DOCKED_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider#")
+val SPLIT_SCREEN_DIVIDER_COMPONENT = FlickerComponentName("", "StageCoordinatorSplitDivider#")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
deleted file mode 100644
index c9cab39b7d8b..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
+++ /dev/null
@@ -1,115 +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.flicker.apppairs
-
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import org.junit.After
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launch app from launcher. When the device doesn't support non-resizable in multi window
- * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs should not pair
- * non-resizable apps.
- *
- * To run this test: `atest WMShellFlickerTests:AppPairsTestCannotPairNonResizeableApps`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class AppPairsTestCannotPairNonResizeableApps(
- testSpec: FlickerTestParameter
-) : AppPairsTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- nonResizeableApp?.launchViaIntent(wmHelper)
- // TODO pair apps through normal UX flow
- executeShellCommand(
- composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true))
- nonResizeableApp?.run { wmHelper.waitForFullScreenApp(nonResizeableApp.component) }
- }
- }
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, -1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Ignore
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Ignore
- @Test
- fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd()
-
- @Ignore
- @Test
- fun onlyResizeableAppWindowVisible() {
- val nonResizeableApp = nonResizeableApp
- require(nonResizeableApp != null) {
- "Non resizeable app not initialized"
- }
- testSpec.assertWmEnd {
- isAppWindowVisible(nonResizeableApp.component)
- isAppWindowInvisible(primaryApp.component)
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = AppPairsHelper.TEST_REPETITIONS)
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
deleted file mode 100644
index 60c32c99d1ff..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ /dev/null
@@ -1,104 +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.flicker.apppairs
-
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
-import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launch app from launcher.
- * To run this test: `atest WMShellFlickerTests:AppPairsTestPairPrimaryAndSecondaryApps`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class AppPairsTestPairPrimaryAndSecondaryApps(
- testSpec: FlickerTestParameter
-) : AppPairsTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- // TODO pair apps through normal UX flow
- executeShellCommand(
- composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
- waitAppsShown(primaryApp, secondaryApp)
- }
- }
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Ignore
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @Ignore
- @Test
- fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
-
- @Ignore
- @Test
- fun bothAppWindowsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(primaryApp.component)
- isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Ignore
- @Test
- fun appsEndingBounds() {
- testSpec.assertLayersEnd {
- val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(primaryApp.component)
- .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion))
- visibleRegion(secondaryApp.component)
- .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion))
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = AppPairsHelper.TEST_REPETITIONS)
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
deleted file mode 100644
index 24869a802167..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
+++ /dev/null
@@ -1,128 +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.flicker.apppairs
-
-import android.view.Display
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import org.junit.After
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launch app from launcher. When the device supports non-resizable in multi window
- * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs can pair
- * non-resizable apps.
- *
- * To run this test: `atest WMShellFlickerTests:AppPairsTestSupportPairNonResizeableApps`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class AppPairsTestSupportPairNonResizeableApps(
- testSpec: FlickerTestParameter
-) : AppPairsTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- nonResizeableApp?.launchViaIntent(wmHelper)
- // TODO pair apps through normal UX flow
- executeShellCommand(
- composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true))
- val waitConditions = mutableListOf(
- WindowManagerConditionsFactory.isWindowVisible(primaryApp.component),
- WindowManagerConditionsFactory.isLayerVisible(primaryApp.component),
- WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
-
- nonResizeableApp?.let {
- waitConditions.add(
- WindowManagerConditionsFactory.isWindowVisible(nonResizeableApp.component))
- waitConditions.add(
- WindowManagerConditionsFactory.isLayerVisible(nonResizeableApp.component))
- }
- wmHelper.waitFor(*waitConditions.toTypedArray())
- }
- }
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, 1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Ignore
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @Ignore
- @Test
- fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
-
- @Ignore
- @Test
- fun bothAppWindowVisible() {
- val nonResizeableApp = nonResizeableApp
- require(nonResizeableApp != null) {
- "Non resizeable app not initialized"
- }
- testSpec.assertWmEnd {
- isAppWindowVisible(nonResizeableApp.component)
- isAppWindowVisible(primaryApp.component)
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = AppPairsHelper.TEST_REPETITIONS)
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
deleted file mode 100644
index 007415d19860..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ /dev/null
@@ -1,121 +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.flicker.apppairs
-
-import android.os.SystemClock
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
-import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launch app from launcher.
- * To run this test: `atest WMShellFlickerTests:AppPairsTestUnpairPrimaryAndSecondaryApps`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class AppPairsTestUnpairPrimaryAndSecondaryApps(
- testSpec: FlickerTestParameter
-) : AppPairsTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- setup {
- eachRun {
- executeShellCommand(
- composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
- waitAppsShown(primaryApp, secondaryApp)
- }
- }
- transitions {
- // TODO pair apps through normal UX flow
- executeShellCommand(
- composePairsCommand(primaryTaskId, secondaryTaskId, pair = false))
- SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
- }
- }
-
- @Ignore
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @Ignore
- @Test
- fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd()
-
- @Ignore
- @Test
- fun bothAppWindowsInvisible() {
- testSpec.assertWmEnd {
- isAppWindowInvisible(primaryApp.component)
- isAppWindowInvisible(secondaryApp.component)
- }
- }
-
- @Ignore
- @Test
- fun appsStartingBounds() {
- testSpec.assertLayersStart {
- val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(primaryApp.component)
- .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion))
- visibleRegion(secondaryApp.component)
- .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion))
- }
- }
-
- @Ignore
- @Test
- fun appsEndingBounds() {
- testSpec.assertLayersEnd {
- notContains(primaryApp.component)
- notContains(secondaryApp.component)
- }
- }
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = AppPairsHelper.TEST_REPETITIONS)
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
deleted file mode 100644
index 3e17948b4a84..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
+++ /dev/null
@@ -1,178 +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.flicker.apppairs
-
-import android.app.Instrumentation
-import android.content.Context
-import android.system.helpers.ActivityHelper
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.FlickerBuilderProvider
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
-import com.android.wm.shell.flicker.helpers.BaseAppHelper
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import com.android.wm.shell.flicker.testapp.Components
-import org.junit.After
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-
-abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) {
- protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- protected val context: Context = instrumentation.context
- protected val activityHelper = ActivityHelper.getInstance()
- protected val appPairsHelper = AppPairsHelper(instrumentation,
- Components.SplitScreenActivity.LABEL,
- Components.SplitScreenActivity.COMPONENT.toFlickerComponent())
-
- protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation)
- protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
- protected open val nonResizeableApp: SplitScreenHelper? =
- SplitScreenHelper.getNonResizeable(instrumentation)
- protected var primaryTaskId = ""
- protected var secondaryTaskId = ""
- protected var nonResizeableTaskId = ""
- private var prevDevEnableNonResizableMultiWindow = 0
-
- @Before
- open fun setup() {
- prevDevEnableNonResizableMultiWindow = getDevEnableNonResizableMultiWindow(context)
- if (prevDevEnableNonResizableMultiWindow != 0) {
- // Turn off the development option
- setDevEnableNonResizableMultiWindow(context, 0)
- }
- }
-
- @After
- open fun teardown() {
- setDevEnableNonResizableMultiWindow(context, prevDevEnableNonResizableMultiWindow)
- }
-
- @FlickerBuilderProvider
- fun buildFlicker(): FlickerBuilder {
- return FlickerBuilder(instrumentation).apply {
- transition(this)
- }
- }
-
- internal open val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
- eachRun {
- this.setRotation(testSpec.startRotation)
- primaryApp.launchViaIntent(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- nonResizeableApp?.launchViaIntent(wmHelper)
- updateTasksId()
- }
- }
- teardown {
- eachRun {
- executeShellCommand(composePairsCommand(
- primaryTaskId, secondaryTaskId, pair = false))
- executeShellCommand(composePairsCommand(
- primaryTaskId, nonResizeableTaskId, pair = false))
- primaryApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
- nonResizeableApp?.exit(wmHelper)
- }
- }
- }
-
- protected fun updateTasksId() {
- primaryTaskId = getTaskIdForActivity(
- primaryApp.component.packageName, primaryApp.component.className).toString()
- secondaryTaskId = getTaskIdForActivity(
- secondaryApp.component.packageName, secondaryApp.component.className).toString()
- val nonResizeableApp = nonResizeableApp
- if (nonResizeableApp != null) {
- nonResizeableTaskId = getTaskIdForActivity(
- nonResizeableApp.component.packageName,
- nonResizeableApp.component.className).toString()
- }
- }
-
- private fun getTaskIdForActivity(pkgName: String, activityName: String): Int {
- return activityHelper.getTaskIdForActivity(pkgName, activityName)
- }
-
- internal fun executeShellCommand(cmd: String) {
- BaseAppHelper.executeShellCommand(instrumentation, cmd)
- }
-
- internal fun composePairsCommand(
- primaryApp: String,
- secondaryApp: String,
- pair: Boolean
- ): String = buildString {
- // dumpsys activity service SystemUIService WMShell {pair|unpair} ${TASK_ID_1} ${TASK_ID_2}
- append("dumpsys activity service SystemUIService WMShell ")
- if (pair) {
- append("pair ")
- } else {
- append("unpair ")
- }
- append("$primaryApp $secondaryApp")
- }
-
- @Ignore
- @Test
- open fun navBarLayerIsVisible() {
- testSpec.navBarLayerIsVisible()
- }
-
- @Ignore
- @Test
- open fun statusBarLayerIsVisible() {
- testSpec.statusBarLayerIsVisible()
- }
-
- @Ignore
- @Test
- open fun navBarWindowIsVisible() {
- testSpec.navBarWindowIsVisible()
- }
-
- @Ignore
- @Test
- open fun statusBarWindowIsVisible() {
- testSpec.statusBarWindowIsVisible()
- }
-
- @Ignore
- @Test
- open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @Ignore
- @Test
- open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS
deleted file mode 100644
index 8446b37dbf06..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# window manager > wm shell > Split Screen
-# Bug component: 928697
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
deleted file mode 100644
index b0c3ba20d948..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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.flicker.apppairs
-
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open apps to app pairs and rotate.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppsInAppPairsMode`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class RotateTwoLaunchedAppsInAppPairsMode(
- testSpec: FlickerTestParameter
-) : RotateTwoLaunchedAppsTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- executeShellCommand(composePairsCommand(
- primaryTaskId, secondaryTaskId, true /* pair */))
- waitAppsShown(primaryApp, secondaryApp)
- setRotation(testSpec.endRotation)
- }
- }
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Ignore
- @Test
- override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
-
- @Ignore
- @Test
- fun bothAppWindowsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(primaryApp.component)
- isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Ignore
- @Test
- fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
-
- @Ignore
- @Test
- fun appPairsPrimaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation,
- primaryApp.component)
-
- @Ignore
- @Test
- fun appPairsSecondaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation,
- secondaryApp.component)
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270)
- )
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
deleted file mode 100644
index ae56c7732a4d..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.flicker.apppairs
-
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open apps to app pairs and rotate.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppsRotateAndEnterAppPairsMode`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode(
- testSpec: FlickerTestParameter
-) : RotateTwoLaunchedAppsTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- this.setRotation(testSpec.endRotation)
- executeShellCommand(
- composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
- waitAppsShown(primaryApp, secondaryApp)
- }
- }
-
- @Ignore
- @Test
- fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
-
- @Ignore
- @Test
- override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Ignore
- @Test
- override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
-
- @Ignore
- @Test
- override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @Ignore
- @Test
- fun bothAppWindowsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(primaryApp.component)
- isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Ignore
- @Test
- fun appPairsPrimaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation,
- primaryApp.component)
-
- @Ignore
- @Test
- fun appPairsSecondaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation,
- secondaryApp.component)
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270)
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
deleted file mode 100644
index b1f1c9e539df..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.flicker.apppairs
-
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assume.assumeFalse
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-
-abstract class RotateTwoLaunchedAppsTransition(
- testSpec: FlickerTestParameter
-) : AppPairsTransition(testSpec) {
- override val nonResizeableApp: SplitScreenHelper?
- get() = null
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- this.setRotation(Surface.ROTATION_0)
- primaryApp.launchViaIntent(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- updateTasksId()
- }
- }
- teardown {
- eachRun {
- executeShellCommand(composePairsCommand(
- primaryTaskId, secondaryTaskId, pair = false))
- primaryApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
- }
- }
- }
-
- @Before
- override fun setup() {
- // AppPairs hasn't been updated to Shell Transition. There will be conflict on rotation.
- assumeFalse(isShellTransitionsEnabled())
- super.setup()
- }
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() {
- super.navBarLayerIsVisible()
- }
-
- @Ignore
- @Test
- override fun navBarLayerRotatesAndScales() {
- super.navBarLayerRotatesAndScales()
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index e9d438a569d5..f73d191b1917 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -39,7 +39,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
) {
private val mediaSessionManager: MediaSessionManager
get() = context.getSystemService(MediaSessionManager::class.java)
- ?: error("Could not get MediaSessionManager")
+ ?: error("Could not get MediaSessionManager")
private val mediaController: MediaController?
get() = mediaSessionManager.getActiveSessions(null).firstOrNull {
@@ -69,8 +69,10 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
action: String? = null,
stringExtras: Map<String, String>
) {
- launchViaIntentAndWaitShown(wmHelper, expectedWindowName, action, stringExtras,
- waitConditions = arrayOf(WindowManagerStateHelper.pipShownCondition))
+ launchViaIntentAndWaitShown(
+ wmHelper, expectedWindowName, action, stringExtras,
+ waitConditions = arrayOf(WindowManagerStateHelper.pipShownCondition)
+ )
}
/**
@@ -85,7 +87,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
// from "the bottom".
repeat(FOCUS_ATTEMPTS) {
uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
- ?: error("The object we try to focus on is gone.")
+ ?: error("The object we try to focus on is gone.")
uiDevice.pressDPadDown()
uiDevice.waitForIdle()
@@ -100,29 +102,35 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
// Wait on WMHelper or simply wait for 3 seconds
wmHelper?.waitPipShown() ?: SystemClock.sleep(3_000)
// when entering pip, the dismiss button is visible at the start. to ensure the pip
- // animation is complete, wait until the pip dismiss button is no longer visible.
+ // animation is complete, wait until the pip dismiss button is no longer visible.
// b/176822698: dismiss-only state will be removed in the future
uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT)
}
+ fun enableEnterPipOnUserLeaveHint() {
+ clickObject(ENTER_PIP_ON_USER_LEAVE_HINT)
+ }
+
fun clickStartMediaSessionButton() {
clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID)
}
fun checkWithCustomActionsCheckbox() = uiDevice
- .findObject(By.res(component.packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID))
- ?.takeIf { it.isCheckable }
- ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) }
- ?: error("'With custom actions' checkbox not found")
+ .findObject(By.res(component.packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID))
+ ?.takeIf { it.isCheckable }
+ ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) }
+ ?: error("'With custom actions' checkbox not found")
fun pauseMedia() = mediaController?.transportControls?.pause()
- ?: error("No active media session found")
+ ?: error("No active media session found")
fun stopMedia() = mediaController?.transportControls?.stop()
- ?: error("No active media session found")
+ ?: error("No active media session found")
- @Deprecated("Use PipAppHelper.closePipWindow(wmHelper) instead",
- ReplaceWith("closePipWindow(wmHelper)"))
+ @Deprecated(
+ "Use PipAppHelper.closePipWindow(wmHelper) instead",
+ ReplaceWith("closePipWindow(wmHelper)")
+ )
fun closePipWindow() {
if (isTelevision) {
uiDevice.closeTvPipWindow()
@@ -152,7 +160,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
val dismissPipObject = uiDevice.findObject(dismissSelector)
- ?: error("PIP window dismiss button not found")
+ ?: error("PIP window dismiss button not found")
val dismissButtonBounds = dismissPipObject.visibleBounds
uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
}
@@ -172,7 +180,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
val expandPipObject = uiDevice.findObject(expandSelector)
- ?: error("PIP window expand button not found")
+ ?: error("PIP window expand button not found")
val expandButtonBounds = expandPipObject.visibleBounds
uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
wmHelper.waitPipGone()
@@ -194,5 +202,6 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
private const val ENTER_PIP_BUTTON_ID = "enter_pip"
private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions"
private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"
+ private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index 0ec9b2d869a8..d4298b8f29a0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -17,9 +17,18 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import android.content.res.Resources
+import android.graphics.Point
+import android.os.SystemClock
+import android.view.InputDevice
+import android.view.MotionEvent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.testapp.Components
class SplitScreenHelper(
@@ -31,25 +40,152 @@ class SplitScreenHelper(
companion object {
const val TEST_REPETITIONS = 1
const val TIMEOUT_MS = 3_000L
+ const val DRAG_DURATION_MS = 1_000L
+ const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
+ const val GESTURE_STEP_MS = 16L
- // TODO: remove all legacy split screen flicker tests when legacy split screen is fully
- // deprecated.
- fun isUsingLegacySplit(): Boolean =
- Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useLegacySplit)
+ private val notificationScrollerSelector: BySelector
+ get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
+ private val notificationContentSelector: BySelector
+ get() = By.text("Notification content")
fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(instrumentation,
+ SplitScreenHelper(
+ instrumentation,
Components.SplitScreenActivity.LABEL,
- Components.SplitScreenActivity.COMPONENT.toFlickerComponent())
+ Components.SplitScreenActivity.COMPONENT.toFlickerComponent()
+ )
fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(instrumentation,
+ SplitScreenHelper(
+ instrumentation,
Components.SplitScreenSecondaryActivity.LABEL,
- Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent())
+ Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent()
+ )
fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(instrumentation,
+ SplitScreenHelper(
+ instrumentation,
Components.NonResizeableActivity.LABEL,
- Components.NonResizeableActivity.COMPONENT.toFlickerComponent())
+ Components.NonResizeableActivity.COMPONENT.toFlickerComponent()
+ )
+
+ fun getSendNotification(instrumentation: Instrumentation): SplitScreenHelper =
+ SplitScreenHelper(
+ instrumentation,
+ Components.SendNotificationActivity.LABEL,
+ Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
+ )
+
+ fun dragFromNotificationToSplit(
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ wmHelper: WindowManagerStateHelper
+ ) {
+ val displayBounds = wmHelper.currentState.layerState
+ .displays.firstOrNull { !it.isVirtual }
+ ?.layerStackSpace
+ ?: error("Display not found")
+
+ // Pull down the notifications
+ device.swipe(
+ displayBounds.centerX(), 5,
+ displayBounds.centerX(), displayBounds.bottom, 20 /* steps */
+ )
+ SystemClock.sleep(TIMEOUT_MS)
+
+ // Find the target notification
+ val notificationScroller = device.wait(
+ Until.findObject(notificationScrollerSelector), TIMEOUT_MS
+ )
+ var notificationContent = notificationScroller.findObject(notificationContentSelector)
+
+ while (notificationContent == null) {
+ device.swipe(
+ displayBounds.centerX(), displayBounds.centerY(),
+ displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */
+ )
+ notificationContent = notificationScroller.findObject(notificationContentSelector)
+ }
+
+ // Drag to split
+ var dragStart = notificationContent.visibleCenter
+ var dragMiddle = Point(dragStart.x + 50, dragStart.y)
+ var dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
+ val downTime = SystemClock.uptimeMillis()
+
+ touch(
+ instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime,
+ TIMEOUT_MS, dragStart
+ )
+ // It needs a horizontal movement to trigger the drag
+ touchMove(
+ instrumentation, downTime, SystemClock.uptimeMillis(),
+ DRAG_DURATION_MS, dragStart, dragMiddle
+ )
+ touchMove(
+ instrumentation, downTime, SystemClock.uptimeMillis(),
+ DRAG_DURATION_MS, dragMiddle, dragEnd
+ )
+ // Wait for a while to start splitting
+ SystemClock.sleep(TIMEOUT_MS)
+ touch(
+ instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(),
+ GESTURE_STEP_MS, dragEnd
+ )
+ SystemClock.sleep(TIMEOUT_MS)
+ }
+
+ fun touch(
+ instrumentation: Instrumentation,
+ action: Int,
+ downTime: Long,
+ eventTime: Long,
+ duration: Long,
+ point: Point
+ ) {
+ val motionEvent = MotionEvent.obtain(
+ downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0
+ )
+ motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+ instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
+ motionEvent.recycle()
+ SystemClock.sleep(duration)
+ }
+
+ fun touchMove(
+ instrumentation: Instrumentation,
+ downTime: Long,
+ eventTime: Long,
+ duration: Long,
+ from: Point,
+ to: Point
+ ) {
+ val steps: Long = duration / GESTURE_STEP_MS
+ var currentTime = eventTime
+ var currentX = from.x.toFloat()
+ var currentY = from.y.toFloat()
+ val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
+ val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
+
+ for (i in 1..steps) {
+ val motionMove = MotionEvent.obtain(
+ downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0
+ )
+ motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
+ instrumentation.uiAutomation.injectInputEvent(motionMove, true)
+ motionMove.recycle()
+
+ currentTime += GESTURE_STEP_MS
+ if (i == steps - 1) {
+ currentX = to.x.toFloat()
+ currentY = to.y.toFloat()
+ } else {
+ currentX += stepX
+ currentY += stepY
+ }
+ SystemClock.sleep(GESTURE_STEP_MS)
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
deleted file mode 100644
index c86a1229d8d8..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open activity and dock to primary split screen
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenDockActivity`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
-class EnterSplitScreenDockActivity(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- device.launchSplitScreen(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, LIVE_WALLPAPER_COMPONENT,
- splitScreenApp.component, FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT, LAUNCHER_COMPONENT)
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun dockedStackDividerBecomesVisible() = testSpec.dockedStackDividerBecomesVisible()
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun appWindowIsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(splitScreenApp.component)
- }
- }
-
- @FlakyTest
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0), // bugId = 179116910
- supportedNavigationModes = listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
deleted file mode 100644
index 2f9244be9c18..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test enter split screen from a detached recent task
- *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromDetachedRecentTask`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
-class EnterSplitScreenFromDetachedRecentTask(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- splitScreenApp.launchViaIntent(wmHelper)
- // Press back to remove the task, but it should still be shown in recent.
- device.pressBack()
- }
- }
- transitions {
- device.launchSplitScreen(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun appWindowIsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
- )
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
deleted file mode 100644
index 1740c3ec24ca..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open activity to primary split screen and dock secondary activity to side
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenLaunchToSide`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
-class EnterSplitScreenLaunchToSide(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- device.launchSplitScreen(wmHelper)
- device.reopenAppFromOverview(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component,
- secondaryApp.component, FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- secondaryApp.component)
-
- @Presubmit
- @Test
- fun dockedStackDividerBecomesVisible() = testSpec.dockedStackDividerBecomesVisible()
-
- @Presubmit
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- // when the app is launched, first the activity becomes visible, then the
- // SnapshotStartingWindow appears and then the app window becomes visible.
- // Because we log WM once per frame, sometimes the activity and the window
- // become visible in the same entry, sometimes not, thus it is not possible to
- // assert the visibility of the activity here
- this.isAppWindowInvisible(secondaryApp.component)
- .then()
- // during re-parenting, the window may disappear and reappear from the
- // trace, this occurs because we log only 1x per frame
- .notContains(secondaryApp.component, isOptional = true)
- .then()
- .isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0) // bugId = 175687842
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
deleted file mode 100644
index 4c063b918e96..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.canSplitScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-import org.junit.Assert
-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
-
-/**
- * Test enter split screen from non-resizable activity. When the device doesn't support
- * non-resizable in multi window, there should be no button to enter split screen for non-resizable
- * activity.
- *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNotSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
-class EnterSplitScreenNotSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- nonResizeableApp.launchViaIntent(wmHelper)
- }
- }
- transitions {
- if (device.canSplitScreen(wmHelper)) {
- Assert.fail("Non-resizeable app should not enter split screen")
- }
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT,
- nonResizeableApp.component,
- splitScreenApp.component)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, -1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
deleted file mode 100644
index f75dee619564..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-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
-
-/**
- * Test enter split screen from non-resizable activity. When the device supports
- * non-resizable in multi window, there should be a button to enter split screen for non-resizable
- * activity.
- *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group2
-class EnterSplitScreenSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- nonResizeableApp.launchViaIntent(wmHelper)
- }
- }
- transitions {
- device.launchSplitScreen(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT,
- nonResizeableApp.component,
- splitScreenApp.component)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, 1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun appWindowIsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
deleted file mode 100644
index ef7d65e8a732..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
+++ /dev/null
@@ -1,124 +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.flicker.legacysplitscreen
-
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open resizeable activity split in primary, and drag divider to bottom exit split screen
- * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottom`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class ExitLegacySplitScreenFromBottom(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- setup {
- eachRun {
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- teardown {
- eachRun {
- splitScreenApp.exit(wmHelper)
- }
- }
- transitions {
- device.exitSplitScreenFromBottom(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
- splitScreenApp.component, secondaryApp.component,
- FlickerComponentName.SNAPSHOT)
-
- @FlakyTest
- @Test
- fun layerBecomesInvisible() {
- testSpec.assertLayers {
- this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
- .then()
- .isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
- }
- }
-
- @FlakyTest
- @Test
- fun appWindowBecomesInVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(secondaryApp.component)
- .then()
- .isAppWindowInvisible(secondaryApp.component)
- }
- }
-
- @FlakyTest
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @FlakyTest
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @FlakyTest
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @FlakyTest
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0) // b/175687842
- )
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
deleted file mode 100644
index d913a6d85d3d..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test dock activity to primary split screen, and open secondary to side, exit primary split
- * and test secondary activity become full screen.
- * To run this test: `atest WMShellFlickerTests:ExitPrimarySplitScreenShowSecondaryFullscreen`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class ExitPrimarySplitScreenShowSecondaryFullscreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- teardown {
- eachRun {
- secondaryApp.exit(wmHelper)
- }
- }
- transitions {
- splitScreenApp.launchViaIntent(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- device.reopenAppFromOverview(wmHelper)
- // TODO(b/175687842) Can not find Split screen divider, use exit() instead
- splitScreenApp.exit(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
- splitScreenApp.component, secondaryApp.component,
- FlickerComponentName.SNAPSHOT)
-
- @Presubmit
- @Test
- fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
-
- @FlakyTest
- @Test
- fun layerBecomesInvisible() {
- testSpec.assertLayers {
- this.isVisible(splitScreenApp.component)
- .then()
- .isInvisible(splitScreenApp.component)
- }
- }
-
- @FlakyTest
- @Test
- fun appWindowBecomesInVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(splitScreenApp.component)
- .then()
- .isAppWindowInvisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
deleted file mode 100644
index f3ff7b156aaf..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-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
-
-/**
- * Test launch non-resizable activity via intent in split screen mode. When the device does not
- * support non-resizable in multi window, it should trigger exit split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentNotSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class LegacySplitScreenFromIntentNotSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- transitions {
- nonResizeableApp.launchViaIntent(wmHelper)
- wmHelper.waitForAppTransitionIdle()
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
- nonResizeableApp.component, splitScreenApp.component,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, -1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun resizableAppLayerBecomesInvisible() {
- testSpec.assertLayers {
- this.isVisible(splitScreenApp.component)
- .then()
- .isInvisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun nonResizableAppLayerBecomesVisible() {
- testSpec.assertLayers {
- this.notContains(nonResizeableApp.component)
- .then()
- .isInvisible(nonResizeableApp.component)
- .then()
- .isVisible(nonResizeableApp.component)
- }
- }
-
- /**
- * Assets that [splitScreenApp] exists at the start of the trace and, once it becomes
- * invisible, it remains invisible until the end of the trace.
- */
- @Presubmit
- @Test
- fun resizableAppWindowBecomesInvisible() {
- testSpec.assertWm {
- // when the activity gets PAUSED the window may still be marked as visible
- // it will be updated in the next log entry. This occurs because we record 1x
- // per frame, thus ignore activity check here
- this.isAppWindowVisible(splitScreenApp.component)
- .then()
- // immediately after the window (after onResume and before perform relayout)
- // the activity is invisible. This may or not be logged, since we record 1x
- // per frame, thus ignore activity check here
- .isAppWindowInvisible(splitScreenApp.component)
- }
- }
-
- /**
- * Assets that [nonResizeableApp] doesn't exist at the start of the trace, then
- * [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes
- * visible, it remains visible until the end of the trace.
- */
- @Presubmit
- @Test
- fun nonResizableAppWindowBecomesVisible() {
- testSpec.assertWm {
- this.notContains(nonResizeableApp.component)
- .then()
- // we log once per frame, upon logging, window may be visible or not depending
- // on what was processed until that moment. Both behaviors are correct
- .isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
- .then()
- // immediately after the window (after onResume and before perform relayout)
- // the activity is invisible. This may or not be logged, since we record 1x
- // per frame, thus ignore activity check here
- .isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- /**
- * Asserts that both the app window and the activity are visible at the end of the trace
- */
- @Presubmit
- @Test
- fun nonResizableAppWindowBecomesVisibleAtEnd() {
- testSpec.assertWmEnd {
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
-
- @Presubmit
- @Test
- fun onlyNonResizableAppWindowIsVisibleAtEnd() {
- testSpec.assertWmEnd {
- isAppWindowInvisible(splitScreenApp.component)
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
deleted file mode 100644
index 42e707ab0850..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-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
-
-/**
- * Test launch non-resizable activity via intent in split screen mode. When the device supports
- * non-resizable in multi window, it should show the non-resizable app in split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class LegacySplitScreenFromIntentSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- transitions {
- nonResizeableApp.launchViaIntent(wmHelper)
- wmHelper.waitForAppTransitionIdle()
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
- nonResizeableApp.component, splitScreenApp.component,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, 1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun nonResizableAppLayerBecomesVisible() {
- testSpec.assertLayers {
- this.isInvisible(nonResizeableApp.component)
- .then()
- .isVisible(nonResizeableApp.component)
- }
- }
-
- /**
- * Assets that [nonResizeableApp] doesn't exist at the start of the trace, then
- * [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes
- * visible, it remains visible until the end of the trace.
- */
- @Presubmit
- @Test
- fun nonResizableAppWindowBecomesVisible() {
- testSpec.assertWm {
- this.notContains(nonResizeableApp.component)
- .then()
- // we log once per frame, upon logging, window may be visible or not depending
- // on what was processed until that moment. Both behaviors are correct
- .isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
- .then()
- // immediately after the window (after onResume and before perform relayout)
- // the activity is invisible. This may or not be logged, since we record 1x
- // per frame, thus ignore activity check here
- .isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun bothAppsWindowsAreVisibleAtEnd() {
- testSpec.assertWmEnd {
- isAppWindowVisible(splitScreenApp.component)
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
deleted file mode 100644
index c1fba7d1530c..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * 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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-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
-
-/**
- * Test launch non-resizable activity via recent overview in split screen mode. When the device does
- * not support non-resizable in multi window, it should trigger exit split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentNotSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class LegacySplitScreenFromRecentNotSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- nonResizeableApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- transitions {
- device.reopenAppFromOverview(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
- TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, -1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun resizableAppLayerBecomesInvisible() {
- testSpec.assertLayers {
- this.isVisible(splitScreenApp.component)
- .then()
- .isInvisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun nonResizableAppLayerBecomesVisible() {
- testSpec.assertLayers {
- this.isInvisible(nonResizeableApp.component)
- .then()
- .isVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun resizableAppWindowBecomesInvisible() {
- testSpec.assertWm {
- // when the activity gets PAUSED the window may still be marked as visible
- // it will be updated in the next log entry. This occurs because we record 1x
- // per frame, thus ignore activity check here
- this.isAppWindowVisible(splitScreenApp.component)
- .then()
- // immediately after the window (after onResume and before perform relayout)
- // the activity is invisible. This may or not be logged, since we record 1x
- // per frame, thus ignore activity check here
- .isAppWindowInvisible(splitScreenApp.component)
- }
- }
-
- @FlakyTest
- @Test
- fun nonResizableAppWindowBecomesVisible() {
- testSpec.assertWm {
- this.isAppWindowInvisible(nonResizeableApp.component)
- .then()
- .isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
-
- @Presubmit
- @Test
- fun onlyNonResizableAppWindowIsVisibleAtEnd() {
- testSpec.assertWmEnd {
- isAppWindowInvisible(splitScreenApp.component)
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
deleted file mode 100644
index 6ac8683ac054..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * 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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-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
-
-/**
- * Test launch non-resizable activity via recent overview in split screen mode. When the device
- * supports non-resizable in multi window, it should show the non-resizable app in split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class LegacySplitScreenFromRecentSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- nonResizeableApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- transitions {
- device.reopenAppFromOverview(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
- TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, 1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun nonResizableAppLayerBecomesVisible() {
- testSpec.assertLayers {
- this.isInvisible(nonResizeableApp.component)
- .then()
- .isVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun nonResizableAppWindowBecomesVisible() {
- testSpec.assertWm {
- // when the app is launched, first the activity becomes visible, then the
- // SnapshotStartingWindow appears and then the app window becomes visible.
- // Because we log WM once per frame, sometimes the activity and the window
- // become visible in the same entry, sometimes not, thus it is not possible to
- // assert the visibility of the activity here
- this.isAppWindowInvisible(nonResizeableApp.component)
- .then()
- // during re-parenting, the window may disappear and reappear from the
- // trace, this occurs because we log only 1x per frame
- .notContains(nonResizeableApp.component, isOptional = true)
- .then()
- // if the window reappears after re-parenting it will most likely not
- // be visible in the first log entry (because we log only 1x per frame)
- .isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
- .then()
- .isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun bothAppsWindowsAreVisibleAtEnd() {
- testSpec.assertWmEnd {
- isAppWindowVisible(splitScreenApp.component)
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
deleted file mode 100644
index b01f41c9e2ec..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.flicker.legacysplitscreen
-
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-
-abstract class LegacySplitScreenRotateTransition(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- }
- }
- teardown {
- eachRun {
- splitScreenApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
- this.setRotation(Surface.ROTATION_0)
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
deleted file mode 100644
index fb1004bda0cb..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
+++ /dev/null
@@ -1,160 +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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible
-import com.android.wm.shell.flicker.helpers.SimpleAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncher`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class LegacySplitScreenToLauncher(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- private val testApp = SimpleAppHelper(instrumentation)
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
- }
- eachRun {
- testApp.launchViaIntent(wmHelper)
- this.setRotation(testSpec.endRotation)
- device.launchSplitScreen(wmHelper)
- device.waitForIdle()
- }
- }
- teardown {
- eachRun {
- testApp.exit(wmHelper)
- }
- }
- transitions {
- device.exitSplitScreen()
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
-
- @Presubmit
- @Test
- fun entireScreenCovered() = testSpec.entireScreenCovered()
-
- @Presubmit
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @Presubmit
- @Test
- fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
-
- @FlakyTest
- @Test
- fun dockedStackDividerBecomesInvisible() = testSpec.dockedStackDividerBecomesInvisible()
-
- @FlakyTest
- @Test
- fun layerBecomesInvisible() {
- testSpec.assertLayers {
- this.isVisible(testApp.component)
- .then()
- .isInvisible(testApp.component)
- }
- }
-
- @FlakyTest
- @Test
- fun focusDoesNotChange() {
- testSpec.assertEventLog {
- this.focusDoesNotChange()
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- // b/161435597 causes the test not to work on 90 degrees
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
deleted file mode 100644
index a4a1f617e497..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
+++ /dev/null
@@ -1,149 +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/LICENSE2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.legacysplitscreen
-
-import android.app.Instrumentation
-import android.content.Context
-import android.support.test.launcherhelper.LauncherStrategyFactory
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.FlickerBuilderProvider
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-import org.junit.Assume.assumeFalse
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.Test
-
-abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestParameter) {
- protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- protected val context: Context = instrumentation.context
- protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
- protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
- protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
- protected val LAUNCHER_COMPONENT = FlickerComponentName("",
- LauncherStrategyFactory.getInstance(instrumentation)
- .launcherStrategy.supportedLauncherPackage)
- private var prevDevEnableNonResizableMultiWindow = 0
-
- @Before
- open fun setup() {
- // Only run legacy split tests when the system is using legacy split screen.
- assumeTrue(SplitScreenHelper.isUsingLegacySplit())
- // Legacy split is having some issue with Shell transition, and will be deprecated soon.
- assumeFalse(isShellTransitionsEnabled())
- prevDevEnableNonResizableMultiWindow = getDevEnableNonResizableMultiWindow(context)
- if (prevDevEnableNonResizableMultiWindow != 0) {
- // Turn off the development option
- setDevEnableNonResizableMultiWindow(context, 0)
- }
- }
-
- @After
- open fun teardown() {
- setDevEnableNonResizableMultiWindow(context, prevDevEnableNonResizableMultiWindow)
- }
-
- /**
- * List of windows that are ignored when verifying that visible elements appear on 2
- * consecutive entries in the trace.
- *
- * b/182720234
- */
- open val ignoredWindows: List<FlickerComponentName> = listOf(
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- protected open val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- this.setRotation(testSpec.startRotation)
- }
- }
- teardown {
- eachRun {
- secondaryApp.exit(wmHelper)
- splitScreenApp.exit(wmHelper)
- this.setRotation(Surface.ROTATION_0)
- }
- }
- }
-
- @FlickerBuilderProvider
- fun buildFlicker(): FlickerBuilder {
- return FlickerBuilder(instrumentation).apply {
- transition(this)
- }
- }
-
- internal open val cleanSetup: FlickerBuilder.() -> Unit
- get() = {
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
- this.setRotation(testSpec.startRotation)
- }
- }
- teardown {
- eachRun {
- nonResizeableApp.exit(wmHelper)
- splitScreenApp.exit(wmHelper)
- device.pressHome()
- this.setRotation(Surface.ROTATION_0)
- }
- }
- }
-
- @FlakyTest(bugId = 178447631)
- @Test
- open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.assertWm {
- this.visibleWindowsShownMoreThanOneConsecutiveEntry(ignoredWindows)
- }
- }
-
- @FlakyTest(bugId = 178447631)
- @Test
- open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers {
- this.visibleLayersShownMoreThanOneConsecutiveEntry(ignoredWindows)
- }
- }
-
- companion object {
- internal val LIVE_WALLPAPER_COMPONENT = FlickerComponentName("",
- "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2")
- internal val LETTERBOX_COMPONENT = FlickerComponentName("", "Letterbox")
- internal val TOAST_COMPONENT = FlickerComponentName("", "Toast")
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS
deleted file mode 100644
index 8446b37dbf06..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# window manager > wm shell > Split Screen
-# Bug component: 928697
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
deleted file mode 100644
index 087b21c544c5..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
+++ /dev/null
@@ -1,122 +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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.statusBarLayerIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreen`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class OpenAppToLegacySplitScreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- device.launchSplitScreen(wmHelper)
- wmHelper.waitForAppTransitionIdle()
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @FlakyTest
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- this.isAppWindowInvisible(splitScreenApp.component)
- .then()
- .isAppWindowVisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun entireScreenCovered() = testSpec.entireScreenCovered()
-
- @Presubmit
- @Test
- fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
-
- @Presubmit
- @Test
- fun appPairsDividerBecomesVisible() = testSpec.appPairsDividerBecomesVisible()
-
- @FlakyTest
- @Test
- fun layerBecomesVisible() {
- testSpec.assertLayers {
- this.isInvisible(splitScreenApp.component)
- .then()
- .isVisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun focusChanges() {
- testSpec.assertEventLog {
- this.focusChanges(splitScreenApp.`package`,
- "recents_animation_input_consumer", "NexusLauncherActivity")
- }
- }
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
deleted file mode 100644
index e2da1a4565c0..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ /dev/null
@@ -1,228 +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.flicker.legacysplitscreen
-
-import android.util.Rational
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import androidx.test.uiautomator.By
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.resizeSplitScreen
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.helpers.SimpleAppHelper
-import com.android.wm.shell.flicker.testapp.Components
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test split screen resizing window transitions.
- * To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreen`
- *
- * Currently it runs only in 0 degrees because of b/156100803
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 159096424)
-@Group2
-class ResizeLegacySplitScreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- private val testAppTop = SimpleAppHelper(instrumentation)
- private val testAppBottom = ImeAppHelper(instrumentation)
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- this.setRotation(testSpec.startRotation)
- this.launcherStrategy.clearRecentAppsFromOverview()
- testAppBottom.launchViaIntent(wmHelper)
- device.pressHome()
- testAppTop.launchViaIntent(wmHelper)
- device.waitForIdle()
- device.launchSplitScreen(wmHelper)
- val snapshot =
- device.findObject(By.res(device.launcherPackageName, "snapshot"))
- snapshot.click()
- testAppBottom.openIME(device)
- device.pressBack()
- device.resizeSplitScreen(startRatio)
- }
- }
- teardown {
- eachRun {
- testAppTop.exit(wmHelper)
- testAppBottom.exit(wmHelper)
- }
- }
- transitions {
- device.resizeSplitScreen(stopRatio)
- }
- }
-
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @FlakyTest(bugId = 156223549)
- @Test
- fun topAppWindowIsAlwaysVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent())
- }
- }
-
- @FlakyTest(bugId = 156223549)
- @Test
- fun bottomAppWindowIsAlwaysVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(Components.ImeActivity.COMPONENT.toFlickerComponent())
- }
- }
-
- @Test
- fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
-
- @Test
- fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
-
- @Test
- fun entireScreenCovered() = testSpec.entireScreenCovered()
-
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @Test
- fun topAppLayerIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent())
- }
- }
-
- @Test
- fun bottomAppLayerIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(Components.ImeActivity.COMPONENT.toFlickerComponent())
- }
- }
-
- @Test
- fun dividerLayerIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
- }
- }
-
- @FlakyTest
- @Test
- fun appsStartingBounds() {
- testSpec.assertLayersStart {
- val displayBounds = WindowUtils.displayBounds
- val dividerBounds =
- layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
-
- val topAppBounds = Region.from(0, 0, dividerBounds.right,
- dividerBounds.top + WindowUtils.dockedStackDividerInset)
- val bottomAppBounds = Region.from(0,
- dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.right,
- displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
- visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
- .coversExactly(topAppBounds)
- visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent())
- .coversExactly(bottomAppBounds)
- }
- }
-
- @FlakyTest
- @Test
- fun appsEndingBounds() {
- testSpec.assertLayersStart {
- val displayBounds = WindowUtils.displayBounds
- val dividerBounds =
- layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
-
- val topAppBounds = Region.from(0, 0, dividerBounds.right,
- dividerBounds.top + WindowUtils.dockedStackDividerInset)
- val bottomAppBounds = Region.from(0,
- dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.right,
- displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
-
- visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
- .coversExactly(topAppBounds)
- visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent())
- .coversExactly(bottomAppBounds)
- }
- }
-
- @Test
- fun focusDoesNotChange() {
- testSpec.assertEventLog {
- focusDoesNotChange()
- }
- }
-
- companion object {
- private val startRatio = Rational(1, 3)
- private val stopRatio = Rational(2, 3)
-
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
- .map {
- val description = (startRatio.toString().replace("/", "-") + "_to_" +
- stopRatio.toString().replace("/", "-"))
- val newName = "${FlickerTestParameter.defaultName(it)}_$description"
- FlickerTestParameter(it.config, nameOverride = newName)
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
deleted file mode 100644
index d703ea082c87..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
+++ /dev/null
@@ -1,114 +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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test dock activity to primary split screen and rotate
- * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppAndEnterSplitScreen`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class RotateOneLaunchedAppAndEnterSplitScreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- device.launchSplitScreen(wmHelper)
- this.setRotation(testSpec.startRotation)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @FlakyTest
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- this.isAppWindowInvisible(splitScreenApp.component)
- .then()
- .isAppWindowVisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
deleted file mode 100644
index 6b1883914e59..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
+++ /dev/null
@@ -1,113 +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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Rotate
- * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppInSplitScreenMode`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class RotateOneLaunchedAppInSplitScreenMode(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- this.setRotation(testSpec.startRotation)
- device.launchSplitScreen(wmHelper)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() = testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(
- testSpec.startRotation, splitScreenApp.component)
-
- @Presubmit
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @FlakyTest
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- this.isAppWindowInvisible(splitScreenApp.component)
- .then()
- .isAppWindowVisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
deleted file mode 100644
index acd658b5ba56..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
+++ /dev/null
@@ -1,136 +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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppAndEnterSplitScreen`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class RotateTwoLaunchedAppAndEnterSplitScreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- this.setRotation(testSpec.startRotation)
- device.launchSplitScreen(wmHelper)
- device.reopenAppFromOverview(wmHelper)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- secondaryApp.component)
-
- @Presubmit
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @Presubmit
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- // when the app is launched, first the activity becomes visible, then the
- // SnapshotStartingWindow appears and then the app window becomes visible.
- // Because we log WM once per frame, sometimes the activity and the window
- // become visible in the same entry, sometimes not, thus it is not possible to
- // assert the visibility of the activity here
- this.isAppWindowInvisible(secondaryApp.component)
- .then()
- // during re-parenting, the window may disappear and reappear from the
- // trace, this occurs because we log only 1x per frame
- .notContains(secondaryApp.component, isOptional = true)
- .then()
- // if the window reappears after re-parenting it will most likely not
- // be visible in the first log entry (because we log only 1x per frame)
- .isAppWindowInvisible(secondaryApp.component, isOptional = true)
- .then()
- .isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
deleted file mode 100644
index b40be8b5f401..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
+++ /dev/null
@@ -1,133 +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.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppInSplitScreenMode`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class RotateTwoLaunchedAppInSplitScreenMode(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- setup {
- eachRun {
- device.launchSplitScreen(wmHelper)
- device.reopenAppFromOverview(wmHelper)
- this.setRotation(testSpec.startRotation)
- }
- }
- transitions {
- this.setRotation(testSpec.startRotation)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- secondaryApp.component)
-
- @Presubmit
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @FlakyTest
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- this.isAppWindowInvisible(secondaryApp.component)
- .then()
- .isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
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
new file mode 100644
index 000000000000..e7d641e9c66e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.flicker.pip
+
+import androidx.test.filters.RequiresDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from an app via [onUserLeaveHint] and by navigating to home.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterPipOnUserLeaveHintTest`
+ *
+ * Actions:
+ * Launch an app in full screen
+ * Select "Via code behind" radio button
+ * Press Home button to put [pipApp] in pip mode
+ *
+ * Notes:
+ * 1. All assertions are inherited from [EnterPipTest]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group3
+class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
+ protected val taplInstrumentation = LauncherInstrumentation()
+ /**
+ * Defines the transition used to run the test
+ */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setupAndTeardown(this)
+ setup {
+ eachRun {
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.enableEnterPipOnUserLeaveHint()
+ }
+ }
+ teardown {
+ eachRun {
+ pipApp.exit(wmHelper)
+ }
+ }
+ transitions {
+ taplInstrumentation.goHome()
+ }
+ }
+
+ override fun pipAppLayerAlwaysVisible() {
+ if (!testSpec.isGesturalNavigation) super.pipAppLayerAlwaysVisible() else {
+ // pip layer in gesture nav will disappear during transition
+ testSpec.assertLayers {
+ this.isVisible(pipApp.component)
+ .then().isInvisible(pipApp.component)
+ .then().isVisible(pipApp.component)
+ }
+ }
+ }
+
+ override fun pipLayerReduces() {
+ // in gestural nav the pip enters through alpha animation
+ Assume.assumeFalse(testSpec.isGesturalNavigation)
+ super.pipLayerReduces()
+ }
+
+ override fun focusChanges() {
+ // in gestural nav the focus goes to different activity on swipe up
+ Assume.assumeFalse(testSpec.isGesturalNavigation)
+ super.focusChanges()
+ }
+
+ override fun pipLayerRemainInsideVisibleBounds() {
+ if (!testSpec.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds() else {
+ // pip layer in gesture nav will disappear during transition
+ testSpec.assertLayersStart {
+ this.visibleRegion(pipApp.component).coversAtMost(displayBounds)
+ }
+ testSpec.assertLayersEnd {
+ this.visibleRegion(pipApp.component).coversAtMost(displayBounds)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 0640ac526bd0..b4267ffced5c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -43,7 +43,7 @@ import org.junit.runners.Parameterized
*
* Notes:
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [PipTransition]
+ * are inherited from [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
@@ -54,7 +54,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
-class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
/**
* Defines the transition used to run the test
@@ -98,7 +98,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
*/
@Presubmit
@Test
- fun pipAppLayerAlwaysVisible() {
+ open fun pipAppLayerAlwaysVisible() {
testSpec.assertLayers {
this.isVisible(pipApp.component)
}
@@ -122,7 +122,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
*/
@Presubmit
@Test
- fun pipLayerRemainInsideVisibleBounds() {
+ open fun pipLayerRemainInsideVisibleBounds() {
testSpec.assertLayersVisibleRegion(pipApp.component) {
coversAtMost(displayBounds)
}
@@ -133,7 +133,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
*/
@Presubmit
@Test
- fun pipLayerReduces() {
+ open fun pipLayerReduces() {
val layerName = pipApp.component.toLayerName()
testSpec.assertLayers {
val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
@@ -175,7 +175,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
*/
@Presubmit
@Test
- fun focusChanges() {
+ open fun focusChanges() {
testSpec.assertEventLog {
this.focusChanges(pipApp.`package`, "NexusLauncherActivity")
}
@@ -192,8 +192,10 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 3)
+ .getConfigNonRotationTests(
+ supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 3
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 37e9344348d9..c6a705dacb8d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -80,7 +80,17 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+ override fun statusBarLayerRotatesScales() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ super.statusBarLayerRotatesScales()
+ }
+
+ @Presubmit
+ @Test
+ fun statusBarLayerRotatesScales_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ super.statusBarLayerRotatesScales()
+ }
/** {@inheritDoc} */
@FlakyTest(bugId = 197726610)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
index 1a21d32f568c..fe51228230cb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.pip
-import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -35,7 +35,6 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
-@FlakyTest(bugId = 217777115)
class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) {
@Before
@@ -43,7 +42,7 @@ class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardT
Assume.assumeTrue(isShellTransitionsEnabled)
}
- @FlakyTest(bugId = 214452854)
+ @Presubmit
@Test
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
deleted file mode 100644
index 21175a0767a5..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,148 +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.flicker.pip
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.android.wm.shell.flicker.helpers.ImeAppHelper
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
-import org.junit.Assume.assumeFalse
-import org.junit.Assume.assumeTrue
-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
-
-/**
- * Test Pip with split-screen.
- * To run this test: `atest WMShellFlickerTests:PipLegacySplitScreenTest`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
-class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
- private val imeApp = ImeAppHelper(instrumentation)
- private val testApp = FixedAppHelper(instrumentation)
-
- @Before
- open fun setup() {
- // Only run legacy split tests when the system is using legacy split screen.
- assumeTrue(SplitScreenHelper.isUsingLegacySplit())
- // Legacy split is having some issue with Shell transition, and will be deprecated soon.
- assumeFalse(isShellTransitionsEnabled())
- }
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- test {
- removeAllTasksButHome()
- device.wakeUpAndGoToHomeScreen()
- pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"),
- wmHelper = wmHelper)
- }
- }
- transitions {
- testApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- imeApp.launchViaIntent(wmHelper)
- }
- teardown {
- eachRun {
- imeApp.exit(wmHelper)
- testApp.exit(wmHelper)
- }
- test {
- removeAllTasksButHome()
- }
- }
- }
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @FlakyTest(bugId = 161435597)
- @Test
- fun pipWindowInsideDisplayBounds() {
- testSpec.assertWmVisibleRegion(pipApp.component) {
- coversAtMost(displayBounds)
- }
- }
-
- @Presubmit
- @Test
- fun bothAppWindowsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(testApp.component)
- isAppWindowVisible(imeApp.component)
- doNotOverlap(testApp.component, imeApp.component)
- }
- }
-
- @FlakyTest(bugId = 161435597)
- @Test
- fun pipLayerInsideDisplayBounds() {
- testSpec.assertLayersVisibleRegion(pipApp.component) {
- coversAtMost(displayBounds)
- }
- }
-
- @Presubmit
- @Test
- fun bothAppLayersVisible() {
- testSpec.assertLayersEnd {
- visibleRegion(testApp.component).coversAtMost(displayBounds)
- visibleRegion(imeApp.component).coversAtMost(displayBounds)
- }
- }
-
- @FlakyTest(bugId = 161435597)
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
-
- companion object {
- const val TEST_REPETITIONS = 2
-
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = TEST_REPETITIONS
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index c1ee1a7cbb35..4618fb376f7f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -27,12 +27,10 @@ import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -141,14 +139,6 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Presubmit
@Test
fun pipLayerRotates_StartingBounds() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- pipLayerRotates_StartingBounds_internal()
- }
-
- @FlakyTest(bugId = 228024285)
- @Test
- fun pipLayerRotates_StartingBounds_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
pipLayerRotates_StartingBounds_internal()
}
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
new file mode 100644
index 000000000000..702710caded7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.flicker.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+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 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
+
+/**
+ * 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`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class EnterSplitScreenByDragFromAllApps(
+ testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+ @Before
+ open fun before() {
+ Assume.assumeTrue(taplInstrumentation.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ taplInstrumentation.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ taplInstrumentation.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.component.packageName,
+ primaryApp.component.packageName)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ testSpec.endRotation, primaryApp.component, false /* splitLeftTop */)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+ testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(secondaryApp.component)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
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
new file mode 100644
index 000000000000..7323d992ecd4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.flicker.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+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 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
+
+/**
+ * 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`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class EnterSplitScreenByDragFromNotification(
+ testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+ private val sendNotificationApp = SplitScreenHelper.getSendNotification(instrumentation)
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(taplInstrumentation.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ // Send a notification
+ sendNotificationApp.launchViaIntent(wmHelper)
+ val sendNotification = device.wait(
+ Until.findObject(By.text("Send Notification")),
+ SplitScreenHelper.TIMEOUT_MS
+ )
+ sendNotification?.click() ?: error("Send notification button not found")
+
+ taplInstrumentation.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+ }
+ teardown {
+ eachRun {
+ sendNotificationApp.exit(wmHelper)
+ }
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() =
+ testSpec.layerBecomesVisible(sendNotificationApp.component)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ testSpec.endRotation, primaryApp.component, false /* splitLeftTop */
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+ testSpec.endRotation, sendNotificationApp.component, true /* splitLeftTop */
+ )
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowIsVisibleAtEnd() =
+ testSpec.appWindowIsVisibleAtEnd(sendNotificationApp.component)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ )
+ }
+ }
+}
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
new file mode 100644
index 000000000000..52c2daf96a3c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.app.Instrumentation
+import android.content.Context
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+
+abstract class SplitScreenBase(protected val testSpec: FlickerTestParameter) {
+ protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ protected val taplInstrumentation = LauncherInstrumentation()
+ protected val context: Context = instrumentation.context
+ protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation)
+ protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ transition(this)
+ }
+ }
+
+ protected open val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ test {
+ taplInstrumentation.setEnableRotation(true)
+ setRotation(testSpec.startRotation)
+ taplInstrumentation.setExpectedRotation(testSpec.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
index bd98585b67ec..bc0b0b6292b4 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
@@ -92,6 +92,17 @@
</intent-filter>
</activity>
+ <activity android:name=".SendNotificationActivity"
+ android:taskAffinity="com.android.wm.shell.flicker.testapp.SendNotificationActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:label="SendNotificationApp"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
<activity android:name=".NonResizeableActivity"
android:resizeableActivity="false"
android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity"
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
new file mode 100644
index 000000000000..8d59b567e59b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/black">
+
+ <Button
+ android:id="@+id/button_send_notification"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:text="Send Notification" />
+</RelativeLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
index 909b77c87894..e9e7bb61660d 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -44,6 +44,32 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
+ android:checkedButton="@id/enter_pip_on_leave_disabled">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Enter PiP on home press"/>
+
+ <RadioButton
+ android:id="@+id/enter_pip_on_leave_disabled"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Disabled"
+ android:onClick="onAutoPipSelected"/>
+
+ <RadioButton
+ android:id="@+id/enter_pip_on_leave_manual"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Via code behind"
+ android:onClick="onAutoPipSelected"/>
+ </RadioGroup>
+
+ <RadioGroup
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
android:checkedButton="@id/ratio_default">
<TextView
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
index 0ed59bdafd1d..a2b580da5898 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
@@ -88,6 +88,12 @@ public class Components {
PACKAGE_NAME + ".SplitScreenSecondaryActivity");
}
+ public static class SendNotificationActivity {
+ public static final String LABEL = "SendNotificationApp";
+ public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
+ PACKAGE_NAME + ".SendNotificationActivity");
+ }
+
public static class LaunchBubbleActivity {
public static final String LABEL = "LaunchBubbleApp";
public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
index a6ba7823e22d..a39aa4dc7bde 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
@@ -48,6 +48,7 @@ import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CheckBox;
+import android.widget.RadioButton;
import java.util.ArrayList;
import java.util.Arrays;
@@ -201,6 +202,17 @@ public class PipActivity extends Activity {
super.onDestroy();
}
+ @Override
+ protected void onUserLeaveHint() {
+ // Only used when auto PiP is disabled. This is to simulate the behavior that an app
+ // supports regular PiP but not auto PiP.
+ final boolean manuallyEnterPip =
+ ((RadioButton) findViewById(R.id.enter_pip_on_leave_manual)).isChecked();
+ if (manuallyEnterPip) {
+ enterPictureInPictureMode();
+ }
+ }
+
private RemoteAction buildRemoteAction(Icon icon, String label, String action) {
final Intent intent = new Intent(action);
final PendingIntent pendingIntent =
@@ -216,6 +228,17 @@ public class PipActivity extends Activity {
enterPictureInPictureMode(mPipParamsBuilder.build());
}
+ public void onAutoPipSelected(View v) {
+ switch (v.getId()) {
+ case R.id.enter_pip_on_leave_manual:
+ // disable auto enter PiP
+ case R.id.enter_pip_on_leave_disabled:
+ mPipParamsBuilder.setAutoEnterEnabled(false);
+ setPictureInPictureParams(mPipParamsBuilder.build());
+ break;
+ }
+ }
+
public void onRatioSelected(View v) {
switch (v.getId()) {
case R.id.ratio_default:
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
new file mode 100644
index 000000000000..8020ef2270a0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
@@ -0,0 +1,61 @@
+/*
+ * 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.flicker.testapp;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+public class SendNotificationActivity extends Activity {
+ private NotificationManager mNotificationManager;
+ private String mChannelId = "Channel id";
+ private String mChannelName = "Channel name";
+ private NotificationChannel mChannel;
+ private int mNotifyId = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_notification);
+ findViewById(R.id.button_send_notification).setOnClickListener(this::sendNotification);
+
+ mChannel = new NotificationChannel(mChannelId, mChannelName,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ mNotificationManager = getSystemService(NotificationManager.class);
+ mNotificationManager.createNotificationChannel(mChannel);
+ }
+
+ private void sendNotification(View v) {
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
+ new Intent(this, SendNotificationActivity.class),
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ Notification notification = new Notification.Builder(this, mChannelId)
+ .setContentTitle("Notification App")
+ .setContentText("Notification content")
+ .setWhen(System.currentTimeMillis())
+ .setSmallIcon(R.drawable.ic_message)
+ .setContentIntent(pendingIntent)
+ .build();
+
+ mNotificationManager.notify(mNotifyId, notification);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index a6caefe6d3e7..0b53c4069c3f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -133,7 +133,7 @@ public class ShellTaskOrganizerTests {
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
- mCompatUI, Optional.empty()));
+ mCompatUI, Optional.empty(), Optional.empty()));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
deleted file mode 100644
index e73d9aaf190a..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
+++ /dev/null
@@ -1,123 +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.apppairs;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.hardware.display.DisplayManager;
-
-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.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link AppPair}
- * Build/Install/Run:
- * atest WMShellUnitTests:AppPairTests
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AppPairTests extends ShellTestCase {
-
- private AppPairsController mController;
- @Mock private SyncTransactionQueue mSyncQueue;
- @Mock private ShellTaskOrganizer mTaskOrganizer;
- @Mock private DisplayController mDisplayController;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
- when(mDisplayController.getDisplay(anyInt())).thenReturn(
- mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
- mController = new TestAppPairsController(
- mTaskOrganizer,
- mSyncQueue,
- mDisplayController);
- spyOn(mController);
- }
-
- @After
- public void tearDown() {}
-
- @Test
- @UiThreadTest
- public void testContains() {
- final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
- final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
-
- final AppPair pair = mController.pairInner(task1, task2);
- assertThat(pair.contains(task1.taskId)).isTrue();
- assertThat(pair.contains(task2.taskId)).isTrue();
-
- pair.unpair();
- assertThat(pair.contains(task1.taskId)).isFalse();
- assertThat(pair.contains(task2.taskId)).isFalse();
- }
-
- @Test
- @UiThreadTest
- public void testVanishUnpairs() {
- final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
- final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
-
- final AppPair pair = mController.pairInner(task1, task2);
- assertThat(pair.contains(task1.taskId)).isTrue();
- assertThat(pair.contains(task2.taskId)).isTrue();
-
- pair.onTaskVanished(task1);
- assertThat(pair.contains(task1.taskId)).isFalse();
- assertThat(pair.contains(task2.taskId)).isFalse();
- }
-
- @Test
- @UiThreadTest
- public void testOnTaskInfoChanged_notSupportsMultiWindow() {
- final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
- final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
-
- final AppPair pair = mController.pairInner(task1, task2);
- assertThat(pair.contains(task1.taskId)).isTrue();
- assertThat(pair.contains(task2.taskId)).isTrue();
-
- task1.supportsMultiWindow = false;
- pair.onTaskInfoChanged(task1);
- verify(mController).unpair(pair.getRootTaskId());
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
deleted file mode 100644
index 505c153eff9c..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
+++ /dev/null
@@ -1,104 +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.apppairs;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.hardware.display.DisplayManager;
-
-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.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/** Tests for {@link AppPairsController} */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AppPairsControllerTests extends ShellTestCase {
- private TestAppPairsController mController;
- private TestAppPairsPool mPool;
- @Mock private SyncTransactionQueue mSyncQueue;
- @Mock private ShellTaskOrganizer mTaskOrganizer;
- @Mock private DisplayController mDisplayController;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
- when(mDisplayController.getDisplay(anyInt())).thenReturn(
- mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
- mController = new TestAppPairsController(
- mTaskOrganizer,
- mSyncQueue,
- mDisplayController);
- mPool = mController.getPool();
- }
-
- @After
- public void tearDown() {}
-
- @Test
- @UiThreadTest
- public void testPairUnpair() {
- final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
- final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
-
- final AppPair pair = mController.pairInner(task1, task2);
- assertThat(pair.contains(task1.taskId)).isTrue();
- assertThat(pair.contains(task2.taskId)).isTrue();
- assertThat(mPool.poolSize()).isGreaterThan(0);
-
- mController.unpair(task2.taskId);
- assertThat(pair.contains(task1.taskId)).isFalse();
- assertThat(pair.contains(task2.taskId)).isFalse();
- assertThat(mPool.poolSize()).isGreaterThan(1);
- }
-
- @Test
- @UiThreadTest
- public void testUnpair_DontReleaseToPool() {
- final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
- final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
-
- final AppPair pair = mController.pairInner(task1, task2);
- assertThat(pair.contains(task1.taskId)).isTrue();
- assertThat(pair.contains(task2.taskId)).isTrue();
-
- mController.unpair(task2.taskId, false /* releaseToPool */);
- assertThat(pair.contains(task1.taskId)).isFalse();
- assertThat(pair.contains(task2.taskId)).isFalse();
- assertThat(mPool.poolSize()).isEqualTo(1);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java
deleted file mode 100644
index a3f134ee97ed..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java
+++ /dev/null
@@ -1,77 +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.apppairs;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
-
-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.SyncTransactionQueue;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/** Tests for {@link AppPairsPool} */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AppPairsPoolTests extends ShellTestCase {
- private TestAppPairsController mController;
- private TestAppPairsPool mPool;
- @Mock private SyncTransactionQueue mSyncQueue;
- @Mock private ShellTaskOrganizer mTaskOrganizer;
- @Mock private DisplayController mDisplayController;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
- mController = new TestAppPairsController(
- mTaskOrganizer,
- mSyncQueue,
- mDisplayController);
- mPool = mController.getPool();
- }
-
- @After
- public void tearDown() {}
-
- @Test
- public void testInitialState() {
- // Pool should always start off with at least 1 entry.
- assertThat(mPool.poolSize()).isGreaterThan(0);
- }
-
- @Test
- public void testAcquireRelease() {
- assertThat(mPool.poolSize()).isGreaterThan(0);
- final AppPair appPair = mPool.acquire();
- assertThat(mPool.poolSize()).isGreaterThan(0);
- mPool.release(appPair);
- assertThat(mPool.poolSize()).isGreaterThan(1);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
deleted file mode 100644
index 294bc1276291..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
+++ /dev/null
@@ -1,42 +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.apppairs;
-
-import static org.mockito.Mockito.mock;
-
-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.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-public class TestAppPairsController extends AppPairsController {
- private TestAppPairsPool mPool;
-
- public TestAppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
- DisplayController displayController) {
- super(organizer, syncQueue, displayController, mock(ShellExecutor.class),
- mock(DisplayImeController.class), mock(DisplayInsetsController.class));
- mPool = new TestAppPairsPool(this);
- setPairsPool(mPool);
- }
-
- TestAppPairsPool getPool() {
- return mPool;
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java
deleted file mode 100644
index 1ee7fff44892..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java
+++ /dev/null
@@ -1,36 +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.apppairs;
-
-import android.app.ActivityManager;
-
-import com.android.wm.shell.TestRunningTaskInfoBuilder;
-
-public class TestAppPairsPool extends AppPairsPool{
- TestAppPairsPool(AppPairsController controller) {
- super(controller);
- }
-
- @Override
- void incrementPool() {
- final AppPair entry = new AppPair(mController);
- final ActivityManager.RunningTaskInfo info =
- new TestRunningTaskInfoBuilder().build();
- entry.onTaskAppeared(info, null /* leash */);
- release(entry);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
new file mode 100644
index 000000000000..44ff35466ae2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.bubbles;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.floatThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.os.SystemClock;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test {@link MotionEvent} handling in {@link BubblesNavBarMotionEventHandler}.
+ * Verifies that swipe events
+ */
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner.class)
+public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
+
+ private BubblesNavBarMotionEventHandler mMotionEventHandler;
+ @Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private Runnable mInterceptTouchRunnable;
+ @Mock
+ private MotionEventListener mMotionEventListener;
+ private long mMotionEventTime;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(),
+ mWindowManager);
+ mMotionEventHandler = new BubblesNavBarMotionEventHandler(getContext(), positioner,
+ mInterceptTouchRunnable, mMotionEventListener);
+ mMotionEventTime = SystemClock.uptimeMillis();
+ }
+
+ @Test
+ public void testMotionEvent_swipeUpInGestureZone_handled() {
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 690));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 490));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 390));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 390));
+
+ verify(mMotionEventListener).onDown(0, 990);
+ verify(mMotionEventListener).onMove(0, -300);
+ verify(mMotionEventListener).onMove(0, -500);
+ verify(mMotionEventListener).onMove(0, -600);
+ // Check that velocity up is about 5000
+ verify(mMotionEventListener).onUp(eq(0f), floatThat(f -> Math.round(f) == -5000));
+ verifyZeroInteractions(mMotionEventListener);
+ verify(mInterceptTouchRunnable).run();
+ }
+
+ @Test
+ public void testMotionEvent_swipeUpOutsideGestureZone_ignored() {
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 500));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 100));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 100));
+
+ verifyZeroInteractions(mMotionEventListener);
+ verifyZeroInteractions(mInterceptTouchRunnable);
+ }
+
+ @Test
+ public void testMotionEvent_horizontalMoveMoreThanTouchSlop_handled() {
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 100, 990));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 100, 990));
+
+ verify(mMotionEventListener).onDown(0, 990);
+ verify(mMotionEventListener).onMove(100, 0);
+ verify(mMotionEventListener).onUp(0, 0);
+ verifyZeroInteractions(mMotionEventListener);
+ verify(mInterceptTouchRunnable).run();
+ }
+
+ @Test
+ public void testMotionEvent_moveLessThanTouchSlop_ignored() {
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 989));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 989));
+
+ verify(mMotionEventListener).onDown(0, 990);
+ verifyNoMoreInteractions(mMotionEventListener);
+ verifyZeroInteractions(mInterceptTouchRunnable);
+ }
+
+ @Test
+ public void testMotionEvent_actionCancel_listenerNotified() {
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_CANCEL, 0, 990));
+ verify(mMotionEventListener).onDown(0, 990);
+ verify(mMotionEventListener).onCancel();
+ verifyNoMoreInteractions(mMotionEventListener);
+ verifyZeroInteractions(mInterceptTouchRunnable);
+ }
+
+ private MotionEvent newEvent(int actionDown, float x, float y) {
+ MotionEvent event = MotionEvent.obtain(0L, mMotionEventTime, actionDown, x, y, 0);
+ mMotionEventTime += 10;
+ return event;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java
new file mode 100644
index 000000000000..991913afbb90
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.animation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.bubbles.BubbleExpandedView;
+import com.android.wm.shell.bubbles.TestableBubblePositioner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class ExpandedViewAnimationControllerTest extends ShellTestCase {
+
+ private ExpandedViewAnimationController mController;
+
+ @Mock
+ private WindowManager mWindowManager;
+
+ @Mock
+ private BubbleExpandedView mMockExpandedView;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(),
+ mWindowManager);
+ mController = new ExpandedViewAnimationControllerImpl(getContext(), positioner);
+
+ mController.setExpandedView(mMockExpandedView);
+ when(mMockExpandedView.getContentHeight()).thenReturn(1000);
+ }
+
+ @Test
+ public void testUpdateDrag_expandedViewMovesUpAndClipped() {
+ // Drag by 50 pixels which corresponds to 10 pixels with overscroll
+ int dragDistance = 50;
+ int dampenedDistance = 10;
+
+ mController.updateDrag(dragDistance);
+
+ verify(mMockExpandedView).setTopClip(dampenedDistance);
+ verify(mMockExpandedView).setContentTranslationY(-dampenedDistance);
+ verify(mMockExpandedView).setManageButtonTranslationY(-dampenedDistance);
+ }
+
+ @Test
+ public void testUpdateDrag_zOrderUpdates() {
+ mController.updateDrag(10);
+ mController.updateDrag(20);
+
+ verify(mMockExpandedView, times(1)).setSurfaceZOrderedOnTop(true);
+ verify(mMockExpandedView, times(1)).setAnimating(true);
+ }
+
+ @Test
+ public void testUpdateDrag_moveBackToZero_zOrderRestored() {
+ mController.updateDrag(50);
+ reset(mMockExpandedView);
+ mController.updateDrag(0);
+ mController.updateDrag(0);
+
+ verify(mMockExpandedView, times(1)).setSurfaceZOrderedOnTop(false);
+ verify(mMockExpandedView, times(1)).setAnimating(false);
+ }
+
+ @Test
+ public void testUpdateDrag_hapticFeedbackOnlyOnce() {
+ // Drag by 10 which is below the collapse threshold - no feedback
+ mController.updateDrag(10);
+ verify(mMockExpandedView, times(0)).performHapticFeedback(anyInt());
+ // 150 takes it over the threshold - perform feedback
+ mController.updateDrag(150);
+ verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt());
+ // Continue dragging, no more feedback
+ mController.updateDrag(200);
+ verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt());
+ // Drag below threshold and over again - no more feedback
+ mController.updateDrag(10);
+ mController.updateDrag(150);
+ verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt());
+ }
+
+ @Test
+ public void testShouldCollapse_doNotCollapseIfNotDragged() {
+ assertThat(mController.shouldCollapse()).isFalse();
+ }
+
+ @Test
+ public void testShouldCollapse_doNotCollapseIfVelocityDown() {
+ assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1);
+ mController.setSwipeVelocity(getVelocityAboveMinFling());
+ assertThat(mController.shouldCollapse()).isFalse();
+ }
+
+ @Test
+ public void tesShouldCollapse_doNotCollapseIfVelocityUpIsSmall() {
+ assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1);
+ mController.setSwipeVelocity(-getVelocityBelowMinFling());
+ assertThat(mController.shouldCollapse()).isFalse();
+ }
+
+ @Test
+ public void testShouldCollapse_collapseIfVelocityUpIsLarge() {
+ assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1);
+ mController.setSwipeVelocity(-getVelocityAboveMinFling());
+ assertThat(mController.shouldCollapse()).isTrue();
+ }
+
+ @Test
+ public void testShouldCollapse_collapseIfPastThreshold() {
+ mController.updateDrag(500);
+ assertThat(mController.shouldCollapse()).isTrue();
+ }
+
+ @Test
+ public void testReset() {
+ mController.updateDrag(100);
+ reset(mMockExpandedView);
+ mController.reset();
+ verify(mMockExpandedView, atLeastOnce()).setAnimating(false);
+ verify(mMockExpandedView).setContentAlpha(1);
+ verify(mMockExpandedView).setBackgroundAlpha(1);
+ verify(mMockExpandedView).setManageButtonAlpha(1);
+ verify(mMockExpandedView).setManageButtonAlpha(1);
+ verify(mMockExpandedView).setTopClip(0);
+ verify(mMockExpandedView).setContentTranslationY(-0f);
+ verify(mMockExpandedView).setManageButtonTranslationY(-0f);
+ verify(mMockExpandedView).setBottomClip(0);
+ verify(mMockExpandedView).movePointerBy(0, 0);
+ assertThat(mController.shouldCollapse()).isFalse();
+ }
+
+ private int getVelocityBelowMinFling() {
+ return getMinFlingVelocity() - 1;
+ }
+
+ private int getVelocityAboveMinFling() {
+ return getMinFlingVelocity() + 1;
+ }
+
+ private int getMinFlingVelocity() {
+ return ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();
+ }
+}
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 bb6026c36c97..7e6595f1abe2 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
@@ -182,8 +182,10 @@ public class DragAndDropPolicyTest {
info.configuration.windowConfiguration.setActivityType(actType);
info.configuration.windowConfiguration.setWindowingMode(winMode);
info.isResizeable = true;
- info.baseActivity = new ComponentName(getInstrumentation().getContext().getPackageName(),
+ info.baseActivity = new ComponentName(getInstrumentation().getContext(),
".ActivityWithMode" + winMode);
+ info.baseIntent = new Intent();
+ info.baseIntent.setComponent(info.baseActivity);
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.packageName = info.baseActivity.getPackageName();
activityInfo.name = info.baseActivity.getClassName();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
deleted file mode 100644
index 4523e2c9cba5..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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.fullscreen;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-
-import static org.junit.Assume.assumeFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.WindowConfiguration;
-import android.content.res.Configuration;
-import android.graphics.Point;
-import android.os.SystemProperties;
-import android.view.SurfaceControl;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.recents.RecentTasksController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-@SmallTest
-public class FullscreenTaskListenerTest {
- private static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
-
- @Mock
- private SyncTransactionQueue mSyncQueue;
- @Mock
- private FullscreenUnfoldController mUnfoldController;
- @Mock
- private RecentTasksController mRecentTasksController;
- @Mock
- private SurfaceControl mSurfaceControl;
-
- private Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
-
- private FullscreenTaskListener mListener;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mFullscreenUnfoldController = Optional.of(mUnfoldController);
- mListener = new FullscreenTaskListener(mSyncQueue, mFullscreenUnfoldController,
- Optional.empty());
- }
-
- @Test
- public void testAnimatableTaskAppeared_notifiesUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo info = createTaskInfo(/* visible */ true, /* taskId */ 0);
-
- mListener.onTaskAppeared(info, mSurfaceControl);
-
- verify(mUnfoldController).onTaskAppeared(eq(info), any());
- }
-
- @Test
- public void testMultipleAnimatableTasksAppeared_notifiesUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo animatable1 = createTaskInfo(/* visible */ true, /* taskId */ 0);
- RunningTaskInfo animatable2 = createTaskInfo(/* visible */ true, /* taskId */ 1);
-
- mListener.onTaskAppeared(animatable1, mSurfaceControl);
- mListener.onTaskAppeared(animatable2, mSurfaceControl);
-
- InOrder order = inOrder(mUnfoldController);
- order.verify(mUnfoldController).onTaskAppeared(eq(animatable1), any());
- order.verify(mUnfoldController).onTaskAppeared(eq(animatable2), any());
- }
-
- @Test
- public void testNonAnimatableTaskAppeared_doesNotNotifyUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
-
- mListener.onTaskAppeared(info, mSurfaceControl);
-
- verifyNoMoreInteractions(mUnfoldController);
- }
-
- @Test
- public void testNonAnimatableTaskChanged_doesNotNotifyUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
- mListener.onTaskAppeared(info, mSurfaceControl);
-
- mListener.onTaskInfoChanged(info);
-
- verifyNoMoreInteractions(mUnfoldController);
- }
-
- @Test
- public void testNonAnimatableTaskVanished_doesNotNotifyUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
- mListener.onTaskAppeared(info, mSurfaceControl);
-
- mListener.onTaskVanished(info);
-
- verifyNoMoreInteractions(mUnfoldController);
- }
-
- @Test
- public void testAnimatableTaskBecameInactive_notifiesUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo animatableTask = createTaskInfo(/* visible */ true, /* taskId */ 0);
- mListener.onTaskAppeared(animatableTask, mSurfaceControl);
- RunningTaskInfo notAnimatableTask = createTaskInfo(/* visible */ false, /* taskId */ 0);
-
- mListener.onTaskInfoChanged(notAnimatableTask);
-
- verify(mUnfoldController).onTaskVanished(eq(notAnimatableTask));
- }
-
- @Test
- public void testAnimatableTaskVanished_notifiesUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo taskInfo = createTaskInfo(/* visible */ true, /* taskId */ 0);
- mListener.onTaskAppeared(taskInfo, mSurfaceControl);
-
- mListener.onTaskVanished(taskInfo);
-
- verify(mUnfoldController).onTaskVanished(eq(taskInfo));
- }
-
- private RunningTaskInfo createTaskInfo(boolean visible, int taskId) {
- final RunningTaskInfo info = spy(new RunningTaskInfo());
- info.isVisible = visible;
- info.positionInParent = new Point();
- when(info.getWindowingMode()).thenReturn(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
- final Configuration configuration = new Configuration();
- configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
- when(info.getConfiguration()).thenReturn(configuration);
- info.taskId = taskId;
- return info;
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index 440a6f8fb59a..1eadeed7cd3b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -88,7 +88,7 @@ public class KidsModeTaskOrganizerTest {
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
mOrganizer = spy(new KidsModeTaskOrganizer(mTaskOrganizerController, mTestExecutor,
mHandler, mContext, mSyncTransactionQueue, mDisplayController,
- mDisplayInsetsController, Optional.empty(), mObserver));
+ mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver));
mOrganizer.initialize(mStartingWindowController);
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index ecf1c5d41864..6a6db8aa3c04 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -345,8 +345,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
false);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
- mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
- Surface.ROTATION_90, handlerWCT);
+ mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT);
verify(mMockDisplayAreaOrganizer, atLeastOnce()).onRotateDisplay(eq(mContext),
eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
@@ -358,8 +358,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
false);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
- mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
- Surface.ROTATION_90, handlerWCT);
+ mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT);
verify(mMockDisplayAreaOrganizer, never()).onRotateDisplay(eq(mContext),
eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
@@ -371,8 +371,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
true);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
- mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
- Surface.ROTATION_90, handlerWCT);
+ mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT);
verify(mMockDisplayAreaOrganizer, never()).onRotateDisplay(eq(mContext),
eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
@@ -384,8 +384,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
false);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
- mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
- Surface.ROTATION_90, handlerWCT);
+ mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT);
verify(mMockDisplayAreaOrganizer, atLeastOnce()).onRotateDisplay(eq(mContext),
eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
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 df18133adcfb..abd55dd7d606 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
@@ -76,6 +76,7 @@ public class PipControllerTest extends ShellTestCase {
@Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
+ @Mock private PipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
@Mock private PipSnapAlgorithm mMockPipSnapAlgorithm;
@Mock private PipMediaController mMockPipMediaController;
@Mock private PipTaskOrganizer mMockPipTaskOrganizer;
@@ -101,7 +102,8 @@ public class PipControllerTest extends ShellTestCase {
}).when(mMockExecutor).execute(any());
mPipController = new PipController(mContext, mMockDisplayController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
- mMockPipBoundsState, mMockPipMediaController,
+ mMockPipKeepClearAlgorithm,
+ mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mPipParamsChangedForwarder,
@@ -134,7 +136,8 @@ public class PipControllerTest extends ShellTestCase {
assertNull(PipController.create(spyContext, mMockDisplayController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
- mMockPipBoundsState, mMockPipMediaController,
+ mMockPipKeepClearAlgorithm,
+ mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mPipParamsChangedForwarder,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
new file mode 100644
index 000000000000..e0f7e35f8d02
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * Unit tests against {@link PipKeepClearAlgorithm}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class PipKeepClearAlgorithmTest extends ShellTestCase {
+
+ private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
+ private static final Rect DISPLAY_BOUNDS = new Rect(0, 0, 1000, 1000);
+
+ @Before
+ public void setUp() throws Exception {
+ mPipKeepClearAlgorithm = new PipKeepClearAlgorithm();
+ }
+
+ @Test
+ public void adjust_withCollidingRestrictedKeepClearAreas_movesBounds() {
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(50, 50, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
+ Set.of(), DISPLAY_BOUNDS);
+
+ assertFalse(outBounds.contains(keepClearRect));
+ }
+
+ @Test
+ public void adjust_withNonCollidingRestrictedKeepClearAreas_boundsDoNotChange() {
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(100, 100, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
+ Set.of(), DISPLAY_BOUNDS);
+
+ assertEquals(inBounds, outBounds);
+ }
+
+ @Test
+ public void adjust_withCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
+ // TODO(b/183746978): update this test to accommodate for the updated algorithm
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(50, 50, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
+ Set.of(keepClearRect), DISPLAY_BOUNDS);
+
+ assertEquals(inBounds, outBounds);
+ }
+
+ @Test
+ public void adjust_withNonCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(100, 100, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
+ Set.of(keepClearRect), DISPLAY_BOUNDS);
+
+ assertEquals(inBounds, outBounds);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 50f6bd7b4927..2b4d1a6390c9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -47,7 +47,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.StagedSplitBounds;
+import com.android.wm.shell.util.SplitBounds;
import org.junit.Before;
import org.junit.Test;
@@ -80,7 +80,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
mRecentTasksController = spy(new RecentTasksController(mContext, mTaskStackListener,
mMainExecutor));
mShellTaskOrganizer = new ShellTaskOrganizer(mMainExecutor, mContext,
- null /* sizeCompatUI */, Optional.of(mRecentTasksController));
+ null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController));
}
@Test
@@ -89,7 +89,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
setRawList(t1, t2);
- mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(StagedSplitBounds.class));
+ mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(SplitBounds.class));
verify(mRecentTasksController).notifyRecentTasksChanged();
reset(mRecentTasksController);
@@ -104,10 +104,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
setRawList(t1, t2);
// Verify only one update if the split info is the same
- StagedSplitBounds bounds1 = new StagedSplitBounds(new Rect(0, 0, 50, 50),
+ SplitBounds bounds1 = new SplitBounds(new Rect(0, 0, 50, 50),
new Rect(50, 50, 100, 100), t1.taskId, t2.taskId);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1);
- StagedSplitBounds bounds2 = new StagedSplitBounds(new Rect(0, 0, 50, 50),
+ SplitBounds bounds2 = new SplitBounds(new Rect(0, 0, 50, 50),
new Rect(50, 50, 100, 100), t1.taskId, t2.taskId);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2);
verify(mRecentTasksController, times(1)).notifyRecentTasksChanged();
@@ -139,8 +139,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
- StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 4);
- StagedSplitBounds pair2Bounds = new StagedSplitBounds(new Rect(), new Rect(), 3, 5);
+ SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4);
+ SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
@@ -162,7 +162,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
setRawList(t1, t2, t3);
// Add a pair
- StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 3);
+ SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 3);
mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds);
reset(mRecentTasksController);
@@ -245,15 +245,15 @@ public class RecentTasksControllerTest extends ShellTestCase {
: -1;
if (pair.mTaskInfo2 != null) {
- assertNotNull(pair.mStagedSplitBounds);
- int leftTopTaskId = pair.mStagedSplitBounds.leftTopTaskId;
- int bottomRightTaskId = pair.mStagedSplitBounds.rightBottomTaskId;
+ assertNotNull(pair.mSplitBounds);
+ int leftTopTaskId = pair.mSplitBounds.leftTopTaskId;
+ int bottomRightTaskId = pair.mSplitBounds.rightBottomTaskId;
// Unclear if pairs are ordered by split position, most likely not.
assertTrue(leftTopTaskId == taskId1 || leftTopTaskId == pair.mTaskInfo2.taskId);
assertTrue(bottomRightTaskId == taskId1
|| bottomRightTaskId == pair.mTaskInfo2.taskId);
} else {
- assertNull(pair.mStagedSplitBounds);
+ assertNull(pair.mSplitBounds);
}
}
assertTrue("Expected: " + Arrays.toString(expectedTaskIds)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index ad73c56950bd..068a60a12f5f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -9,7 +9,7 @@ import android.graphics.Rect;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.wm.shell.util.StagedSplitBounds;
+import com.android.wm.shell.util.SplitBounds;
import org.junit.Before;
import org.junit.Test;
@@ -17,7 +17,7 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class StagedSplitBoundsTest {
+public class SplitBoundsTest {
private static final int DEVICE_WIDTH = 100;
private static final int DEVICE_LENGTH = 200;
private static final int DIVIDER_SIZE = 20;
@@ -42,21 +42,21 @@ public class StagedSplitBoundsTest {
@Test
public void testVerticalStacked() {
- StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect,
+ SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
TASK_ID_1, TASK_ID_2);
assertTrue(ssb.appsStackedVertically);
}
@Test
public void testHorizontalStacked() {
- StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect,
+ SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
TASK_ID_1, TASK_ID_2);
assertFalse(ssb.appsStackedVertically);
}
@Test
public void testHorizontalDividerBounds() {
- StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect,
+ SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
TASK_ID_1, TASK_ID_2);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(0, dividerBounds.left);
@@ -67,7 +67,7 @@ public class StagedSplitBoundsTest {
@Test
public void testVerticalDividerBounds() {
- StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect,
+ SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
TASK_ID_1, TASK_ID_2);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left);
@@ -78,7 +78,7 @@ public class StagedSplitBoundsTest {
@Test
public void testEqualVerticalTaskPercent() {
- StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect,
+ SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
TASK_ID_1, TASK_ID_2);
float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH;
assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01);
@@ -86,7 +86,7 @@ public class StagedSplitBoundsTest {
@Test
public void testEqualHorizontalTaskPercent() {
- StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect,
+ SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
TASK_ID_1, TASK_ID_2);
float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH;
assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01);
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 0639ad5d0a62..68cb57c14d8c 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
@@ -61,7 +61,7 @@ public class MainStageTests extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
- mSyncQueue, mSurfaceSession, mIconProvider, null);
+ mSyncQueue, mSurfaceSession, mIconProvider);
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 a31aa58bdc26..3b42a48b5a40 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
@@ -66,7 +66,7 @@ public class SideStageTests extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mRootTask = new TestRunningTaskInfoBuilder().build();
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
- mSyncQueue, mSurfaceSession, mIconProvider, null);
+ mSyncQueue, mSurfaceSession, mIconProvider);
mSideStage.onTaskAppeared(mRootTask, mRootLeash);
}
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 eb9d3a11d285..a67853cfe745 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
@@ -40,8 +40,6 @@ import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
-import javax.inject.Provider;
-
public class SplitTestUtils {
static SplitLayout createMockSplitLayout() {
@@ -74,12 +72,10 @@ public class SplitTestUtils {
DisplayInsetsController insetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger, ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks,
- Provider<Optional<StageTaskUnfoldController>> unfoldController) {
+ Optional<RecentTasksController> recentTasks) {
super(context, displayId, syncQueue, taskOrganizer, mainStage,
sideStage, displayController, imeController, insetsController, splitLayout,
- transitions, transactionPool, logger, mainExecutor, recentTasks,
- unfoldController);
+ transitions, transactionPool, logger, mainExecutor, recentTasks);
// 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 ffaab652aa99..304ca66dd3bb 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
@@ -118,16 +118,16 @@ public class SplitTransitionTests extends ShellTestCase {
mSplitLayout = SplitTestUtils.createMockSplitLayout();
mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
- mIconProvider, null);
+ mIconProvider);
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
- mIconProvider, null);
+ mIconProvider);
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
- mTransactionPool, mLogger, mMainExecutor, Optional.empty(), Optional::empty);
+ mTransactionPool, mLogger, mMainExecutor, Optional.empty());
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
.when(mTransitions).startTransition(anyInt(), any(), any());
@@ -340,7 +340,7 @@ public class SplitTransitionTests extends ShellTestCase {
TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0);
info.addChange(mainChange);
info.addChange(sideChange);
- IBinder transition = mSplitScreenTransitions.startDismissTransition(null,
+ IBinder transition = mSplitScreenTransitions.startDismissTransition(
new WindowContainerTransaction(), mStageCoordinator,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, STAGE_TYPE_SIDE);
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -363,7 +363,7 @@ public class SplitTransitionTests extends ShellTestCase {
TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0);
info.addChange(mainChange);
info.addChange(sideChange);
- IBinder transition = mSplitScreenTransitions.startDismissTransition(null,
+ IBinder transition = mSplitScreenTransitions.startDismissTransition(
new WindowContainerTransaction(), mStageCoordinator, EXIT_REASON_DRAG_DIVIDER,
STAGE_TYPE_SIDE);
mMainStage.onTaskVanished(mMainChild);
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 42d998f6b0ee..af2c495c85c5 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
@@ -34,6 +34,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -58,6 +59,7 @@ 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.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -68,8 +70,6 @@ import org.mockito.MockitoAnnotations;
import java.util.Optional;
-import javax.inject.Provider;
-
/**
* Tests for {@link StageCoordinator}
*/
@@ -85,10 +85,6 @@ public class StageCoordinatorTests extends ShellTestCase {
@Mock
private SideStage mSideStage;
@Mock
- private StageTaskUnfoldController mMainUnfoldController;
- @Mock
- private StageTaskUnfoldController mSideUnfoldController;
- @Mock
private SplitLayout mSplitLayout;
@Mock
private DisplayController mDisplayController;
@@ -107,6 +103,7 @@ public class StageCoordinatorTests extends ShellTestCase {
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
+ private final Rect mRootBounds = new Rect(0, 0, 45, 60);
private SurfaceSession mSurfaceSession = new SurfaceSession();
private SurfaceControl mRootLeash;
@@ -119,11 +116,12 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mLogger,
- mMainExecutor, Optional.empty(), new UnfoldControllerProvider()));
+ mMainExecutor, Optional.empty()));
doNothing().when(mStageCoordinator).updateActivityOptions(any(), anyInt());
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
+ when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
when(mSplitLayout.isLandscape()).thenReturn(false);
mRootTask = new TestRunningTaskInfoBuilder().build();
@@ -168,13 +166,6 @@ public class StageCoordinatorTests extends ShellTestCase {
}
@Test
- public void testRootTaskAppeared_initializesUnfoldControllers() {
- verify(mMainUnfoldController).init();
- verify(mSideUnfoldController).init();
- verify(mStageCoordinator).onRootTaskAppeared();
- }
-
- @Test
public void testRootTaskInfoChanged_updatesSplitLayout() {
mStageCoordinator.onTaskInfoChanged(mRootTask);
@@ -184,26 +175,25 @@ public class StageCoordinatorTests extends ShellTestCase {
@Test
public void testLayoutChanged_topLeftSplitPosition_updatesUnfoldStageBounds() {
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
- clearInvocations(mMainUnfoldController, mSideUnfoldController);
+ final SplitScreenListener listener = mock(SplitScreenListener.class);
+ mStageCoordinator.registerSplitScreenListener(listener);
+ clearInvocations(listener);
mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
- verify(mMainUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- false);
- verify(mSideUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT, false);
+ verify(listener).onSplitBoundsChanged(mRootBounds, mBounds2, mBounds1);
}
@Test
public void testLayoutChanged_bottomRightSplitPosition_updatesUnfoldStageBounds() {
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null);
- clearInvocations(mMainUnfoldController, mSideUnfoldController);
+ final SplitScreenListener listener = mock(SplitScreenListener.class);
+ mStageCoordinator.registerSplitScreenListener(listener);
+ clearInvocations(listener);
mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
- verify(mMainUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT,
- false);
- verify(mSideUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- false);
+ verify(listener).onSplitBoundsChanged(mRootBounds, mBounds1, mBounds2);
}
@Test
@@ -314,20 +304,4 @@ public class StageCoordinatorTests extends ShellTestCase {
verify(mSplitLayout).applySurfaceChanges(any(), any(), any(), any(), any(), eq(false));
}
-
- private class UnfoldControllerProvider implements
- Provider<Optional<StageTaskUnfoldController>> {
-
- private boolean isMain = true;
-
- @Override
- public Optional<StageTaskUnfoldController> get() {
- if (isMain) {
- isMain = false;
- return Optional.of(mMainUnfoldController);
- } else {
- return Optional.of(mSideUnfoldController);
- }
- }
- }
}
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 157c30bcb6c7..5ee8bf3006a3 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
@@ -25,7 +25,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -72,8 +71,6 @@ public final class StageTaskListenerTests extends ShellTestCase {
private SyncTransactionQueue mSyncQueue;
@Mock
private IconProvider mIconProvider;
- @Mock
- private StageTaskUnfoldController mStageTaskUnfoldController;
@Captor
private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
private SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -92,8 +89,7 @@ public final class StageTaskListenerTests extends ShellTestCase {
mCallbacks,
mSyncQueue,
mSurfaceSession,
- mIconProvider,
- mStageTaskUnfoldController);
+ mIconProvider);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootTask.parentTaskId = INVALID_TASK_ID;
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
@@ -130,30 +126,6 @@ public final class StageTaskListenerTests extends ShellTestCase {
verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
}
- @Test
- public void testTaskAppeared_notifiesUnfoldListener() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- final ActivityManager.RunningTaskInfo task =
- new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
-
- mStageTaskListener.onTaskAppeared(task, mSurfaceControl);
-
- verify(mStageTaskUnfoldController).onTaskAppeared(eq(task), eq(mSurfaceControl));
- }
-
- @Test
- public void testTaskVanished_notifiesUnfoldListener() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- final ActivityManager.RunningTaskInfo task =
- new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
- mStageTaskListener.onTaskAppeared(task, mSurfaceControl);
- clearInvocations(mStageTaskUnfoldController);
-
- mStageTaskListener.onTaskVanished(task);
-
- verify(mStageTaskUnfoldController).onTaskVanished(eq(task));
- }
-
@Test(expected = IllegalArgumentException.class)
public void testUnknownTaskVanished() {
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
new file mode 100644
index 000000000000..798208956180
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
@@ -0,0 +1,341 @@
+/*
+ * 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.unfold;
+
+import static com.android.wm.shell.unfold.UnfoldAnimationControllerTest.TestUnfoldTaskAnimator.UNSET;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
+import android.testing.AndroidTestingRunner;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Predicate;
+
+/**
+ * Tests for {@link UnfoldAnimationController}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:UnfoldAnimationControllerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+public class UnfoldAnimationControllerTest extends ShellTestCase {
+
+ @Mock
+ private TransactionPool mTransactionPool;
+ @Mock
+ private UnfoldTransitionHandler mUnfoldTransitionHandler;
+ @Mock
+ private SurfaceControl mLeash;
+
+ private UnfoldAnimationController mUnfoldAnimationController;
+
+ private final TestShellUnfoldProgressProvider mProgressProvider =
+ new TestShellUnfoldProgressProvider();
+ private final TestShellExecutor mShellExecutor = new TestShellExecutor();
+
+ private final TestUnfoldTaskAnimator mTaskAnimator1 = new TestUnfoldTaskAnimator();
+ private final TestUnfoldTaskAnimator mTaskAnimator2 = new TestUnfoldTaskAnimator();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mTransactionPool.acquire()).thenReturn(mock(SurfaceControl.Transaction.class));
+
+ final List<UnfoldTaskAnimator> animators = new ArrayList<>();
+ animators.add(mTaskAnimator1);
+ animators.add(mTaskAnimator2);
+ mUnfoldAnimationController = new UnfoldAnimationController(
+ mTransactionPool,
+ mProgressProvider,
+ animators,
+ () -> Optional.of(mUnfoldTransitionHandler),
+ mShellExecutor
+ );
+ }
+
+ @Test
+ public void testAppearedMatchingTask_appliesUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f);
+ }
+
+ @Test
+ public void testAppearedMatchingTaskTwoDifferentAnimators_appliesUnfoldProgressToBoth() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 1);
+ mTaskAnimator2.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(1).build();
+ RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo1, mLeash);
+ mUnfoldAnimationController.onTaskAppeared(taskInfo2, mLeash);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f);
+ assertThat(mTaskAnimator2.mLastAppliedProgress).isEqualTo(0.5f);
+ }
+
+ @Test
+ public void testAppearedNonMatchingTask_doesNotApplyUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(0).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET);
+ }
+
+ @Test
+ public void testAppearedAndChangedToNonMatchingTask_doesNotApplyUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(0);
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET);
+ }
+
+ @Test
+ public void testAppearedAndChangedToNonMatchingTaskAndBack_appliesUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(0);
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(2);
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f);
+ }
+
+ @Test
+ public void testAppearedNonMatchingTaskAndChangedToMatching_appliesUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(0).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(2);
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f);
+ }
+
+ @Test
+ public void testAppearedMatchingTaskAndChanged_appliesUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f);
+ }
+
+ @Test
+ public void testShellTransitionRunning_doesNotApplyUnfoldProgress() {
+ when(mUnfoldTransitionHandler.willHandleTransition()).thenReturn(true);
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET);
+ }
+
+ @Test
+ public void testApplicableTaskDisappeared_resetsSurface() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId);
+
+ mUnfoldAnimationController.onTaskVanished(taskInfo);
+
+ assertThat(mTaskAnimator1.mResetTasks).contains(taskInfo.taskId);
+ }
+
+ @Test
+ public void testNonApplicableTaskAppearedDisappeared_doesNotResetSurface() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(0).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ mUnfoldAnimationController.onTaskVanished(taskInfo);
+
+ assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId);
+ }
+
+ @Test
+ public void testInit_initsAndStartsAnimators() {
+ mUnfoldAnimationController.init();
+
+ assertThat(mTaskAnimator1.mInitialized).isTrue();
+ assertThat(mTaskAnimator1.mStarted).isTrue();
+ }
+
+ 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 onStateChangeStarted() {
+ mListeners.forEach(UnfoldListener::onStateChangeStarted);
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ mListeners.forEach(unfoldListener -> unfoldListener.onStateChangeProgress(progress));
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ mListeners.forEach(UnfoldListener::onStateChangeFinished);
+ }
+ }
+
+ public static class TestUnfoldTaskAnimator implements UnfoldTaskAnimator {
+
+ public static final float UNSET = -1f;
+ private Predicate<TaskInfo> mTaskMatcher = (info) -> false;
+
+ Map<Integer, TaskInfo> mTasksMap = new HashMap<>();
+ Set<Integer> mResetTasks = new HashSet<>();
+
+ boolean mInitialized = false;
+ boolean mStarted = false;
+ float mLastAppliedProgress = UNSET;
+
+ @Override
+ public void init() {
+ mInitialized = true;
+ }
+
+ @Override
+ public void start() {
+ mStarted = true;
+ }
+
+ @Override
+ public void stop() {
+ mStarted = false;
+ }
+
+ @Override
+ public boolean isApplicableTask(TaskInfo taskInfo) {
+ return mTaskMatcher.test(taskInfo);
+ }
+
+ @Override
+ public void applyAnimationProgress(float progress, Transaction transaction) {
+ mLastAppliedProgress = progress;
+ }
+
+ public void setTaskMatcher(Predicate<TaskInfo> taskMatcher) {
+ mTaskMatcher = taskMatcher;
+ }
+
+ @Override
+ public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {
+ mTasksMap.put(taskInfo.taskId, taskInfo);
+ }
+
+ @Override
+ public void onTaskVanished(TaskInfo taskInfo) {
+ mTasksMap.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public void onTaskChanged(TaskInfo taskInfo) {
+ mTasksMap.put(taskInfo.taskId, taskInfo);
+ }
+
+ @Override
+ public void resetSurface(TaskInfo taskInfo, Transaction transaction) {
+ mResetTasks.add(taskInfo.taskId);
+ }
+
+ @Override
+ public void resetAllSurfaces(Transaction transaction) {
+ mTasksMap.values().forEach((t) -> mResetTasks.add(t.taskId));
+ }
+
+ @Override
+ public boolean hasActiveTasks() {
+ return mTasksMap.size() > 0;
+ }
+
+ public List<TaskInfo> getCurrentTasks() {
+ return new ArrayList<>(mTasksMap.values());
+ }
+ }
+}
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 1e5be6c3eed7..4b0ddd2fa2ef 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -201,8 +201,9 @@ void JankTracker::finishFrame(FrameInfo& frame, std::unique_ptr<FrameMetricsRepo
// If we are in triple buffering, we have enough buffers in queue to sustain a single frame
// drop without jank, so adjust the frame interval to the deadline.
if (isTripleBuffered) {
- deadline += frameInterval;
- frame.set(FrameInfoIndex::FrameDeadline) += frameInterval;
+ int64_t originalDeadlineDuration = deadline - frame[FrameInfoIndex::IntendedVsync];
+ deadline = mNextFrameStartUnstuffed + originalDeadlineDuration;
+ frame.set(FrameInfoIndex::FrameDeadline) = deadline;
}
// If we hit the deadline, cool!
diff --git a/libs/hwui/tests/unit/JankTrackerTests.cpp b/libs/hwui/tests/unit/JankTrackerTests.cpp
index 5b397de36a86..b67e419e7d4a 100644
--- a/libs/hwui/tests/unit/JankTrackerTests.cpp
+++ b/libs/hwui/tests/unit/JankTrackerTests.cpp
@@ -195,3 +195,68 @@ TEST(JankTracker, doubleStuffedThenPauseThenJank) {
ASSERT_EQ(3, container.get()->totalFrameCount());
ASSERT_EQ(2, container.get()->jankFrameCount());
}
+
+TEST(JankTracker, doubleStuffedTwoIntervalBehind) {
+ std::mutex mutex;
+ ProfileDataContainer container(mutex);
+ JankTracker jankTracker(&container);
+ std::unique_ptr<FrameMetricsReporter> reporter = std::make_unique<FrameMetricsReporter>();
+
+ uint64_t frameNumber = 0;
+ uint32_t surfaceId = 0;
+
+ // First frame janks
+ FrameInfo* info = jankTracker.startFrame();
+ info->set(FrameInfoIndex::IntendedVsync) = 100_ms;
+ info->set(FrameInfoIndex::Vsync) = 101_ms;
+ info->set(FrameInfoIndex::SwapBuffersCompleted) = 107_ms;
+ info->set(FrameInfoIndex::GpuCompleted) = 117_ms;
+ info->set(FrameInfoIndex::FrameCompleted) = 117_ms;
+ info->set(FrameInfoIndex::FrameInterval) = 16_ms;
+ info->set(FrameInfoIndex::FrameDeadline) = 116_ms;
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
+
+ ASSERT_EQ(1, container.get()->jankFrameCount());
+
+ // Second frame is long, but doesn't jank because double-stuffed.
+ // Second frame duration is between 1*interval ~ 2*interval
+ info = jankTracker.startFrame();
+ info->set(FrameInfoIndex::IntendedVsync) = 116_ms;
+ info->set(FrameInfoIndex::Vsync) = 116_ms;
+ info->set(FrameInfoIndex::SwapBuffersCompleted) = 129_ms;
+ info->set(FrameInfoIndex::GpuCompleted) = 133_ms;
+ info->set(FrameInfoIndex::FrameCompleted) = 133_ms;
+ info->set(FrameInfoIndex::FrameInterval) = 16_ms;
+ info->set(FrameInfoIndex::FrameDeadline) = 132_ms;
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
+
+ ASSERT_EQ(1, container.get()->jankFrameCount());
+
+ // Third frame is even longer, cause a jank
+ // Third frame duration is between 2*interval ~ 3*interval
+ info = jankTracker.startFrame();
+ info->set(FrameInfoIndex::IntendedVsync) = 132_ms;
+ info->set(FrameInfoIndex::Vsync) = 132_ms;
+ info->set(FrameInfoIndex::SwapBuffersCompleted) = 160_ms;
+ info->set(FrameInfoIndex::GpuCompleted) = 165_ms;
+ info->set(FrameInfoIndex::FrameCompleted) = 165_ms;
+ info->set(FrameInfoIndex::FrameInterval) = 16_ms;
+ info->set(FrameInfoIndex::FrameDeadline) = 148_ms;
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
+
+ ASSERT_EQ(2, container.get()->jankFrameCount());
+
+ // 4th frame is double-stuffed with a 2 * interval latency
+ // 4th frame duration is between 2*interval ~ 3*interval
+ info = jankTracker.startFrame();
+ info->set(FrameInfoIndex::IntendedVsync) = 148_ms;
+ info->set(FrameInfoIndex::Vsync) = 148_ms;
+ info->set(FrameInfoIndex::SwapBuffersCompleted) = 170_ms;
+ info->set(FrameInfoIndex::GpuCompleted) = 181_ms;
+ info->set(FrameInfoIndex::FrameCompleted) = 181_ms;
+ info->set(FrameInfoIndex::FrameInterval) = 16_ms;
+ info->set(FrameInfoIndex::FrameDeadline) = 164_ms;
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
+
+ ASSERT_EQ(2, container.get()->jankFrameCount());
+}